ES6目标,让JavaScript变成一个企业级的开发语言,不只仅限制与前端页面的脚本语言。javascript
标准(Standard): 用于定义与其余事物区别的一套规则 实现(Implementation): 某个标准的具体实施/真实实践
服务端使用
babel
编译
构建工具fis使用
插件:fis-parser-babel2
编译ES6前端
fis.match('**.ts', { parser: fis.plugin('babel2'), rExt: '.js' });
TypeScript与babel编译有区别:java
例如:对一个类的编译TypeScript编译的结果是一个闭包类,babel编译的结果是一个安全类.node
function _classCallCheck( instance, Constructor ) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Person = function Person() { _classCallCheck(this, Person); };
浏览器端编译
traceur
库来编译.
traceur库,引入两个库:traceur.js
和 treaceur-bootstrap.js
算法
写<script>的ES6中,须要指定type类型为module
注意:<script> 须要写在引入库的后边。编程
<script src="../lib/traceur.js" type="text/javascript" charset="utf-8"></script> <script src="../lib/traceur-bootstrap.js" type="text/javascript" charset="utf-8"></script> <script type="module"> class Person { } console.log(Person); </script>
let声明一个块级做用域,而且能够初始化该变量。用法与var相似。只能在所在的代码块内有效。json
块做用域:{}
构成的一个代码库处的做用域叫作块做用域。bootstrap
let 容许吧变量的做用域限制在块级域中。与var 不一样的是:var声明变量要么是全局的,要么是函数级的,而没法是块级的。数组
典型应用:for循环中的计数器
let定义的变量,会在块做用域保留下来,访问的就是当前循环的变量值。promise
for ( let j=0; j<4; j++ ) { } console.log(j); // error 报错。
for(let i = 0; i < 5; i++){ setTimeout(function(){ console.log(i); }, i * 100); }
做用域规则
用let定义的变量的做用域是定义它们的块内,以及包含在这个块中的子块
与var区别,是否能够有块级概念,是否有存在变量提高
function test () { let a = 10; if ( true ) { let a = 20; console.log(a); // 20 } console.log(a); // 10 } test();
不存在变量提高
console.log(b); // undefined console.log(a); // 报错ReferenceError let a = 10; var b = 10;
function foo(){ a = 20; // 报错 ReferenceError console.log(a); let a = 10; console.log(a); } foo();
let 的暂存死区
在同一个函数或同一个做用域中的let重复定义一个变量将引入TypeError
注意:switch中声明的变量。
if ( true ) { let a; let a; // TypeError thrown. }
若是块中存在let 和const。 凡是在声明以前就使用这些变量,就会报错。
let声明以前,该变量都是不可用的。称之为:暂时性死区(temproal dead zone简称 TDZ)
if (true) { // TDZ开始 a = 10; // ReferenceError console.log(tmp); // ReferenceError let a; // TDZ结束 console.log(tmp); // undefined a = 100; console.log(tmp); // 100 }
ES6中的TDZ 像是运行时的检查而不是语法声明上的概念.
if(true){ setTimeout(function(){ console.log(x); // 1 }); let x = 1; }
let特色:
为了避免出发死亡区,必须,变量先声明,后使用的规定。
let声明的全局变量并非全局对象的属性
let声明的变量知道控制流到达该变量被定义的代码行时才会被装载,因此在到达以前会使用该变量会触发错误,也就是所说的暂时性死亡区。没有分为预解析阶段。
块级做用域的意义:
ES5中,只有全局做用域和函数做用域,没有块级做用域。
缺点:
1:内存变量可能会覆盖外层变量。
2:用来计数的循环变量泄漏为全局变量。(循环内变量过分共享)
const声明一个只读的常量。 一旦声明,常量的值就不可更改。
意味着,const一旦声明常量,就必须当即初始化,不可留到之后赋值。
语法:const key = value;
在定义常量的前面,不能访问到常量,所以通常都将常量定义在文件的最前面。
例如:require()
模块的时候使用。
const http = require('http');
常量不能被重复定义,为了保证常量定义使用的时候的安全性。
const 和 let 的区别:
const具备let的全部特性,比let更加严格,不能够被重复定义(赋值)。
const 只是保证变量名指向的地址不变,并不保证该地址的数据不变。因此将一个对象声明为常量必须很是当心。
const foo = {}; foo.k1 = 100; console.log( foo.k1 ); foo = {}; // TypeError: "foo" is read-only // 常量foo 储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另外一个地址,可是对象自己是可变的,因此依然能够为其添加新的属性。
Objcet.freeze()
做用:冻结对象(不可为对象添加属性和和方法)
参数:被冻结的对象
const foo = {} Object.freeze(foo); // 常规模式时,下面一行不起做用, 忽略添加 // 严格模式时,该行会报错 foo.k1 = 100; console.log(foo);
结构的做用是能够快速取得数组或对象当中的元素或属性,而无需使用arr[x]
或者obj[key]
等传统方式进行赋值.
解构:从数组和对象中提取值,对变量进行赋值。
基本用法:
let [a, b, c] = [1, 2, 3]; // 从数组中提取值,按照对应位置,对变量赋值。 // 这种写法,本质上属于“模式匹配”。 只要等号两边的模式相同,左右的变量就会赋予对应的值。 let [foo, [[bar], baz]] = [1, [[10], 100]]; let [x, y, ...z] = ['a', 'b', 'c', 'd']; console.log(x,y,z); // a b ["c", "d"]
若是解构不成功,变量值为undefiend。
let [d] = []; // undefined let [e, f] = [1]; // 1 undefined
不彻底解构: 等号左边的模式,只匹配一部分等号右边的数组。
let [x, y] = [1, 2, 3]; console.log(x, y); // 1 2 let [a, [b], c] = [1, [2, 3], 4]; console.log(a, b, c); // 1 2 4
等号的右边不是数组,(严格的说,不是可遍历的结构),会报错。
// 报错 let [foo] = 1; let [foo] = false; let [foo] = NaN; let [foo] = undefined; let [foo] = null; let [foo] = {}; // 转为对象之后不具有Iterator接口(前面5个表达式), 自己就不具有Iterator接口(最后一个表达式)
只要某种数据结构具备Iterator接口,均可以采用数组形式的解构赋值。 (例如:函数执行以后的返回值)
默认值
解构赋值容许设置默认值
let [a = 100] = []; console.log(a); // 100
ES6内部使用严格相等 运算符(===),判断一个位置是否有值。若是一个数组成员不严格等于undefeind,默认值不会生效。
let [a = 1] = [undefined]; let [b = 10] = [null]; let [c = 100] = [NaN]; console.log(a, b, c); // 1 null NaN
若是默认值是一个表达式,这个表达式是惰性求值(只有在用到的时候才会求值)。
function fn () { console.log('a'); } let [x = fn()] = [1]; console.log(x); // 1 // fn(); 函数不会执行
默认值能够引用其它解构赋值的变量
默认值能够引用解构赋值的其它变量,但该变量必须已经声明,若是不声明,将会报错。
let [x = 1, y = x] = []; // x = 1 y = 1 let [x = y, y = 1] = []; // ReferenceError // x用到默认值y时,y尚未声明。
最为经常使用的地方是:函数参数的arguments类数组对象的解构赋值。
对象的解构与数组有一个重要的不一样之处,数组的元素是按次序排序,变量的取值由它的位置决定的,而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let {a, b} = {b: 10, a: 100}; console.log(a, b); // 10 100 let { c } = {d: 1000, e: 100 } console.log(c); // undefined
对象的解构赋值的内部机制:先找到同名属性,而后再赋值给对应的变量。真被赋值的是后者,而不是前者。
let {foo: baz} = {foo: 'aaa', bar: 'bbb'} console.log(baz); // aaa console.log(foo); // ReferenceError // 被赋值的是变量baz,而不是模式foo
默认值
默认值生效的条件:对象的属性值严格等于undefined。
let {x = 3} = {x: undefined} // x = 3; let {y = 10} = {y: null} console.log(x, y); // 3 null
若是解构失败,变量值等于undefined
字符串的解构赋值本质:字符串被转为类数组对象,类数组对象中有length属性,能够有Iterator接口,能够被遍历。
const [a, b, c, d, e] = 'hello'; console.log(a, b, c, d, e); // h e l l o
须要指定包装对象toString
解构规则:等号右边的值不是对象,就先将其转为对象。 undefind 和 null 没法转为对象,对其两者解构赋值,都会报错。
let {toString: s} = 10; console.log( s === Number.prototype.toString ); // true console.log(s); // function toString() { [native code] } let { toString: t } = true; console.log(t); // function toString() { [native code] }
能够解构赋值,可使用默认值
[[1, 2], [3, 4]].map( ([a, b]) => a + b ); // 3 7 [[, 2], [, 4]].map(function ( [a = 10, b] ) { return a + b; });
对于编译器来讲,一个式子究竟是模式,,仍是表达式,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。
问题:模式中出现的圆括号怎么处理。
解析规则:只要有可能致使解构的歧义,就不得使用圆括号。
建议:只要有可能,就不要再模式中放置圆括号。能不使用,就不使用圆括号。
不能使用圆括号的状况
变量声明语句中,不能带有圆括号
// 所有报错 var [(a)] = [1]; var {x: (c)} = {}; var ({x: c}) = {}; var {(x: c)} = {}; var {(x): c} = {};} var { o: ({ p: p }) } = { o: { p: 2 } }; // 都是变量声明语句,模式不能使用圆括号。
函数参数中,模式不能带有圆括号。
// 报错 function f([(z)]) { return z; }
赋值语句中,不能将整个模式,或嵌套模式中一层,放在圆括号之中。
// 所有报错 ({ p: a }) = { p: 42 }; ([a]) = [5];
可使用圆括号的状况:
赋值语句的非模式部分,可使用圆括号。
[(b)] = [3]; // 正确 // 模式是取数组的第一个成员,跟圆括号无关 ({ p: (d) } = {}); // 正确 // 模式是p,而不是d; [(parseInt.prop)] = [3]; // 正确 // 模式是取数组的第一个成员,跟圆括号无关
交换变量的值
[x, y] = [y, x]
从函数返回多个值
函数只能返回一个值,若是要返回多个值,只能将它们放在数组或对象里返回。
// 返回一个数组 function example() { return [1, 2, 3]; } var [a, b, c] = example(); // 返回一个对象 function example() { return { foo: 1, bar: 2 }; } var { foo, bar } = example();
函数参数定义
将一组参数与变量名对应。
// 参数是一组有次序的值 function f([x, y, z]) { ... } f([1, 2, 3]); // 参数是一组无次序的值 function f({x, y, z}) { ... } f({z: 3, y: 2, x: 1});
提取JSON数据
提取JSON对象中的数据
函数参数的默认值遍历Map结构
输入模块的指定方法
加载模块时,每每须要指定输入那些方法。
const { SourceMapConsumer, SourceNode } = require("source-map");
str.starsWith(str1, num);
判断字符串以指定参数开头
参数1:开头的字符
参数2:可选,指定开始查找的索引位置
返回布尔值
str.endWith(str1, num);
判断字符串以指定参数结束
参数1:结束的字符子串
参数2:可选,指定开始查找的索引位置
返回布尔值
str.includes(str1, num);
判断指定的参数字符串是否包含在字符串中。
参数1:被查询的字符串
参数2:可选,从那边开始查找索引位置。
返回布尔值
``能够紧跟一个函数名后边,该函数将被调用来处理这个模板字符串。 称之为:“标签模板”(tagged template);
alert`123`; alert(123); // 等同
标签模板实质并非模板,而是函数的调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。
若是模板字符里面有变量,就不在是简单的调用,而是会将模板字符串先处理成多个参数,再调用函数。
语法:${表达式}
须要经过``来声明字符串,才会生效。
若是使用''
单引号声明没有效果。并不会解析模板中的表达式。
//let num = `十`; let num = `5`; let intro = `<h1>笑一笑,${5 * 2}年少</h1>`;
重复字符串
参数:指定重复的次数
let num = '10'; let intro = `<h1>笑一笑,${num * 2}年少</h1>`; let introT = intro.repeat(2); console.log(intro); console.log(introT);
返回原始字符串,该方法不会将字符串中的转义字符转义
这个方法特殊,不须要使用()
调用.
是String底下的一个方法,直接经过String调用便可。
console.log('success \n 1'); let str = String.raw`success \n 1`; console.log(str);
ES6在Number对象上,提供了Number.isFinite() 和 Number.isNaN();两个方法。
用来检查Infinite和NaN这两个特殊值。
检查是不是做为数字存在,对于不存在或者是NaN返回值为:false,对于数字返回值为:true
Number.isFinite(15); // true Number.isFinite(0.8); // true Number.isFinite(NaN); // false Number.isFinite(Infinity); // false Number.isFinite(-Infinity); // false Number.isFinite('foo'); // false Number.isFinite('15'); // false Number.isFinite(true); // false
// 实现方式 (function (global) { var global_isFinite = global.isFinite; Object.defineProperty(Number, 'isFinite', { value: function isFinite(value) { return typeof value === 'number' && global_isFinite(value); }, configurable: true, enumerable: false, writable: true }); })(this);
当参数是NaN的时候,才返回true,其它状况都返回false。
Number.isNaN(NaN) // true Number.isNaN(15) // false Number.isNaN('15') // false Number.isNaN(true) // false Number.isNaN(9/NaN) // true Number.isNaN('true'/0) // true Number.isNaN('true'/'true') // true
// 实现方式 (function (global) { var global_isNaN = global.isNaN; Object.defineProperty(Number, 'isNaN', { value: function isNaN(value) { return typeof value === 'number' && global_isNaN(value); }, configurable: true, enumerable: false, writable: true }); })(this);
与传统的全局方法isFinte()方法isNaN()的区别:
传统方法先调用Number()将非数值的值转为数值,再进行判断。
Number.isFinte()和isNaN();只对数值有效,非数值一概返回false.
ES6将全局方法parseInt();和parseFloat();方法 移植到Number对象上,行为彻底保持不变。
目的:逐渐减小全局性方法,是的语言逐步模块化。
当参数是正整数,负整数,0,-0,的时候返回值为:true。其它状况下返回值为:false
在JavaScript内部,整数和浮点数使用一样的存储方法,因此3和3.0被视为同一个值。
Math.sign();用来判断一个数究竟是整数,负数,仍是零。
返回值:
Math.sign(''); // NaN
Math.sign('a'); // NaN
Math.sign(NaN); // NaN
Math.sign(false); // NaN
Math.sign(undefined); // NaN
Math.sign(0); // 0
Math.sign(-0); // -0
Math.sign(-2); // -1
Math.sign(+2); // 1
// 模拟 Math.sign(); 方法 Math.sign = Math.sign || function ( x ) { x = +x; if ( x === 0 || isNaN(x) ) { return x; } return x > 0 ? 1 : 1; }
Math.trunc(); 用于去除一个数的小数部分,返回 整数部分。
非数值,Math.trunc()内部先使用Number();将其转为数值。
Math.trunc(123.456); // 123
对于空值和没法截取整数的值,返回NaN
Math.trunc(NaN); // NaN Math.trunc('hello'); // NaN // 模拟Math.trunc(); Math.trunc = Math.trunc || function ( x ) { return x < 0 ? Math.ceil(x) : Math.floor(x); }
做用:将类数组对象转化成数组对象
参数1:类数组对象
参数2:可选,处理的回调函数。能够处理类对象每一个成员
该回调函数中参数1:value值,(类数组的成员), 参数2:索引值。返回值会保留最终结果,若是没有返回值,那么from函数会返回由undefined构成的一个新数组
若是是转一个真正的数组,Array.from();会返回一个如出一辙的新数组。
更常常的使用时:arguments 和 获取的DOM元素。
// 类数组对象 let arrayLike = { 0: 'a', 1: 'b', length: 2 } // ES5的写法 let arr1 = [].slice.call(arrayLike); // ES6的写法 let arr2 = Array.from(arrayLike); console.log(arr1, arr2); // ['a', 'b']['a', 'b']
let arrayLike = { 0: 'a', 1: 'b', length: 2 } // ES6的写法 let arr2 = Array.from(arrayLike, function ( value, index ) { return value + '-xixi'; });
Array.of();将一组值,转为数组。
返回值:返回参数值组成的数组,若是没有参数,就返回一个空数组。
做用:弥补数组构造函数Array()的不足。由于参数个数的不一样,会致使Array()的行为有所差别。
Array()若是是一个参数,则表示当前数组的长度。若是是二个或二个以上,则表示数组成员。
Array.of(1, 2, 3, 'xixi');
Array.of();基本上能够用来替代Array()或new Array(); 而且不存在因为参数不一样而致使的重载问题。
Array.of() // [] Array.of(undefined) // [undefined] Array.of(1) // [1]
// 模拟Arraay.of();方法 function ArrayOf () { return [].slice.call(arguments); }
find();方法和findIndex();方法
find();
做用:获取第一个符合条件的数组成员。
参数:回调函数。全部的数组成员一次执行该回调函数。知道找出一个返回值为true的成员。若是没有符合条件的成员,则返回false。
回调函数中的参数: 当前value值,当前索引值index(可选), 原数组arr(可选)
[1, 2, 23, 10].find( (n) => n > 10 ); // 23 [1, 2, 3].find( ( value, index, arr ) => value < 2 ) // 1
findIndex();
做用:返回第一个符合条件的数组成员的索引值,若是全部成员都不符合条件,则返回 -1.
参数:和find();参数同样是一个回调函数。
['cyan', 'pink', 'tan', 'red'].findIndex( (value, index, arr) => Object.is('tan', value) );
提出的解决问题:
indexOf();没法检测出成员中是否有NaN
[NaN].indexOf(NaN); // -1
findIndex();搭配 Object.is(); 能够检测出来
[10,NaN].findIndex( (value) => Object.is(NaN, value) ); // 1
使用给定值,填充一个数组。
参数1:填充的值
参数2:可选,填充的起始位置。
参数3: 可选,填充的结束位置。
keys() & values(); & entrices();
keys();
获取数组全部成员的索引值。
返回的是一个迭代器对象。
须要经过 for-of
遍历数组迭代器
let colors = ['cyan', 'pink', 'tan', 'red']; let Idx = colors.keys(); console.log( Idx ); // ArrayIterator {} for ( let index of Idx ) { console.log(index); }
values();
获取全部成员的值,返回一个数组迭代器对象。
let colors = ['cyan', 'pink', 'tan', 'red']; let vals = colors.values(); console.log( vals ); // ArrayIterator {} for ( let val of vals ) { console.log(val); }
entries();
对键值对的遍历。
将数组成员的索引值以及数组成员的值,保留在一个新数组中。
let colors = ['cyan', 'pink', 'tan', 'red']; let entri = colors.entries(); console.log(entri); for ( let entris of entri ) { console.log( entris ); } // 输出: // [0, "cyan"] // [1, "pink"] // [2, "tan"] // [3, "red"]
若是不使用for-of
遍历,能够手动调用遍历器对象的next();方法,进行遍历。
let colors = ['cyan', 'pink', 'tan', 'red']; let entri = colors.entries(); console.log( entri.next().value ); // [0, "cyan"] console.log( entri.next().value ); // [1, "pink"] console.log( entri.next().value ); // [2, "tan"] console.log( entri.next().value ); // [3, "red"] // next();方法返回:Object {value: Array[2], done: false}
数组的空位:数组的某一个位置没有任何值。
例如:Array构造函数返回的数组都是空位。
空位不是undefined,一个位置的值等于undefined,依然是有值的。空位是没有任何值
注意:空位不是undefiend,一个位置的值等于undefeind。依然是有值。空位是没有任何值。
0 in [undefined, undefined, undefined] // true 0 in [, , ,] // false // 第一个数组的0号位置是有值的,第二个数组的 0 号位置没有值。
ES5中对于空位的不一致:
// forEach();
[,'a'].forEach((x,i) => console.log(i)); // 1
// filter();
['a',,'b'].filter(x => true) // ['a','b']
// every();
[,'a'].every(x => x==='a') // true
// some();
[,'a'].some(x => x !== 'a') // false
// map();
[,'a'].map(x => 1) // [,1]
// join();
[,'a',undefined,null].join('#') // "#a##"
// toString();
[,'a',undefined,null].toString() // ",a,,"
ES6则明确将空位转成undeinfed
因为空位的处理规则很是不统一,因此应该尽可能避免出现空位。
ES6容许直接写入变量和函数,做为对象的属性和方法。
在定义一个对象的时候,若是对象的属性与属性值对应的变量名同名,能够简写属性名。
let objName = 'bar'; let obj1 = {objName} console.log( obj1 ); // Object {objName: "bar"} // 等同于 let obj2 = {objName: objName} // Object {objName: "bar"} console.log(obj2); // 在对象之中,只写属性名,不写属性值,这时候,属性值等于属性名所表明的变量。
// 参数简写 function Person ( name, age ) { return {name, age} // ES6 容许直接引入变量,不用对对象添加同名属性 } console.log( Person('cyan', 22) ); // Object {name: "cyan", age: 22} // 等同于 function Person(name , age) { return {name: name, age: age}; }
// 方法的简写 // 若是对象属性名与对象属性名对应的方法名称相同,能够省略属性以及 function关键字, 像定义类中的方法同样定义这个属性方法。 let obj1 = { name: 'cyan', age: 22, sayAge: function () { console.log(this.age); // 22 }, sayName () { // ES6容许定义方法的时候,能够省略方法对应的属性名,和 fucntion 关键字 console.log(this.name); // cyan } } obj1.sayName(); obj1.sayAge(); // sayName等同于 var obj1 = { sayName: function() { console.log(this.name); } }
// 这种属性简写用于函数返回很是方便 function getPoint() { var x = 1; var y = 10; return {x, y}; } getPoint(); // {x:1, y:10}
JavaScript语言定义对象的属性,有两种方式。
// 方法一 obj.foo = true; // 标识符做为属性名 // 方法二 obj['a' + 'bc'] = 123; // 表达式做为属性名
// 容许定义对象时候,对对象属性应用表达式 let a = 'num'; let b = 'ber'; let obj = { [a + b]: 100 } console.log( obj );
// 表达式也能够用于方法名 let obj = { ['h'+'ello']() { return 'hi'; } }; obj.hello() // hi
注意:属性名表达式不能与简洁表示法同时使用,会报错。
// 报错 let a = 'b'; let b = 'pink'; let color = { [a] } console.log(color); // 正确 let a = 'cyan'; let color = { [a]: 'pink' } console.log(color);
函数的name属性,返回函数名。
对象方法也是函数,,所以也有name属性。
let obj = { name: 'cyan', sayName() { return this.name; } } console.log( obj.sayName.name ); // sayName
特殊:
bind();方法创造的函数,name属性返回"bound"加上原函数的名字。
Function构造函数创造的函数,name属性返回"anonymous".
console.log( (new Function()).name ); // anonymous let doSomething = function () {} console.log( doSomething.bind().name ); //bound doSomething
Object.is();
做用判断两个参数是否相等。
能够判断0
与-0
,是否相等。
ES5中,比较两个值是否相等使用:==
或者===
==
缺点:自动转换数据类型===
缺点:NaN不等于自身,以及0
等于-0
ES6提出“Same-value equality”(同值相等)算法.
用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
+0 === -0 //true NaN === NaN // false Object.is(+0, -0) // false Object.is(NaN, NaN) // true
Object.assgin();
做用:用于对象合并。
将源对象(source)的全部可枚举属性,复制到目标对象(target)。
参数1:目标对象。
后面的参数都是源对象。
只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。
若是目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
若是只有一个参数对象,则返回该参数对象。
let obj1 = { x: 1, y: 2 } let obj2 = { a: 'a', b: 'b' } console.log( Object.assign(obj1, obj2) );
若是参数不是对象,则会先转成对象而后返回。
undefeind和null没法转成对象,做为参数,会报错。
Object.assign(2); // Number {[[PrimitiveValue]]: 2} Object.assign(undefined) // 报错 Object.assign(null) // 报错
Object.assign();是浅拷贝,而不是深拷贝。若是源对象某个属性的值是对象,那么目标对象拷贝获得的是这个对象的引用。
let obj1 = { x: 1, y: 2 } let obj2 = { a: 'a', b: 'b' } let resObj = Object.assign(obj1, obj2); console.log( resObj ); console.log( obj1 ); console.log( resObj == obj1 ); // true console.log( obj2 );
assign用途
为对象添加属性
class Point { constructor (x, y) { Object.assign(this,{x, y}); } } let p = new Point(10, 20); console.log(p); // Point {x: 10, y: 20} // 经过Object.assign();将x属性和y属性添加到Point类的对象实例。
为对象添加方法
Object.assign(Some.prototype, { someMethod1 ( arg1, arg2 ) { // .... } someMethod2 ( arg1, arg2 ) { // .... } }) // 等价 Some.prototype.someMethod1 = function ( arg1, arg2 ) { // .... } Some.prototype.someMethod2 = function ( arg1, arg2 ) { // .... }
克隆对象
function clone(origin) { return Object.assign({}, origin); } // 将原对象拷贝到一个空对象,获得了原始对象的克隆。
合并多个对象
将多个对象合并到某个对象,能够是空对象中(合并以后返回新的对象)
const merge = (target, ...sources) => Object.assign({}, ...sources);
ES6中共有5中能够遍历属性的方法:
遍历规则:
Object.keys();
做用: 参数对象自身的(不含继承的) 全部可遍历属性的键名。
返回数组
Object.values();
做用:参数对象自身的(不含继承的) 全部可遍历属性的键值。
返回数组
Object.entries();
做用:参数对象自身的(不含继承的) 全部可遍历属性的键值对数组。
返回数组
let obj = { a: 1, b: 2 }; console.log( Object.entries(obj) ); [ ["a", 1], ["b", 2] ]
ES6引入Symbol的缘由:根本上防止属性名的冲突。
ES6引入一种新的原始数据类型Symbol,表示独一无二的值。
它是JavaScript语言的第七种中数据类型。前六中是:Undefiend,Null,布尔值(Boolean),字符串(String),数值(Number),对象(Object)
let s = Symbol(); typeof s // symbol
Symbol值经过Symbol函数生成。
对象的属性名能够有两种类型,一种是:string。 一种是:Symbol。(凡是属性名属于Symbol类型,就是独一无二,能够保证不会与其它属性名产生冲突)
注意:Symbol函数前不能使用new关键字,不然会报错。由于:Symbol是一个原始类型的值,不是对象。因为Symbol值不是对象,因此不能添加属性。基本上,它是一种相似于字符串的数据类型。
Symbol函数能够接收一个字符串做为参数,表示对Symbol实例的描述,
做用:控制台显示或转为字符串时,比较容易区分。
返回值不相等
注意:Symbol函数的参数只是表示对当前Symbol值的描述,所以相同参数的Symbol函数的返回值是不相等的。
// 没有参数 let s1 = Symbol(); let s2 = Symbol(); console.log(s1 == s2); // false // 参数 let s3 = Symbol('a'); let s4 = Symbol('b'); console.log(s3 == s4); // false
不能与其它类型值进行运算
let sym = Symbol('My Symbol'); console.log('your symbol is' + sym); // TypeError: Cannot convert a Symbol value to a string console.log(`your symbol is` + sym); // TypeError: Cannot convert a Symbol value to a string
能够显示转换为String,Boolean。(注意:不可以转为数值类型)
let sym = Symbol('My Symbol'); console.log(String(sym)); console.log(sym.toString()); console.log(Boolean(sym)); console.log(!sym); console.log(Number(sym)); // TypeError: Cannot convert a Symbol value to a number console.log(sym + 2); // // TypeError: Cannot convert a Symbol value to a number
Symbol与字符串的区别:
因为每一个Symbol值都是不相等的,意味着Symbol值能够做为标识符。
用于对象的属性名,就能保证不会出现同名的属性。 对于一个对象由多个模块构成的模块状况,能防止某一个键被不当心改写或覆盖。
Symbol值做为属性名时,是公开属性,而不是私有属性。
let mySymbol = Symbol('a'); // 第一种方法 let a = {}; a[mySymbol] = 'hello'; // 第二种写法 let b = { [mySymbol]: 'hell2' }; // 在对象的内部,使用Symbol值定义属性时,Symbol值必须放在方括号之中。 // 第三种写法 let c = {}; Object.defineProperty(c, mySymbol, { value: 'hello3' }); console.log(a[mySymbol], b[mySymbol], c[mySymbol]);
注意:Symbol值做为对象的属性名,不能使用点语法。
let mySymbol = Symbol('a'); let obj = {}; obj[mySymbol] = 'hello'; console.log( obj[mySymbol] ); // hello console.log( obj.mySymbol ); // undefind
定义一组常量,保证这组常量值是不相等。
log.levels = { DEBUG: Symbol('debug'), INFO: Symbol('info'), WARN: Symbol('warn') }; log(log.levels.DEBUG, 'debug message'); log(log.levels.INFO, 'info message');
魔术字符串:在代码之中出现屡次,与代码造成强耦合的某一个具体的字符串或者数值。
缺点:不利于未来的维护和修改。
消除魔术字符串的方法:把它写成一个变量,或者常量。
变量或者常量等于那个值并不重要,只要确保不会跟其它的属性冲突便可。
const shapeType = { triangle: Symbol(), triangleTow: Symbol() } function getArea ( shape, options ) { let area = 0; switch ( shape ) { case shapeType.triangle: area = .5 * options.width * options.height; break; case shapeType.triangleTow: area = .7 * options.width * options.height; break; } return area; } let a = getArea( shapeType.triangleTow, { width: 20, height: 20 } ); let b = getArea( shapeType.triangle, { width: 10, height: 10 } ); console.log( a,b ); // 280 , 50
Symbol做为属性名,该属性不会出如今for-in
, for-of
循环中,也不会被Object.kes();. Object.getOwnProperTyNames();返回。
Symbol并非私有属性,ES6中提供Object.getOwnPropertySymbols();
Object.getOwnPropertySymbols();
做用:获取指定对象的全部Symbol属性名。
返回一个数组。成员是当前对象的全部用做属性名的Symbol值。
let obj1 = {}; let c = Symbol('c'); let d = Symbol('d'); obj1[c] = 'cyan'; obj1[d] = 'tan'; let objectSymbol = Object.getOwnPropertySymbols(obj1); console.log( objectSymbol ); // [Symbol(c), Symbol(d)]
Reflect.ownKeys();
做用:返回全部类型的键名,包括常规键名和Symbol键名。
let obj = { [Symbol('my_key')]: 1, enum: 2, nonEnum: 3 }; console.log( Reflect.ownKeys(obj) ); // ["enum", "nonEnum", Symbol(my_key)]
以Symbol值做为名称的属性,不会被常规方法遍历获得。能够为对象定义一些非私有的,但又但愿只用于内部的方法。形成一种非私有话的内部的效果。
Symbol.for();
做用:使用同一个Symbol的值。
参数:一个字符串。
过程:接收一个字符串后,搜索有没有以该参数做为名称的Symbol值。若是有,就返回这个Symbol值,不然就新建并返回一个以该字符串为名称的Symbol值。(具备登记机制,被登记的会在全局环境中供搜索)
let s1 = Symbol.for('a'); let s2 = Symbol.for('a'); console.log( s1 === s2 ); // true
Symbol.keFor();
返回:一个已经登记的Symbol类型值的key。
let s1 = Symbol.for('a'); let s2 = Symbol.for('b'); let s3 = Symbol('b'); console.log( Symbol.keyFor(s1), Symbol.keyFor(s2), Symbol.keyFor(s3) ); // a b undefined
Signleton模式:指的是调用一个类,任什么时候候返回的都是同一个实例。
对于 Node 来讲,模块文件能够当作是一个类。怎么保证每次执行这个模块文件,返回的都是同一个实例呢?
很容易想到,能够把实例放到顶层对象global。
对于Node来讲,模块文件能够当作是一个类。怎么保证每次执行这个模块文件,都是返回同一个实例呢? 能够把实例挂载到顶层对象global。
// mod.js function A () { this.foo = 'hello'; } if ( !global._foo ) { global._foo = new A(); } module.exprots = global._foo; // 加载mod.js let mod = require('./mod.js'); console.log( mod.foo );
存在问题: 挂载到global的属性,是可写的,任何文件均可以修改。
防止这种状况,可使用Symbol.
// mod.js const FOO_KEY = Symbol.foo('foo'); function A () { this.foo = 'hello'; } if ( !global[FOO_KEY] ) { global[FOO_KEY] = new A(); } module.exprots = global[FOO_KEY];
Proxy做用:修改某些操做的默认行为。 在目标对象以前设置屏障,外界对该对象的访问,都必须先经过这层拦截,来保护该对象(内部的属性,状态,方法,行为等等)。(Proxy的操做等同于在语言层面作出修改,因此属性一种“元编程”,即对编程语言进行编程)
提供一种机制,能够对外界的访问进行过滤和改写。
Proxy这个词的原意是代理,这里表达hi由它来“代理”某些操做,能够译为“代理器”。
语法: new Proxy(代理对象, 屏蔽对象);
屏障的方法,比较特殊,并非任意定,只能经过get来获取代理对象的信息,set来设置原始对象的额信息。可是它们不是直接处理对象的,在处理以前会过滤一些人为操做。屏蔽对象配置拦截行为(对于每个被代理的操做,须要提供一个对应的处理函数,该函数将拦截对应的操做。)
get();
参数:target,key,receiver
set();
参数:target,key,value,receiver
let Start = { count: 0 } let obj1 = new Proxy(Start, { get: function ( target, key ) { console.log(`getting ${key}!`); return target[key]; }, set: function ( target, key, value ) { console.log(`setting ${key}!`); return target[key] = value; } }); console.log( obj1.count ); obj1.count = 1; console.log( obj1.count );
注意:是的Proxy起做用,必须针对Proxy实例进行操做, 而不是针对目标对象进行操做。
若是屏蔽对象没有进行任何拦截操做,那就等同于直接经过原对象,操做原对象
let a = {}; let b = {}; let proxy = new Proxy(a, b); proxy.tag = 'javascript'; console.log(a.tag); // javascript
将Proxy对象,能够设置到Object.proxy属性上,从而达到能够从Object对象上调用。
Proxy实例能够做为其余对象的原型对象。
// 放在object.proxy 属性上 let object = { proxy: new Proxy(target, handler) } // 其它对象的原型上 let proxy = new Proxy({}, { get: function ( target, property ) { console.log( target[property] ); return 10; } }); let obj = Object.create(proxy); console.log(obj.time); // 10
同一个拦截器函数,能够设置多个操做。
var handler = { get: function(target, name) { if (name === 'prototype') { return Object.prototype; } return 'Hello, ' + name; }, apply: function(target, thisBinding, args) { return args[0]; }, construct: function(target, args) { return {value: args[1]}; } }; var fproxy = new Proxy(function(x, y) { return x + y; }, handler); fproxy(1, 2) // 1 new fproxy(1,2) // {value: 2} fproxy.prototype === Object.prototype // true fproxy.foo // "Hello, foo"
对于能够设置,但没有设置拦截的操做,则直接落在目标对象上,按照原先的方式产生结果。
get()
拦截对象属性的读取
set()
拦截对象属性的设置
has(target, propKey)
拦截propKey in proxy
的操做,以及对象的hasOwnProperty();
返回值:布尔值。
判断对象是否具备某个属性时,方法才会生效。典型的操做就是in运算符。
使用has方法隐藏某些属性,不被in运算符发现
let headler = { has ( target, key ) { if ( key[0] === '_' ) { return false; } return key in target; } } let target = { _prop: 'aaa', prop: 'bbb' } let proxy = new Proxy(target, headler); console.log( '_prop' in proxy ); // false
deleteProperty(target, propKey)
拦截delete proxy[propKey]
的操做。
返回值:布尔值。 表示是否删除成功。
apply(target, object, args)
参数args:目标对象的参数数组。
拦截Proxy实例做为函数调用的操做,好比proxy(...ars)
,proxy.call(object, ...args)
, proxy,apply(...)
let target = function () { return 'target'; } let handler = { apply: function () { console.log('apply'); return 'handler'; } } let p1 = new Proxy(target, handler); console.log(p1()); // hanlder
construct(target, args)
拦截做为构造函数调用的操做。 好比: new Proxy(...args)
拦截函数和PHP中的魔术方法有相同的原理,当必定条件下,会触发该声明的函数。
Reflect对象与Proxy对象同样
为了操做对象而提供的新API
设计目的:
ES5的默认参数作法
若是没有if判断,参数是否被赋值,若是没有,再等于默认值
缘由:若是参数赋值了,可是对应值是false,则该赋值就不该该起做用。 例如:调用的时候log('hello', ''); 若是参数是空字符串,就不该该被改成默认值。
function log ( x, y ) { if ( typeof y === undefined ) { y = y || 'world'; } console.log(x, y); } log('Hello'); // Hello world log('Hello', 'hi'); // Hello hi log('Hello', ''); // Hello
ES6使用默认参数,直接写在参数定义的后边。
好处:
1:易阅读代码
2:利于将来的代码优化。将来的版本在对外接口中,测底拿掉这个参数,也不会致使之前的代码没法执行。
特色:参数变量时默认声明的,不能用let或const再次声明
function Point ( x, y=10 ) { this.x = x; this.y = y; } let p = new Point(100); console.log(p); // Point {x: 100, y: 10}
参数默认的位置
通常的,定义默认值的参数,应该是函数的尾参数。这样能够比较容易看出来,到底省略了哪些参数。
若是非尾参数设置默认值,这个参数是无法省略。(想使用默认值,设置为undefeind)
function fn ( x = 1, y ) { return [x ,y]; } console.log( fn() ); // [1, undefined] console.log( fn(2) ); // [2, undefined] console.log( fn(undefined, 1) ); // [1, 1] // 若是传入undefeind,将触发该参数等于默认值, console.log( fn(null, 1) ); // [null, 1] // 若是传入null,则会输出。 console.log( fn( , 10) ); // error
function fn2 ( x, y = 1, z ) { return [x, y, z]; } console.log( fn2() ); // [undefined, 1, undefined] console.log( fn2(2) ); // [2, 1, undefiend] console.log( fn2(2, undefined, 3) ); // [2, 1, 3] console.log( fn2(2, , 3) ); // error
函数的length属性
length属性的含义:该函数预期传入的参数个数。
指定了默认值后,length属性将失真。将返回没有指定默认参数的个数。
某个参数指定了默认值之后,预期传入的参数个数就不包括这个参数了。rest参数不会计入length属性个数中。
let len1 = (function (a) {}).length; let len2 = (function (a = 5) {}).length; let len3 = (function ( a, b, c = 1 ) {}).length; let len4 = (fucntion (...args) {}).length; console.log(len1); // 1 console.log(len2); // 0 console.log( len3 ); // 2 console.log( len4 ); // 0
做用域
若是参数默认值是一个变量,则该变量所处的做用域,与其它变量的做用规则是同样的,即先是当前函数的做用域,而后才是全局做用域。
var x = 1; function fn ( x, y = x ) { console.log(y); } fn(2); // 2
若是参数默认值是一个函数
该函数的做用域是其声明时所在的做用域
let foo = 'outer'; function bar ( func = x => foo ) { let foo = 'inner'; console.log( func() ); // outer } bar(); // 函数bar的参数func的默认值是一个匿名函数,返回值为变量foo。这个匿名函数声明时,bar函数的做用域尚未造成,因此匿名函数里面的foo指向外层做用域foo
应用
利用参数默认值,能够指定某一个参数不得省略,若是省略就抛出一个错误。
function throwMsg () { throw new Error('error'); } function foo ( pro1 = throwMsg() ) { return pro1; } foo(); // 抛出错误
能够将参数默认值设置为unfeined,代表这个参数是能够省略的。
function foo ( options = undefined ) { }
语法:...变量名
做用:把逗号隔开的值序列组合成一个数组
获取过来的是数组,将制定的多余变量放入数组中。
使用rest参数就不须要使用arugments对象
function add ( ...nums ) { let sum = 0; for ( let val of nums ) { sum += val; } return sum; } console.log( add(1, 2, 3) ); // 6
注意:rest参数以后不能再有其它参数(即只能是最后一个参数)
扩展运算符...
。
比如rest参数的逆运算。
做用:把数组或类数组对象展开成一系列用逗号隔开的值(将一个数组转为用逗号分隔的参数序列).
console.log(...[1, 2, 3]); // 1 2 3 console.log(1, ...[2, 3, 4], 5 ); // 1 2 3 4 5
通常的,主要用于函数调用:
function add ( x, y ) { return x + y; } let nums = [10, 20]; console.log( add(...nums) ); // 30
扩展运算符与正常的函数参数,rest参数能够配合使用,体现灵活性。
function fn ( x, y, z, n, m, ...nums ) { console.log(...nums); // 5 ,6 } let args = [1, 2]; fn(-1, ...args, 3, ...[4, 5], 6);
扩展运算符的应用
合并数组
let moreArr = [3,4]; let arr1 = [1, 2].concat(moreArr); console.log(arr1); console.log( [1, 2, ...moreArr] ); let arr2 = ['a', 'b']; let arr3 = ['c', 'd']; let arr4 = ['e', 'f']; // ES5的写法 console.log( arr2.concat(arr3, arr4) ); // ES6的写法 console.log( [...arr2, ...arr3, ...arr4] );
与解构赋值结合
扩展运算符与解构赋值结合起来使用,用于生成数组。
注意:扩展运算符只能放在参数的最后一位。(不然将会报错)
const [first, ...rest] = [1, 2, 3, 4, 5]; const [...butLast, last] = [1, 2, 3, 4, 5]; // SyntaxError: Rest element must be last element in array const [first, ...middle, last] = [1, 2, 3, 4, 5]; // SyntaxError: Rest element must be last element in array
函数返回值
JavaScript的函数只能返回一个值,若是须要返回多个值,只能返回数组或对象。扩展运算符提供变通方法。
字符串
扩展运算符能够将字符串转为数组
console.log( [...'hello'] ); // ["h", "e", "l", "l", "o"]
实现了Iterator接口的对象
任何Iterator接口对象,均可以使用扩展运算符转为真正的数组
例如:arguments对象,nodeList对象
let arrList = { '0': 'a', '1': 'b', '2': 'c', length: 3 } console.log( [...Array.from(arrList)] ); // ["a", "b", "c"] console.log( [...arrList] ); // TypeError: arrList[Symbol.iterator] is not a function
函数的name属性,返回该函数的属性名。
属性被浏览器支持,到ES6才写入规范中。
ES5中匿名函数,name属性会返回空字符串。
ES6中匿名函数,name属性会返回实际的函数名
ES5,ES6中,有名的匿名函数,name属性返回具备函数名的名字
Function
构造函数返回函数实例, name
属性值为:anonymouse
let fn1 = function () {}; // ES5 console.log( fn1.name ); // '' // ES6 console.log( fn1.name ); // fn1 const fn = function fn2 () {} // ES5 console.log( fn.name ); // fn2 // ES6 console.log( fn.name ); // fn2 // Function 构造函数 console.log( (new Function).name ); // 'anonymouse';
基本用法
使用 =>
定义函数() => 表达式
参数 => 表达式
var f = v => v; // 等价 var f = function ( v ) { return v; }
若是箭头函数须要多个参数或者不须要参数,可使用()
表明参数。
() => { 函数体 }
var fn = () => 5; // 等同于 var fn = function () { return 5; } var sum = (num1, num2) => num1 + num2; // 等同于 var sum = function ( num1, num2 ) { return num1 + num2 }
箭头函数返回对象,须要在对象外加()
var getTemp = id => ({ id: id, name: 'cyan' });
箭头函数与变量解构赋值,rest参数搭配使用
let fn = (...s) => { console.log(...s); } const full = ( first, last ) => { return first+ ' ' + last; } // 等同于 function full( first, last ) { return first+ ' ' + last; }
箭头函数使用注意点
this固定
this对象在箭头函数中,是固定的。
this的指向固定化,有利于封装回调函数。
this指向固定化,并非由于箭头函数内部有绑定this的机制,实际缘由是箭头函数根本没有本身的this。致使内部的this就是外层代码块的this。正是由于它没有this,因此也就不可以做为构造函数。
箭头函数的this永远指向定义的时候的做用域,在各对象中定义,就指向那个对象。
当使用call()和apply()方法更改做用于时候,箭头函数this是没有更改为功。
function foo () { setTimeout(() => { console.log( 'id:', this.id ); // 42 },100); setTimeout(function () { console.log('id: ' ,this.id); //41 }, 1000); } var id = 21; foo.call({ id: 42 }); // 箭头函数致使this老是只想函数定义生效时所在的对象
对比箭头函数的setTimeout和ES5版本的普通回调函数:
// ES6 function foo () { setTimeout( () => { console.log('id:', this.id); }, 100 ); } foo(); //ES5 function fn2 () { let _this = this; setTimeout(function () { console.log('id:', _this.id); }, 100); } fn2(); // fn2中清楚的说明了,箭头函数里面根本没有本身的this,而是引用外层的this。
function foo() { return () => { console.log(this); // Object {id: 1} return () => { console.log(this); // Object {id: 1} return () => { console.log(this); // Object {id: 1} console.log('id:', this.id); // id: 1 }; }; }; } var f = foo.call( {id: 1} ); f()()();
箭头函数中不存在:this
,argumetns
, super
,new.target
.相应的指向外层函数的对应 this
,argumetns
, super
,new.target
function foo () { console.log('args:', arguments); // args: [1, 2, 3, 4] setTimeout( () => { console.log('args:', arguments); // args: [1, 2, 3, 4] },100 ); } foo(...[1, 2, 3, 4]);
箭头函数嵌套
箭头函数的嵌套:具备管道机制(前一个函数的输出是后一个函数的输入)。
let insert = (value) => ({ into: (array) => ({ after: (afterValue) => { array.splice( array.indexOf(afterValue) + 1, 0, value ); return array; } }) }); console.log( insert(2).into([1, 3]).after(1) ); //[1, 2, 3]
尾调用
尾调用:指某个函数的最后一步是调用另外一函数。
尾调用是函数式编程的重要概念。
function fn (x) { return g(x); // 函数的最后一步调用函数 g() ,就是指的尾调用 } // 不属于为调用的情形 function f1 ( x ) { let y = g(x); return y; } // 调用函数 g(x) 以后,还有赋值操做。 function f2 (x) { return g(x) + 1; } // 调用以后还有其它操做 function f3 ( x ) { g(x); } // f3 最后是返回 undefined // 尾调用不必定要出如今函数的尾部 function f ( x ) { if ( x > 0 ) { return m(x); } return n(x); }
尾调用优化
尾调用特殊之处:与其它调用不一样的,在于它特殊的调用位置。
函数调用会在内存造成一个“调用记录”,又称“调用帧” (call frame),保存调用位置和内部变量等信息。
若是在函数A的内部调用函数B,那么在A的调用帧上方,还会造成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。若是函数B内部还调用函数C,那就还有一个C的调用帧,以此推类。全部的调用帧,就造成一个“调用栈”(call stack)
尾调用因为是函数的最后一步操做,因此不须要保留外层函数的调用帧,由于调用位置,内部变量等信息都不会再使用到。只要直接用内层函数的调用帧,取代外层函数的调用帧就能够。
尾调用优化:只保留内层函数的调用帧
function fn () { let m = 1; let n = 2; return g(m+n); // 执行到这一步,彻底能够删除fn()的调用帧,只保留 g(3) 的调用帧 } fn(); // 等同于 function fn () { return g(3); } fn(); // 等同于 g(3); // “尾调用优化”: 只保留内层函数的调用帧。
若是全部函数都是尾调用,那么彻底能够作到每次执行时,调用帧只有一项,效果就大大节省内存。
注意:只有再也不用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,不然就没法进行“尾调用优化”
尾递归
函数调用自身,称为递归,若是尾调用自身,就称之为:尾递归。
递归很是消耗内存,由于须要同时保存成千上百个调用帧,很容易发生"栈溢出"错误(stack voerflow).可是对于尾递归来讲,因为只存在一个调用帧,因此不会发生"栈溢出"错误。
// 对比阶乘 使用尾递归 和 不使用尾递归的情形 function factorial ( n ) { if ( n === 1 ) { return 1; } return n * factorial(n-1); } // 尾递归 function factorial2 ( n, total ) { if ( n === 1 ) { return total; } return factorial2( n-1, n * total ); } factorial2( 5, 1 ); // 120
递归函数的改写
尾递归的实现,每每须要改写递归函数,确保最后一步只调用自身。作到这一点的方法,就是把全部用到的内部变量改写成函数的参数。
方法1:是在尾递归函数以外,再提供一个正常形式的函数。
function tailFactorial(n, total) { if (n === 1) return total; return tailFactorial(n - 1, n * total); } function factorial(n) { return tailFactorial(n, 1); } factorial(5) // 120
方法2:函数柯里化
柯里化:将多参数的函数转换成单参数的形式。(函数式编程的重要概念)
方法3:ES6的函数默认值
function factorial ( n, total = 1 ) { if ( n === 1 ) return total; return factorial(n-1, n * total); } console.log( factorial(5) ); // 120;
递归本质上是一种循环操做。纯粹的函数式编程语言没有循环操做命令,全部的循环都是使用递归实现。
对于支持“尾调用优化”的语言,只须要知道循环能够用递归代替。
若是一旦使用递归,就最好使用尾递归。
尾递归只在严格模式下有效果
正常模式下:函数内部有两个变量,能够跟踪函数的调用栈。
func.argumetns
: 返回调用时函数的参数 func.caller
: 返回调用当前函数的那个函数。
尾递归优化生效的时候,函数的调用栈会改写,所以argumetns,caller会失真。严格模式下禁用这两个变量。 因此尾递归模式仅在严格模式下生效。
基本用法
Set对象是ES6中新增的一种表示集合的数据结构。它相似于数组,可是成员的值都是惟一的。没有重复的值。
Set自己是一个构造函数,须要经过new关键字来生成。
Set函数能够接受一个数组(或相似数组的对象)做为参数,用来初始化。
获得的实例化对象是去重复的Set对象。
let s2 = new Set([123, 23, , 345, 345, 23]); console.log(s2); // Set {123, 23, undefined, 345} console.log(...s2); // 123 23 undefined 345 console.log( s2.size ); // 4
Set实例的属性和方法
Set结构的实例属性:
Set.prototype.constructor
:构造函数,默认就是Set函数Set.prototype.size
: 返回Set实例的成员总数
Set实例方法分为:操做方法(用于操做数据) 和遍历方法(用于遍历成员)
操做方法
Array.form()
能够将Set结构转为数组。
var items = new Set([1, 2, 3, 5, 6, 3, 4]); console.log( Array.from(items) ); // [1, 2, 3, 5, 6, 4]
遍历操做
Set结构的遍历顺序是插入顺序。 使用场景好比:使用Set结构保存一个回调函数列表,调用的时候,就可以保证按照添加顺序调用。
因为Set结构没有键名,只有键值(或者说键名和键值是同一个值)。 因此keys() 和values()是相同的。
let set = new Set(['pink', 'cyan', 'tan']); for (let item of set.keys()) { console.log(item); } // pink // cyan // tan for (let item of set.values()) { console.log(item); } // pink // cyan // tan for (let item of set.entries()) { console.log(item); } // ["pink", "pink"] // ["cyan", "cyan"] // ["tan", "tan"]
Set结构的实例默承认遍历,它的默认遍历器生成函数就是它values方法。
console.log( Set.prototype[Symbol.iterator] === Set.prototype.values ); // true
遍历的应用
...
) 内部使用 for -of
循环let a = new Set([1, 2, 3]); let b = new Set([4, 3, 2]); // 并集 let union = new Set([...a, ...b]); // Set {1, 2, 3, 4} // 交集 let intersect = new Set([...a].filter(x => b.has(x))); // set {2, 3} // 差集 let difference = new Set([...a].filter(x => !b.has(x))); // Set {1}
WeakSet结构与Set结构相似,也是不重复的值的集合。
WeakSet是一个构造函数,须要经过new关键字。指定参数初始化。
参数:对象,或者 一个数组或相似数组的参数(具备Iterator接口的对象)
var ws = new WeakSet(); var a = [[1, 2], [3, 4]]; console.log( ws.add(a) ); // WeakSet {[Array[2], Array[2]]} console.log( new WeakSet(a) ); // WeakSet {[1, 2], [3, 4]} new WeakSet([1, 2]); // TypeError: Invalid value used in weak set // 数组的成员成为WeakSet的成员,而不是数组自己,意味着,数组的成员只能是对象。
区别:
1:WeakSet的成员只能是对象,而不能是其它类型的值。(具备Iterator接口的对象,均可以做为WeakSet参数)
2:WeakSet对象中弱引用,即垃圾回收至机制不考虑WeakSet对该对象的引用。也就是说:若是其它对象都再也不引用该对象,那么垃圾回收机制会自动回收该对象所占的内存。(WeakSet存在内存中的新生代中,当Form空间和To空间的角色发生对换察觉没有引用就回收),不考虑对象还存在于WeakSet中。这个特色意味着,没法引用WeakSet成员,所以WeakSet是不可遍历。
var ws = new WeakSet(); console.log( ws.add(window) ); // window对象 console.log( ws.add({a: 10}) ); // Object {a: 10}} // console.log( ws.add(Symbol()) ); // TypeError: Invalid value used in weak set // console.log( ws.add(1) ); // TypeError: Invalid value used in weak set // console.log( ws.add('abc') ); // TypeError: Invalid value used in weak set
WeakSet结构方法
var ws = new WeakSet();
ws.add(window);
console.log( ws.has(window) ); // true
WeakSet结构的特色:
不能遍历,缘由:成员都是弱引用,随时均可能消失。遍历机制没法保证成员的存在,极可能刚刚遍历结构,成员就取不到了。
典型的应用:
存储DOM节点,不用可是这些节点充文档移除时,会引起内存泄漏。
Map结构的目的和基本用法
JavaScript的对象,本质上是键值对的集合(Hash结构).可是传统上只能用字符串看成键
Map数据结构,相似对象,也是键值对的结合。可是“键”的范围不只限于字符串。各类类型的值(包括对象)均可以看成键。
Object结构提供了一种“字符串-值”的对应。Map提供了一种“值-值”的对应。是一种更完善的Hash结构实现。
var m = new Map(); var o = {p: 'hello'} m.set(o, 'content'); console.log( m.get(o) ); // content
Map做为构造函数,参数能够是一个数组,该数组的成员是一个个表示键值对的数组。初始化函数。(注意是接受参数,二维数组)
var m = new Map([['name', 'cyan']]); console.log( m.get('name') );
若是对同一个键屡次赋值,后面的值将覆盖前面的值。
若是读取一个未知的键,返回undefined。
let colorsMap = new Map(); colorsMap.set(1, 'tan') .set(1, 'pink'); console.log( colorsMap.get(1) ); // pink new Map().get('asfddfsasadf'); // undefined
注意:只有同一个对象的引用 ,才是 同一个键。
let m = new Map(); m.set(['a'], 111); console.log( m.get(['a']) ); // undefeinds // 表面是同一个键,但实际上,这是两个值,内存地址是不同的,所以get()方法没法读取该键,返回undefiend
Map的键,其实是根内存地址绑定的,只要内存地址不同,就视为两个键。 解决了:同名属性碰撞(clash) 问题。
若是Map的键是一个简单类型的值(数字,字符串,布尔值), 则只要两个值严格相等。Map将其视为一个键,包括(0,-0) 虽然NaN不严格相等于自身,但Map将其视为同一个键
let map = new Map(); map.set(NaN, 123); map.get(NaN) // 123 map.set(-0, 123); map.get(+0) // 123
实例的属性和操做方法
属性:
size属性
返回Map结构的成员总数
操做方法:
遍历方法:
三个遍历器,一个遍历方法
Map的遍历顺序就是插入顺序。
let m = new Map([ ['f', false], ['t', true] ]); for ( let item of m.keys() ) { console.log( item ); } // f // t for ( let item of m.values() ) { console.log( item ); } // false // true for ( let item of m.entries() ) { console.log( item ); } // ["f", false] // ["t", true] m.forEach( ( val, idx, map ) => { console.log( val, idx, map ); } );
Map与其它类型转换
Map转为数组
使用:扩展运算符...
let m = new Map([ ['f', false], ['t', true] ]); console.log(...m); // ["f", false] ["t", true]
数组转为Map结构
把数组放入Map构造函数中,就能够转为Map结构。
let m = new Map([ ['f', false], ['t', true] ]);
Map结构转为对象
前提:全部Map结构中的键都是字符串,它能够转为对象
function strMapToObj ( strMap ) { let obj = Object.create(null); for ( let [k, v] of strMap ) { obj[k] = v; } return obj; } let myMap = new Map().set('yes', true).set('no', false); console.log(strMapToObj(myMap) ); // Object {yes: true, no: false}
对象转为Map结构
function objtoStrMap ( obj ) { let strMap = new Map(); for ( let k of Object.keys(obj) ) { strMap.set(k, obj[k]); } return strMap; } console.log( objtoStrMap({yes: true, no: false}) ); // Map {"yes" => true, "no" => false}
Map结构转为JSON
Map转为JSON分为两种:
Map的键名都是字符串,能够选择JSON.stringify();
Map的键名有非字符串,能够选择转为数组JSON
// Map 转为对象 function strMapToObj ( strMap ) { let obj = Object.create(null); for ( let [k, v] of strMap ) { obj[k] = v; } return obj; } // Map 转为JSON function strMapToJson ( strMap ) { return JSON.stringify( strMapToObj(strMap) ); } let myMap = new Map().set('yes', true).set('no', false); console.log( strMapToJson(myMap) ); // {"yes":true,"no":false} function mapToArrayJson ( map ) { return JSON.stringify([...map]); } let myMap2 = new Map().set('a', 'tan').set('b', 'pink').set('c', 'tan'); console.log( mapToArrayJson(myMap2) ); // [["a","tan"],["b","pink"],["c","tan"]] // 转为二维数组
JSON转为Map
JSON转为Map,正常状况下,全部键名都是字符串。
function objToStrMap ( obj ) { let strMap = new Map(); for ( let k of Object.keys(obj) ) { strMap.set(k, obj[k]); } return strMap; } function jsonToStrMap ( jsonStr ) { return objToStrMap(JSON.parse(jsonStr)); } let json = '{"a": "tan", "b": "pink", "c": "cyan"}'; console.log( jsonToStrMap(json) ); // Map {"a" => "tan", "b" => "pink", "c" => "cyan"}
WakMap结构与Map结构基本相似。
区别:只接受对象做为键名(null除外),不接收其它类型的值做为键名,并且键名所指向的对象,不计入垃圾回收机制。
WeakMap的设计目的在于,键名是对象的弱引用(垃圾回收机制不应将引用考虑在内),因此其对应的对象可能会被自动回收。当对象被回收后,WeakMap自动移除对应的键值对。
典型的: 一个对应的DOM元素的WeakMap结构,DOM节点做为键名。当某个DOM元素被清除,其所对应的WeakMap记录就会被自动被移出。基本上,WeakMap的使用场合,它的键所对应的对象,可能会在未来消失。WeakMap结构有助于防止内存泄漏
let wp = new WeakMap(); wp.set(document.body, 'body'); console.log( wp ); // WeakMap {body {} => "body"}
WeakMap的方法:
JavaScript原有的表示“集合”的数据结构,主要是数组和对象。ES6添加了Map,和Set。
需求:一种统一的接口机制,来处理全部不一样的数据结构。
遍历器(Iterator),是一种接口,为个各类不一样的数据结构提供统一的访问机制。
任何数据结构只要部署了Iterator接口,就能够完成遍历操做。(即依次处理该数据接口的全部成员)
Iterator的做用:
for-of
循环,Iterator接口主要供for-of
消费Iterator遍历过程:
每一次调用next()方法,都会返回数据结构的当前成员的信息。
具体:返回一个包含value
和done
两个属性的对象。value
: 当前成员的值done
: 属性是一个布尔值,表示遍历是否结束。
let it = makeIterator(['a', 'b']); it.next(); it.next(); it.next(); function makeIterator ( arr ) { var nextIndex = 0; return { next: function () { return nextIndex < array.length ? { value: array[nextIdex++] } : { done: true } } } } // 定义一个函数,做用:遍历器对象。对数组['a', 'b'] 执行这个函数,就会返回该数组的遍历器对象(指针对象) it。
调用的指针对象的next方法,就能够遍历事先给定的数据结构。
因为Iterator只是把接口规格加到数据结构之上,遍历器与它所遍历的那个数据结构,其实是分开的,彻底能够写出没有对应数据结构的遍历器对象,或者说用遍历器对象模拟出数据结构。
在ES6中,有些数据结构原生具有Iterator接口(好比数组),即不用任何处理,就能够被for-of循环遍历。有些数据结构就不能够直接被遍历(好比对象).
缘由:数据结构原生部署了Symbol.iterator属性。凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口,调用这个接口,就会返回一个遍历器对象。
Iterator接口的目的:为全部的数据结构,提供一种统一的访问机制。
当使用for-of循环遍历某种数据结构时,该循环会自动寻找Iterator接口。
ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具备Symbol.iterator属性,就能够认为是“可遍历的”
调用Symbol.iterator()方法,就会获得当前数据结构的默认的遍历器生成函数。Symbol.iterator自己是一个表达式,返回Symbl对象的iterator属性。 这是一个预约义好的,类型为Symbol的特殊值,因此要放在方括号内。
ES6中三类数据结构原生具有Iterator接口:
其它数据(主要是Object)的Iterator接口,须要本身的Symbol.iterator属性上面部署。
对象之因此没有默认部署Iterator接口,缘由:对象的那个属性先遍历,那个属性后遍历是不肯定的。
本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。
对于相似数组的对象(存在数值键名和length属性),部署Iterator接口,就是Symbol.iterator方法直接引用数组的Iterator接口。
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; // 或者 NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
相似对数的对象调用数组Symbl.iterator()
let iterable = { 0: 'a', 1: 'b', 2: 'c', length: 3, [Symbol.iterator]: Array.prototype[Symbol.iterator] } for ( let item of iterable ) { console.log(item); } // a // b // c
默认调用
解构赋值
对数组和Set结构进行解构赋值时,会默认调用Symbol.iterator()方法
let s1 = new Set().add('a').add('b').add('c'); let [x ,y] = s1; let [first, ...rest] = s1; console.log( first, ...rest ); // a b c
扩展运算符
扩展运算符...
会默认调用Iterator
let str = 'hello'; console.log( [...str] ); // ["h", "e", "l", "l", "o"] let arr = ['b', 'c']; console.log( ['a', ...arr, 'd'] ); // ["a", "b", "c", "d"]
yield*
yield*后面跟的是一个可遍历的结构
其它场合
因为数组的遍历会调用遍历器接口,因此任何接受数组做为参数的场合,其实都调用了遍历器接口。
字符串是一个相似数组的对象,也原生具备Iterator接口。
let someString = 'hi'; console.log( typeof someString[Symbol.iterator] ); // function let it = someString[Symbol.iterator](); // 指针对象 console.log( it.next() ); // Object {value: "h", done: false} console.log( it.next() ); // Object {value: "i", done: false} console.log( it.next() ); // Object {value: undefined, done: true}
for-of循环可使用的范围包括数组,Set和Map结构,某些相似数组的对象(好比arguments对象,DOM NodeList对象),Generator对象,以及字符串。
JavaScript原有的for-in循环,只能得到对象的键名,不能直接获取键值,ES6提供的for-of循环,容许遍历得到键值。
var arr = ['a', 'b', 'c', 'd']; for ( let a in arr ) { console.log(a) // 0 1 2 3 } for (let a of arr) { console.log( a ); // a b c d }
相似数组的对象都具备Iterator对象,经过Array.from();转为数组。
let arrayLike = { length: 2, 0: 'a', 1: 'b' }; // 报错 for (let x of arrayLike) { console.log(x); } // 正确 for (let x of Array.from(arrayLike)) { console.log(x); }
其它遍历语法比较
最原始的使用for循环。
缺点:写法麻烦
提供forEach();
缺点:没法中途跳出forEach循环,break,return,continue不能生效。
for-in循环
缺点:
for-in循环主要是遍历对象,不适用遍历数组。
for-of循环:
优势:
Promise是一种形式
Promise: 先知,(预先将你的将来告知,规划好你继续的路)
将异步操做转换成更符合先知视角的形式展示
所谓Promise
,简单说就是一个容器,里面保存着某个将来才会结束的事件(一般是一个异步操做)的结果。
语法上:Promise
是一个对象,从它能够获取异步操做的消息。Promise提供统一的API,各类异步操做均可以用一样的方法进行处理。
Promise
对象特色:
Promise
对象表明一个异步操做,有三种状态。Pending(进行中)
,resolved(已完成,又称Fulfilled)
和rejected(已失败)
。
只有异步操做的结果,能够决定当前是哪种状态,任何其它操做都没法改变这个状态。表示无其它手段改变。
Promise
对象的状态改变。只有两种可能:从Pending
变为resolved
和 **从Pending
变为rejected
** 任何一种均可以让状态凝固,就不会再继续变化。
Promise
缺点:
Promise
,一旦新建它就会当即执行,没法中途取消Promise
内部抛出错误,不会反应到外部。Pending
状态时,没法得知目前进展到哪个阶段(刚刚开始仍是即将完成)var promise = new Promise(function ( resolve, reject ) { // some code if ( /* 异步操做成功 */ ) { resolve(); } else { reject(); } });