作用域 作用域主要分为以下几种类型:
全局作用域(global / window);
函数作用域(function);
块级作用域({});
动态作用域(this)。
var 在讲解 let、const 之前,先来了解下 var。
var 声明一个变量,并可选地将其初始化为一个值。
var 用以声明变量;
var 声明的变量,不存在块级作用域,在全局范围内都有效;
var 存在变量提升,因此 var 定义的变量可以先使用,后声明;
example one:
1 2 3 4 5 function fn ( ) { var a = 1 console .log(a) } fn()
example two:
1 2 3 4 5 6 7 8 function fn ( ) { if (true ) { console .log(a) } else { var a = 1 } } fn()
上面这段代码打印出 undefined,是因为 var 变量提升,等价于:
1 2 3 4 5 6 7 8 9 function fn ( ) { var a if (true ) { console .log(a) } else { a = 1 } } fn()
example three:
现在有如下代码,如何只暴露 fn 一个全局变量呢?
1 2 3 4 var a = 1 window .fn1 = function ( ) { console .log(a) }
因为 var a = 1 会产生一个全局变量 a。
假如我们把代码包裹在一个函数里,代码如下:
1 2 3 4 5 6 function fn2 ( ) { var a = 1 window .fn1 = function ( ) { console .log(a) } }
如果像上面这样,a 虽然是局部变量了,但是呢这个函数有名字 fn2,也是一个全局变量。
所以需要使用立即执行函数:
1 2 3 4 5 6 (function ( ) { var a = 1 window .fn1 = function ( ) { console .log(a) } }())
但是这样代码太繁琐了,在 ES6 中,可以使用 let 就可以很方便解决此问题!
let
let 语句声明一个块级作用域的本地变量,并且可选的将其初始化为一个值。
let 关键字用来声明变量;
let 关键字声明的变量不能重复声明;
存在块级作用域,只在其声明的块或子块中可用;
不存在变量提升,只可以先声明,后使用;
let 声明的变量存在暂时性死区,只要块级作用域中存在 let,那么它所声明的变量就绑定了这个区域,不再受外部的影响;
example one:
1 2 3 4 5 6 7 8 { let a = 1 console .log(a) { let a = 2 console .log(a) } }
example two:
1 2 3 4 5 6 7 8 { let a = 1 console .log(a) { console .log(a) let a = 2 } }
example three:
1 2 3 4 5 { let a = 1 console .log(a) let a = 2 }
const
const 关键字用来声明常量,常量是块级作用域,很像使用 let 语句定义的变量。常量的值不能通过重新赋值来改变,并且不能重新声明。
const 在声明时必须赋予初始值,一旦声明,其声明的值就不允许改变,更不允许重复声明;
const 用于声明只读的常量;
存在块级作用域,只在其声明的块或子块中可用;
标识符一般为大写;
不存在变量提升,只可以先声明,后使用;
const 声明的变量存在暂时性死区,只要块级作用域中存在 const,那么它所声明的变量就绑定了这个区域,不再受外部的影响;
example one:
1 2 3 4 5 { const a = 1 console .log(a) a = 2 }
example two:
1 2 3 4 5 6 7 8 9 10 const arr = [1 , 2 , 3 ]arr[0 ] = 2 console .log(arr) const obj = { name: 'zww' , age: 18 } obj.name = 'lq' console .log(obj)
注意:对于数组和对象的元素修改,不算做对常量的修改,因此不会报错。
相关题目 Topic One:
下面代码将打印什么?
1 2 for (var i = 0 ; i < 5 ; i++) {}console .log(i)
查看答案
1 2 3 4 var ifor (i = 0 ; i < 5 ; i++) {}console .log(i)
Topic Two:
下面代码将打印什么?
1 2 3 4 5 6 7 for (var i = 0 ; i < 5 ; i++) { function fn ( ) { console .log(i) } button.onclick = fn } console .log(i)
Topic Three:
点击第三个 li 将打印什么,如何解决此问题?
1 2 3 4 5 6 var liTags = document .querySelectorAll('li' ) for (var i = 0 ; i < liTags.length; i++) { liTags[i].onclick = function ( ) { console .log(i) } }
查看答案
不管点击哪个 li 都将打印出 6!
方法一:
1 2 3 4 5 6 7 var liTags = document .querySelectorAll('li' )for (var i = 0 ; i < liTags.length; i++) { let j = i liTags[j].onclick = function ( ) { console .log(j) } }
方法二:
1 2 3 4 5 6 7 8 var liTags = document .querySelectorAll('li' )for (var i = 0 ; i < liTags.length; i++) { (function (j ) { liTags[j].onclick = function ( ) { console .log(j) } })(i) }
方法三:
1 2 3 4 5 6 var liTags = document .querySelectorAll('li' )for (let i = 0 ; i < liTags.length; i++) { liTags[i].onclick = function ( ) { console .log(i) } }
参考资料 阮一峰 - let和const命令
MDN - var
MDN - let
MDN - const
知乎 - 我用了两个月的时间才理解let
变量的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值。
数组的解构赋值 1 2 3 let name = ['zww' , 'lq' , 'lqzww' ]let [a, b, c, d] = nameconsole .log(a, b, c, d);
对象的解构赋值 1 2 3 4 5 6 7 8 9 10 let info = { name: 'zww' , age: 18 , like: function ( ) { console .log("game" ); } } let { name, age, like } = infoconsole .log(name, age, like); like()
字符串的解构赋值 字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
1 2 3 4 5 6 let str = 'hello' let [a, b, c, d, e, f] = strconsole .log(a, b, c, d, e, f) let { length : len } = strconsole .log(len)
字符串的扩展 模板字符串
模板字符串是字符串的增强版写法,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
模板字符串中可以出现换行符;
可以使用 ${xxx} 的形式嵌入变量;
模板字符串中还能调用函数;
1 2 3 4 5 6 7 8 9 10 11 12 13 let str = `我也是字符串` console .log(str, typeof str); let name = `<ul><li>a</li><li>b</li></ul>` let like = 'game' let out = `${like} 喜欢` console .log(out); function fn ( ) { console .log("fn" ) } `${fn()} `
trimStart()、trimEnd() 它们的行为与 trim() 一致,trimStart() 消除字符串头部的空格 ,trimEnd() 消除尾部的空格 。它们返回的都是新字符串,不会 修改原始字符串。
除了空格键,这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效。
浏览器还部署了额外的两个方法,trimLeft() 是 trimStart() 的别名,trimRight() 是 trimEnd() 的别名。
1 2 3 4 5 let str = ' hello world ' console .log(str) console .log(str.trimStart()) console .log(str.trimEnd())
数组的扩展 扩展运算符
扩展运算符(spread)是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列,对数组进行解包。
1 2 3 4 5 6 7 let name = ['zww' , 'lq' , 'lqzww' ]function fn ( ) { console .log(arguments ) console .log(...arguments) } fn(name)
1 2 3 4 5 let name = ['zww' , 'lq' , 'lqzww' ]let age = [18 , 20 ]var info = [...name, ...age]console .log(info)
1 2 3 4 let name = ['zww' , 'lq' , 'lqzww' ]let copyName = [...name]console .log(copyName)
1 2 3 4 5 6 7 8 <div></div> <div></div> var div = document .querySelectorAll("div" )var arrDiv = [...div]console .log(div) console .log(arrDiv)
Array.from() Array.from() 方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。
1 2 3 4 5 6 7 let arr = { '0' : 'a' , '1' : 'b' , '2' : 'c' , length: 3 }; console .log(Array .from(arr))
1 2 3 4 5 6 7 8 9 10 11 12 let arr = { '0' : 'a' , '1' : 'b' , '2' : 'c' , length: 3 }; let array = Array .from(arr, function (a, b ) { console .log(a, b) })
Array.of() Array.of() 方法用于将一组值,转换为数组。
Array.of() 总是返回参数值组成的数组。如果没有参数,就返回一个空数组。
1 2 console .log(Array .of(1 , 2 , 3 )) console .log(Array .of())
includes() 该方法表示某个数组是否包含给定的值,返回一个布尔值。
该方法的第二个参数表示搜索的起始位置,默认为 0。如果第二个参数为负数,则表示倒数的位置。
1 2 3 4 5 6 let arr = ['天龙八部' , '英雄联盟' , '王者荣耀' , '部落冲突' ]console .log(arr.includes('天龙八部' )) console .log(arr.includes('皇室战争' )) console .log(arr.includes('天龙八部' , 1 ))
flat()、flatMap() flat() flat() 用于将嵌套的数组“拉平 ”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
flat() 默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将 flat() 方法的参数写成一个整数,表示想要拉平的层数,默认为 1。如果参数为 Infinity,表示可展开任意深度的嵌套数组。
1 2 3 4 5 let arr = [1 , 2 , [3 , 4 , [5 , 6 , [7 ]]]]console .log(arr.flat()) console .log(arr.flat(2 )) console .log(arr.flat(Infinity ))
flatMap() flatMap() 方法对原数组的每个成员执行一个函数(相当于执行 Array.prototype.map()),然后对返回值组成的数组执行 flat() 方法。该方法返回一个新数组,不改变原数组。
flatMap() 方法的参数是一个遍历函数,该函数可以接受三个参数 ,分别是当前数组成员、当前数组成员的位置(从零开始)、原数组。
1 2 3 4 5 6 let arr = [1 , 2 , 3 ]let res1 = arr.map(item => [item * 2 ])console .log(res1) let res2 = arr.flatMap(item => [item * 2 ])console .log(res2)
函数的扩展 函数参数默认值
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
1 2 3 4 5 function add (a, b ) { return a + b; } console .log(add(1 , 2 )); console .log(add(1 ));
1 2 3 4 5 function add (a, b = 10 ) { return a + b; } console .log(add(1 , 2 )); console .log(add(1 ));
rest参数
ES6 引入 rest 参数(形式为…变量名),用于获取函数的多余参数,用来代替 arguments,rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
1 2 3 4 function fn (...args ) { console .log(args) } fn(1 , 2 )
1 2 3 4 function fn (a, b, ...args ) { console .log(args) } fn(1 , 2 , 3 , 4 , 5 )
注意:rest 参数必须要放到参数最后。
箭头函数
ES6 允许使用“箭头”(=>)定义函数。
1 2 3 let fn1 = function ( ) {}let fn2 = () => {}
1 2 3 4 5 6 var f = x => n * nvar f = (x, y ) => x + yvar f = (x, y ) => { return x + y }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let fn1 = function ( ) { console .log(this .name); } let fn2 = () => { console .log(this .name); } var name = 'zww' ;const home = { name: "home" } fn1() fn2() fn1.call(home) fn2.call(home)
1 2 3 4 5 6 let Person = name => { this .name = name; } let person = new Person('zww' );console .log(person);
1 2 3 4 5 let fn = () => { console .log(arguments ); } fn(1 , 2 );
总结:
如果形参只有一个,那么小括号可以省略;
函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果;
箭头函数 this 指向声明时所在作用域下 this 的值;
箭头函数不能作为构造函数实例化,也就是说,不可以使用 new 命令,否则会抛出一个错误。
不能使用 arguments,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
数值的扩展 二进制和八进制表示法 ES6 提供了二进制和八进制数值的新的写法,分别用前缀 0b(或0B)和 0o(或0O)表示。
如果要将 0b 和 0o 前缀的字符串数值转为十进制,要使用 Number 方法。
1 2 3 4 5 6 7 8 9 10 11 let b = 0b111 ;console .log(b) let o = 0o234 ;console .log(o) let x = 0xabc ;console .log(x) console .log(Number ('0b111' )) console .log(Number ('0o10' ))
Number.EPSILON ES6 在 Number 对象上面,新增一个极小的常量 Number.EPSILON。它表示 1 与大于 1 的最小浮点数之间的差。
Number.EPSILON 实际上是 JavaScript 能够表示的最小精度 。误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了。
Number.EPSILON 的实质是一个可以接受的最小误差范围。
1 2 3 4 5 6 7 8 9 function equal (a, b ) { if (Math .abs(a - b) < Number .EPSILON) { return true ; } else { return false ; } } console .log(0.1 + 0.2 === 0.3 ) console .log(equal(0.1 + 0.2 , 0.3 ))
Number.isFinite() 与 Number.isNaN() Number.isFinite() 用来检查一个数值是否为有限的(finite),即不是 Infinity。如果参数类型不是数值,Number.isFinite 一律返回 false。
Number.isNaN() 用来检查一个值是否为 NaN。如果参数类型不是 NaN,Number.isNaN 一律返回 false。
1 2 3 4 5 6 7 8 9 10 console .log(Number .isFinite(1 )) console .log(Number .isFinite(1 / 0 )) console .log(Number .isFinite(Infinity )) console .log(Number .isFinite(-Infinity )) console .log(Number .isNaN(1 )) console .log(Number .isNaN(NaN )) console .log(Number .isNaN(NaN / 0 )) console .log(Number .isNaN('true' / 0 )) console .log(Number .isNaN('true' / 'true' ))
Number.parseInt() 与 Number.parseFloat() ES6 将全局方法 parseInt() 和 parseFloat(),移植到 Number 对象上面,行为完全保持不变。
1 2 console .log(Number .parseInt('1.34abc' )) console .log(Number .parseFloat('1.34abc' ))
Number.isInteger() Number.isInteger() 用来判断一个数值是否为整数。
1 2 console .log(Number .isInteger(123 )) console .log(Number .isInteger(1.34 ))
Math.trunc() Math.trunc 方法用于去除一个数的小数部分,返回整数部分。
1 2 console .log(Math .trunc(123 )) console .log(Math .trunc(1.34 ))
Math.sign() Math.sign 方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
它有如下五种返回值:
参数为正数,返回 +1;
参数为负数,返回 -1;
参数为 0,返回 0;
参数为 -0,返回 -0;
其他值,返回 NaN。
1 2 3 4 5 console .log(Math .sign(0 )) console .log(Math .sign(-0 )) console .log(Math .sign(1 )) console .log(Math .sign(-1 )) console .log(Math .sign(NaN ))
指数运算符 指数运算符(**)用来实现幂运算,功能与 Math.pow 结果相同。
1 2 3 4 5 console .log(Math .pow(2 , 10 )) console .log(2 ** 10 ) console .log(2 ** 3 ** 2 )
注意:指数运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。
对象的扩展 简化对象的写法
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
1 2 3 4 5 6 7 8 9 10 11 12 let name = 'zww' let change = function ( ) { console .log('change' ); } let info = { name, change, like ( ) { console .log('game' ); } } console .log(info);
Object.is() Object.is() 它是用来比较两个值是否严格相等,返回 true / false,与严格比较运算符(===)的行为基本一致。
Object.is() 方法如果满足以下条件则两个值相等:
都是 undefined;
都是 null;
都是 true 或 false;
都是相同长度的字符串且相同字符按相同顺序排列;
都是相同对象(意味着每个对象有同一个引用);
都是数字且都是 +0;都是 -0;都是 NaN;或都是非零而且非 NaN 且为同一个值;
1 2 3 4 5 6 7 8 9 10 11 12 Object .is('hello' ,'hello' ) Object .is('hello' ,'hi' ) Object .is([],[]) Object .is(null ,null ) Object .is(null ,undefined ) Object .is(0 ,+0 ) Object .is(0 ,-0 ) Object .is(-0 ,+0 ) Object .is(NaN ,0 /0 )
Object.assign() Object.assign() 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
Object.assign() 方法的第一个参数是目标对象,后面的参数都是源对象。
1 2 3 4 5 6 7 8 9 10 11 let obj1 = { name: 'zww' , age: 18 , like: 'game' } let obj2 = { name: 'lq' , age: 111 , size: 22 } console .log(Object .assign(obj1, obj2))
注意:
如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性;
如果只有一个参数,Object.assign() 会直接返回该参数;
如果该参数不是对象,则会先转成对象,然后返回;
由于 undefined 和 null 无法转成对象,所以如果它们作为参数,就会报错;
Object.assign() 方法实行的是浅拷贝,而不是深拷贝,也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用;
该方法有如下常见用途:
为对象添加属性;
为对象添加方法;
克隆对象;
合并多个对象;
为属性指定默认值;
Object.setPrototypeOf()、Object.getPrototypeOf() Object.setPrototypeOf():用来设置一个对象的原型对象(prototype),返回参数对象本身。
Object.getPrototypeOf():用于读取一个对象的原型对象。
1 2 3 4 5 6 7 8 let a = { name: 'zww' } let b = { age: 18 } console .log(Object .setPrototypeOf(a, b)) console .log(Object .getPrototypeOf(a))
Object.keys()、Object.values()、Object.entries() Object.keys() ES5 引入了 Object.keys 方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
ES2017 引入了跟 Object.keys 配套的 Object.values 和 Object.entries,作为遍历一个对象的补充手段,供 for...of 循环使用。
1 2 3 4 5 var obj = { name: 'zww' , age: 22 }; console .log(Object .keys(obj))
Object.values() Object.values 方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值,它只返回对象自身的可遍历属性。
1 2 3 4 5 var obj = { name: 'zww' , age: 22 }; console .log(Object .values(obj))
Object.entries() Object.entries() 方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
1 2 3 4 5 var obj = { name: 'zww' , age: 22 }; console .log(Object .entries(obj))
Object.entries 方法的另一个用处是,将对象转为真正的 Map 结构。
Object.getOwnPropertyDescriptors() Object.getOwnPropertyDescriptors() 方法,返回指定对象所有自身属性(非继承属性)的描述对象。
1 2 3 4 5 6 var obj = { name: 'zww' , age: 22 } console .log(Object .getOwnPropertyDescriptors(obj))
Object.fromEntries() Object.fromEntries() 方法是 Object.entries() 的逆操作,用于将一个键值对数组转为对象。
该方法的主要目的,是将键值对的数据结构还原为对象,因此特别适合将 Map 结构转为对象。
1 2 3 4 5 6 7 8 9 10 let arr = Object .fromEntries([ ['name' , 'zww' ], ['age' , 18 ] ]) console .log(arr) let m = new Map ()m.set('name' , 'lq' ) let res = Object .fromEntries(m)console .log(res)
Symbol
ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
1 2 3 4 5 6 7 8 let s = Symbol ()console .log(s, typeof s) let s1 = Symbol ('zww' )let s2 = Symbol ('zww' )console .log(s1 == s2) console .log(s1 === s2)
1 2 3 4 5 6 7 8 let s = Symbol .for('1' )console .log(s, typeof s) let s1 = Symbol .for('zww' )let s2 = Symbol .for('zww' )console .log(s1 == s2) console .log(s1 === s2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 let info = { name: 'zww' , like: function ( ) {} } let myInfo = { like: Symbol () } info[myInfo.like] = function ( ) { console .log('hh' ) } console .log(info) let game = { name: 'tlbb' , [Symbol ('ts' )]: function ( ) {}, [Symbol ('em' )]: function ( ) {} } console .log(game)
注意:
Symbol 的值是唯一的,用来解决命名冲突的问题
Symbol 值不能与其他数据进行运算
Symbol 定义的对象属性不能使用 for…in 循环遍历,但是可以使用 Reflect.ownKeys 来获取对象的所有键名
阮一峰 - Symbol
迭代器 遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 消费。
原生具备 Iterator 接口的数据(可用 for of 遍历)的有:Array、Arguments、Set、Map、String、TypedArray、NodeList。
1 2 3 4 5 let arr = [1 , 2 , 3 ]for (let i of arr) { console .log(i) }
Iterator 的工作原理如下:
创建一个指针对象,指向当前数据结构的起始位置;
第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员;
接下来不断的调用 next 方法,指针一直往后移动,直到指向最后一个成员;
每调用 next 方法返回一个包含 value 和 done 属性的对象;
1 2 3 4 5 6 7 8 let arr = [1 , 2 , 3 ]let iterator = arr[Symbol .iterator]();console .log(iterator) console .log(iterator.next()); console .log(iterator.next()); console .log(iterator.next()); console .log(iterator.next());
使用迭代器可以自定义遍历数据,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const info = { name: "zww" , like: ['tlbb' , 'xylm' , 'wzry' ], [Symbol .iterator]() { let index = 0 ; let that = this ; return { next: function ( ) { if (index < that.like.length) { const result = { value: that.like[index], done: false }; index++; return result; } else { return { value: undefined , done: true } } } } } } for (let i of info) { console .log(i); }
阮一峰 - Iterator 和 for…of 循环
生成器 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
1 2 3 4 5 6 7 8 9 function * gen ( ) { yield 'hi' yield 'generator' } let iterator = gen()console .log(iterator) console .log(iterator.next()) console .log(iterator.next()) console .log(iterator.next())
生成器函数可以传入参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function * gen (arg ) { console .log(arg) let one = yield 'one' console .log(one) let two = yield 'two' console .log(two) } let iterator = gen('A' )console .log(iterator.next())console .log(iterator.next('B' ))console .log(iterator.next('CCC' ))
使用生成器函数可以避免回调地狱:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function one ( ) { setTimeout (() => { let data = 'tlbb' iterator.next(data); }, 1000 ) } function two ( ) { setTimeout (() => { let data = 'yxlm' iterator.next(data); }, 2000 ) } function * gen ( ) { let t = yield one(); console .log(t) let y = yield two(); console .log(y) } let iterator = gen();iterator.next();
注意:
* 的位置没有限制,但必须在 function 与函数名之间;
生成器函数返回的结果是迭代器对象,调用迭代器对象的 next 方法可以得到 yield 语句后的值;
yield 相当于函数的暂停标记,也可认为是函数的分隔符,每调用一次 next 方法,执行一段代码;
next 方法可以传递实参,作为 yield 语句的返回值;
阮一峰 - Generator 函数的语法
Promise Promise从入门到放弃
async、await Promise从入门到放弃
Set ES6 提供了新的数据结构 Set(集合)。它类似于数组,但是成员的值都是唯一的,没有重复的值(自带去重)。它实现了 iterator 接口,所以可以使用扩展运算符、for...of。
Set 本身是一个构造函数,用来生成 Set 数据结构。
1 2 let s = new Set ()console .log(s, typeof s)
Set 结构的实例具有以下几个属性:
Set.prototype.constructor:构造函数,默认就是 Set 函数;
Set.prototype.size:返回 Set 实例的成员总数;
Set 实例的方法分为两大类,分别为操作方法 和遍历方法 。
操作方法如下:
.add(value):用于添加某个值,返回 Set 结构本身;
.delete(value):用于删除某个值,返回的是布尔值,表示是否删除成功;
.has(value):用于检测该值是否为 Set 成员,返回一个布尔值;
.clear():用于清除 Set 所有成员,没有返回值;
1 2 3 4 5 6 7 8 9 10 11 12 13 let s = new Set ([1 , 2 , 3 , 2 , 1 ])console .log(s) console .log(s.size) console .log(s.add(4 )) console .log(s.delete(1 )) console .log(s) console .log(s.has(2 )) s.clear() console .log(s)
遍历方法如下:
.keys():返回键名的遍历器;
.values():返回键值的遍历器;
.entries():返回键值对的遍历器;
.forEach():使用回调函数遍历每个成员;
注意:Set 的遍历顺序就是插入顺序。
阮一峰 - ECMAScript6入门 - Set
Map ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。它也实现了 iterator 接口,所以可以使用扩展运算符、for...of。
1 2 let m = new Map ()console .log(m, typeof m)
Map 结构的实例具有以下属性和操作方法:
.size:返回 Map 结构的成员总数;
.set(key, value):设置键名 key 对应的键值为 value,返回整个 Map 结构。如果 key 已经有值,则键值会被更新,否则就新生成该键。set 方法返回的是当前的 Map 对象,因此可以采用链式写法;
.get(key):读取 key 对应的键值,如果找不到 key,返回 undefined;
.has(key):表示某个键是否在当前 Map 对象之中,返回一个布尔值;
.delete(key):删除某个键,返回 true。如果删除失败,返回 false;
.clear():清除所有成员,没有返回值;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let m = new Map ()m.set("name" , "zww" ) m.set("like" , function ( ) { console .log("game" ) }) console .log(m) console .log(m.size) console .log(m.get("name" )) console .log(m.get("age" )) console .log(m.has("name" )) console .log(m.delete("like" )) console .log(m) m.clear() console .log(m)
阮一峰 - ECMAScript6入门 - Map
class 基本使用 在没有 ES6 class 之前的常规写法:
1 2 3 4 5 6 7 8 9 10 11 function Person (name, age ) { this .name = name this .age = age } Person.prototype.sayHi = function (sing ) { console .log(this .name + "唱了" + sing) } var zname = new Person('zww' , 22 )console .log(zname) zname.sayHi('啊哈哈' )
在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实例化对象。
类抽象了对象的公共部分,它泛指某一大类。
对象特指某一个,通过类实例化一个具体的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Star { constructor (name, age ) { this .name = name this .age = age } sayHi (sing ) { console .log(this .name + "唱了" + sing); } } let zname = new Star("zww" , 11 )console .log(zname) zname.sayHi("我爱你" ) console .log(zname.hasOwnProperty('name' )) console .log(zname.hasOwnProperty('sayHi' )) console .log(zname.__proto__.hasOwnProperty('sayHi' ))
注意:
类必须使用 new 实例化对象;
通过 class 关键字创建类,类名首字母一般大写;
类里面有个 constructor 函数,可以接收传递过来的参数,同时返回实例对象;
类里面所有函数都不需要写 function;
多个函数方法之间不需要用逗号隔开;
set、get 我们先来看看 ES5 中 set、get 的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var info = { _age: 18 , set age (newValue ) { if (newValue > 18 ) { console .log("大于18" ) } else { console .log("小于18" ) } }, get age () { console .log("get age" ) return this ._age } } info.age = 10 info.age = 100 info.age
下面我们再来看看在 ES6 中 class 里使用 set、get:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Info { constructor (age ) { this ._age = age } set age (newValue ) { if (newValue > 18 ) { console .log("大于18" ) } else { console .log("小于18" ) } } get age () { console .log("get age" ) return this ._age } } let info = new Info(18 )info.age = 10 info.age = 100 info.age
静态方法 如果在 class 中定义了一个方法,该方法是可以被实例所调用的,那么如果我们不希望实例继承这个方法,只想这个方法被类本身被调用的时候,就需要将方法标记为静态方法,使用 static 关键字来标识一个静态方法,下面是基本的使用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Point { constructor (x, y ) { this .x = x this .y = y } getPosition ( ) { return `(${this .x} ,${this .y} )` } static getClassName ( ) { return Point.name } } let p = new Point(1 , 2 )console .log(p.getPosition()) console .log(Point.getClassName())
new.target new 是从构造函数生成实例对象的命令。ES6 为 new 命令引入了一个 new.target 属性,该属性一般用在构造函数之中,返回 new 命令作用于的那个构造函数。如果构造函数不是通过 new 命令或 Reflect.construct() 调用的,new.target 会返回 undefined,因此这个属性可以用来确定构造函数是怎么调用的。
在普通函数中使用:
1 2 3 4 5 function Point ( ) { console .log(new .target) } let p1 = new Point() let p2 = Point()
在 class 中使用:
1 2 3 4 5 6 class Point { constructor ( ) { console .log(new .target) } } let p = new Point()
在 class 的继承中使用:
1 2 3 4 5 6 7 8 9 10 11 class Parent { constructor ( ) { console .log(new .target) } } class Child extends Parent { constructor ( ) { super () } } let c = new Child()
类的继承 JavaScript 中的类可以继承某个类,其中被继承的类称为父类,而继承父类的被称为子类。
子类可以有自己的函数和构造器,当子类中存在父类相同的方法时,则该方法不会从父类继承,而使用子类的方法。
我们首先来看看在 ES5 中如何实现继承:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function Father (name, age ) { this .name = name this .age = age } Father.prototype.sayHi = function (sing ) { console .log(this .name + '的年龄是' + this .age + ',并且唱了' + sing) } function Son ( ) {} Son.prototype = new Father('zww' , 18 ) let son = new Son()son.sayHi('哈哈' )
接下来我们再来看看 ES6 中是如何实现继承的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Father { constructor (name, age ) { this .name = name this .age = age } sayHi (sing ) { console .log(this .name + '的年龄是' + this .age + ',并且唱了' + sing) } } class Son extends Father { }var father = new Father('zww' , 18 )console .log(father) var son = new Son('lq' , 22 )console .log(son) son.sayHi('呵呵' ) console .log(son instanceof Son) console .log(son instanceof Father)
Object.getPrototypeOf() Object.getPrototypeOf() 方法可以用来从子类上获取父类。因此,可以使用这个方法判断,一个类是否继承了另一个类。
1 2 3 4 class Father {}class Son extends Father {}console .log(Object .getPrototypeOf(Son) === Father)
## super关键字
super 关键字用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数。
super 这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
首先我们来看看 super 作为函数使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Father { constructor (name, age ) { this .name = name this .age = age } sayHi ( ) { console .log('父类函数' ) } } class Son extends Father { constructor (name, age, sex ) { super (name, age) this .sex = sex } sonfn ( ) { super .sayHi() console .log('子类函数' ) } } var son = new Son('lq' , 22 , '女' )console .log(son) son.sonfn('丫丫' )
接下来看看 super 作为对象使用:
在普通方法中,它指向的是父类的原型对象;
在静态方法中,它指向的是父类;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Father { constructor ( ) { this .type = 'father' } getName ( ) { return this .type } } Father.getType = () => { return 'Father' } class Son extends Father { constructor ( ) { super () console .log('constructor:' + super .getName()) } getFatherName ( ) { console .log('getFatherName:' + super .getName()) } } var son = new Son() son.getFatherName()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Father { constructor ( ) { this .type = 'father' } getName ( ) { return this .type } } Father.getType = () => { return 'is Father' } class Son extends Father { constructor ( ) { super () console .log('constructor:' + super .getName()) } getFatherName ( ) { console .log('getFatherName:' + super .getName()) } static getFatherType ( ) { console .log('getFatherType:' + super .getType()) } } var son = new Son() son.getFatherName() Son.getFatherType()
注意:
在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象;
类里面的共有的属性和方法一定要加 this;
this 的指向问题;constructor 里面的 this 指向的是创建的实例对象;方法里面的 this 指向这个方法的调用者;
子类的 __proto__ 指向父类本身;
子类的 prototype 属性的 __proto__ 指向父类的 prototype 属性;
实例的 __proto__ 属性的 __proto__ 指向父类实例的 __proto__;
原生构造函数的继承 原生构造函数是指语言内置的构造函数,通常用来生成数据结构。原生构造函数大致如下:
Boolean();
Number();
String();
Array();
Date();
Function();
RegExp();
Error();
Object();
1 2 3 4 5 6 7 8 class MyArray extends Array { constructor (...args ) { super (...args) } } let arr = new MyArray(4 , 5 , 6 )console .log(arr)console .log(arr.join('--' ))
模块化 模块功能主要由两个命令构成:export 和 import。export 命令用于规定模块的对外接口,import 命令用于输入其他模块提供的功能。
export 暴露模块的三种方法:
分别暴露;
统一暴露;
默认暴露;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 export var name = 'zww' ;export function like ( ) { console .log("like" ); } var name = 'zww' ;function like ( ) { console .log("like" ); } export { name, like }export default { name: 'zww' , like: function ( ) { console .log("like" ); } }
import 引入模块的三种方式:
通用方式;
解构赋值形式;
简便形式,只适用于默认暴露;
1 2 3 4 5 6 7 8 9 10 import * as m from './module.js' import { name, like } from './module.js' import { name as myname, like } from './module.js' import { default as m } from './module.js' import m from './module.js'
注意:在引入模块时,要在 script 标签写上 type="module"。
参考链接 阮一峰 - ECMAScript6入门
尚硅谷 - ES6教程