let和var
1.作用域不同
var举例:
var a=[];
for(var i =0 ; i<10;i++){
a[i]=function(){
console.log(i);
};
}
a[6]();//10 a[6]=function(){console.log(i);} i在全局范围内有效
let:
var a=[];
for(let i=0;i<10;i++){
a[i]=function(){
console.log(i);
};
}
a[6]();//6
//当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新变量,所以最后输出的是6
let不存在变量提升
//var的情况
console.log(foo);//输出undefined
//脚本开始运行时,变量foo已经存在了,但是没有值,所以会输出undefined
var foo = 2;
//let的情况
console.log(bar);//报错ReferenceError
let bar=2;
如果块级作用域内存在let命令.会绑定这个块级作用域,举例
var tmp =123;
if(true){
tmp ='abc'//ReferenceError 后者绑定这个块级作用域
let tmp;
}
//在代码块内,使用let命令声明变量之前,该变量都是不可用的.这在语法上,称为"暂时性死区"(temporal dead zone,简称 TDZ)
if(true){
//TDZ开始
tmp='abc';//ReferenceError
console.log(tmp);ReferenceError
let tmp;//TDZ结束
console.log(tmp);//undefined
tmp=123;
console.log(tmp);//123
}
ES6一共6种声明变量的方法
var,function,let,const,import,class
-
var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令,const命令,class命令声明的全局变量,不属于顶层对象的属性.也就是说,从ES6开始,全局变量将逐步与顶层对象的属性脱钩.
var a= 1;
//如果在Node的REPL环境,可以写成global.a,或者采用通用方法,写成this.a,window.a //1let b = 1;
window.b //underfined
//全局变量a由var命令声明,所以它是顶层对象的属性;全局变量b由let命令声明,所以它不是顶层对象的属性,返回undefined
global对象的问题
顶层对象,在各种环境里面的实现是不统一的:浏览器里面,顶层对象是window;Node里,顶层对象是global.
this变量的局限性:
-- 全局环境中,this会返回顶层对象.但是node模块和ES6模块,this返回的是当前模块.
-- 函数里面的this,如果函数不是作为对象的方法运行,而是单独作为函数运行,this会指向顶层对象.但是严格模式下.this会返回undefined.
垫片库system.global模拟global作为顶层对象,可以在所有环境拿到global:
//CommonJS写法
require('system.global/shim')();
//ES6模块的写法
import shim from 'system.global/shim';shim();
//这些代码可以保证各种环境里面,global对象都是存在的
//CommonJS 写法
var global = require('system.global')();
//ES6 模块的写法
import getGlobal from 'system.global';
const global = getGlobal();
//这些代码将顶层对象放入变量global。
解构赋值(Destructuring)
数组的解构赋值
本质上,这种写法属于"模式匹配",只要等号两边的模式相同,左边的变量就会被赋予对应的值.
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [x, , y] = [1, 2, 3];
x // 1
y // 3
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []
如果解构不成功,变量的值就等于undefined.
解构赋值允许指定默认值
let [foo =true] = [];
foo //true
let [x,y='b'] = ['a'];//x='a',y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
注意,ES6内部使用严格相等运算符(===),判断一个位置是否有值,所以,如果一个数组成员不严格等于undefined,默认值是不会生效的.
let[x=1]=[undefined];
x //1
let[x=1]=[null];
x //null
对象的解构赋值
let{foo,bar}={foo:"aaa",bar:"bbb"};
foo //"aaa"
bar //"bbb"
本质
let {foo:baz}={foo:"aaa",bar:"bbb"};
baz // "aaa"
foo // error: foo is not defined
//foo是匹配的模式,baz才是变量.真正被赋值的是变量baz,而不是模块foo.
字符串的解构赋值
const [a,b,c,d,e]='hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值.
let{length:len}='hello';
len //5
数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会转为对象.
let{toString:s}=123;
s === Number.prototype.toString //true
let {toString:s}=true;
s === Bollean.prototype.toString //true
//因为数值和布尔值的包装对象都有toString属性,因此变量s都能取到值.
由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错.
let{prop:x}=undefined;//TypeError
let{prop:y}=null;//TypeError
函数参数的解构赋值
function add([x,y]){
return x+y;
}
add([1,2]);//3
解构赋值的用途举例
-
(1)交换变量的值
let x= 1;
let y =2;[x,y]=[y,x];
-
(2)从函数返回多个值
-
(3)函数参数的定义
-
(4)提取json数据
-
(5)设置函数参数的默认值
-
(6)遍历Map结构
-
(7)输入模块的指定方法
函数的扩展
函数参数的默认值
function log(x,y='World'){
console.log(x,y);
}
log('Hello') //Hello World
log('Hello','China')//Hello China
log('Hello','')//Hello
rest参数
ES6引入rest参数(形式为…变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了.rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中.
function add(...values){
let sum = 0;
for (var val of values){
sum+=val;
}
return sum;
}
add(2,5,3)//10
rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错.
函数的length属性不包括rest参数,不包括有默认值的参数,也不包括有默认值参数后面的参数
箭头函数
const full = ({first,last}) => first + '' + last;
const isEven = n => n%2 ===0;
const square = n => n*n;
[1,2,3].map(x=>x*x);
箭头函数使用注意点:
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象.
**this对象的指向是可变的,但是在箭头函数中,它是固定的.**
(2)不可以当做构造函数,也就是说不可以使用new命令,否则会抛出一个错误.
(3)不可以使用arguments对象,该对象在函数体内不存在.如果要用,可以用rest参数代替.
(4)不可以用yield命令,因此箭头函数不能用作Generator函数.
**this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this.正是因为它没有this,所以也就不能用作构造函数.**
双冒号运算符
函数绑定运算符是并排的两个冒号(:😃,双冒号左边是一个对象,右边是一个函数.该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面.
foo:bar;
//等同于
bar.bind(foo);
foo:bar(...arguments);
//等同于
bar.bind(foo,arguments)
数组的扩展
扩展运算符
扩展运算符(spread)是三个点(…)。它好比rest参数的逆运算,将一个数组转为用逗号分隔得参数序列.
扩展运算符的应用
(1)复制数组
const a1=[1,2];
//写法一
const a2=[...a1];
//写法二
const [...a2]=a1;
(2)合并数组
//ES6的合并数组
[...arr1,...arr2,...arr3]
//['a','b','c','d','e']
(3)与解构赋值结合
const[first,...rest]=[1,2,3,4,5];
first //1
rest //[2,3,4,5]
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错.
Array.from()
作用:用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map).
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
//****类似数组的对象:本质特征只有一点,即必须有length属性
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
常见用法:
//NodeList对象
let ps = document.querySelectorAll('p');
Array.from(ps).forEach(function(p){
console.log(p);
})
//arguments对象
function foo(){
var args=Array.from(arguments);
}
只要是部署了Iterator接口的数据结构,Array.from都能将其转为数组.
扩展运算符(…)也可以将某些数据结构转为数组.
//arguments对象
function foo(){
const args=[...arguments];
}
//NodeList对象
[...document.querySelectorAll('div')]
Array.from还可以接受第二个参数,作用于数组的map方法,用于对每个元素进行处理,将处理后的值放入返回的数组.
Array.from(arrayLike, x => x*x);
//等同于
Array.from(aerayLike).map(x => x*x);
Array.from([1,2,3], (x) => x*x);
Array.of()
作用:用于将一组值,转换为数组.
Array.of(3,1,2);//[3,1,2]
Array.of(3);//[3]
Array.of(3).length;//1
数组实例的copyWithin()
作用:在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组.也就是说,使用这个方法,会修改当前数组.
Array.prototype.copyWithin(target,start=0,end=this.length)
-
三个参数:
- target(必需):从该位置开始替换数据.
- start(可选):从该位置开始读取数据,默认为0.如果为负值,表示倒数.
- end(可选):到该位置前停止读取数据,默认等于数组长度.如果为负值,表示倒数.这三个参数都应该为数值,如果不是,会自动转为数值.
[1,2,3,4,5].copyWithin(0,3)
//[4,5,3,4,5]
数组实例的find()和findIndex()
作用:数组实例的find方法,用于找出第一个符合条件的数组成员.它的参数是一个回调函数,所以有数组成员依次执行该回调函数,直到找出第一个返回true的成员,然后返回该成员.如果没有符合条件的成员,则返回undefined.
[1,4,-5,10].find((n) => n<0)
// -5
find方法的回调函数可以接受三个参数,依次为当前的值,当前的位置和原数组.
[1,5,10,15].find(function(value,index,arr){
return value > 9;
}) //10
findIndex方法和find方法的用法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1.
[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;
}) // 2
另外,这两个方法都可以发现NaN,弥补了数组的indexOf方法的不足.
[NaN].indexOf(NaN)
// -1
[NaN].findIndex(y => Object.is(NaN, y))
// 0
// findIndex方法可以借助Object.is方法做到。
数组实例的fill()
作用:fill方法使用给定值,填充一个数组.
['a','b','c'].fill(7);
// [7,7,7]
new Array(3).fill(7)
// [7,7,7]
fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置.
['a','b','c'].fill(7,1,2);
//['a',7,'c']
数组实例的entries(),keys()和values()
ES6提供三个新的方法 ----entries(),keys()和values()----用于遍历数组,它们都返回一个遍历器对象,可以用for…of循环进行遍历,唯一的区别是:keys()是对键名的遍历.values()是对键值的遍历,entries()是对键值对的遍历.
for (let index of ['a','b'].keys()) {
console.log(index);
}
//0
//1
for (let item of ['a','b'].values()) {
console.log(item);
}
// 'a'
// 'b'
for (let [index, elem] of ['a','b'].entries()) {
console.log(index,elem);
}
//0 'a'
//1 'b'
数组实例的includes()
Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值.
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
[1, 2, NaN].includes(NaN) // true
该方法的第二个参数表示搜索的起始位置,默认为0.如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始.
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
对象的扩展
属性的简洁表示法
ES6允许直接写入变量和函数,作为对象的属性和方法.
let ms = {};
function getItem (key) {
return key in ms ? ms[key] :nill;
}
function setItem (key,value) {
ms[key] = value;
}
function clear () {
ms = {};
}
module.exports = {getItem,setItem,clear};
//等同于
module.exports = {
getItem:getItem,
setItem:setItem,
clear:clear
}
方法的name属性
函数的name属性,返回函数名,对象方法也是函数,因此也有name属性.
Object.is()
作用:用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致.
Object.is('foo','foo')
// true
Object.is({},{})
// false
不同之处只有两个:(1)+0不等于-0,(2)NaN等于自身.
+0 === -0 // true
NaN === NaN // false
Object.is(+0,-0) //false
Object.is(NaN,NaN) //true
Object.assign()
作用:Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target).
const target = {a: 1};
const source1 = {b: 2};
const source2 = {c: 3};
Object.assign(target,source1,source2);
target //{a:1,b:2,c:3}
Object.assign方法的第一个参数是目标对象,后面的参数都是源对象.
注意:如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性.
_proto_属性,Object.setPrototypeOf(),Object.getPrototypeOf()
_proto_属性
用来读取和设置当前对象的prototype对象.
// es6 的写法
const obj = {
method: function() { ... }
};
obj.__proto__ = someOtherObj;
Object.setPrototypeOf()
Object.setPrototypeOf()方法的作用与_proto_相同,用来设置一个对象的prototype对象,返回参数对象本身,它是ES6正式推荐的设置原型对象的方法.
//格式
Object.setPrototypeOf(object,prototype)
//用法
const o = Object.setPrototypeOf({},null);
例子:
let proto = {};
let obj = {x: 10};
Object.setPrototypeOf(obj, proto);
proto.y = 20;
proto.z = 40;
obj.x //10
obj.y //20
obj.z //40
//将proto对象设为obj对象的原型,所以从obj对象可以读取proto对象的属性。
Object.getPrototypeOf()
该方法与Object.setPrototypeOf方法配套,用于读取一个对象的原型对象。
Object.getPrototypeOf(obj);
super关键字
this关键字总是指向函数所在的当前对象
super关键字指向当前对象的原型对象.
const proto = {
foo: 'hello'
};
const obj = {
find() {
return super.foo;
}
};
Object.setPrototypeOf(Obj,proto);
obj.find() //"hello"
Object.keys(), Object.values(),
Object.entries()
let {keys, values, entries} = Object;
let obj = {a:1,b:2,c:3};
for (let key of keys(obj)) {
console.log(key); //'a','b','c'
}
for (let value of values(obj)) {
console.log(value); //1,2,3
}
for (let [key, value] of entries(obj)) {
console.log([key,value]); //['a',1],['b',2],['c',3]
}
对象的扩展运算符
let z = {a:3,b:4};
let n = {...z};
n //{a:3,b:4}
Null传导运算符?
举例:要读取message.body.user.firstName
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default';
引入“Null传导运算符“ ?. 简化上面的写法
const firstName = message?.body?.user?.firstName || 'default';
Symbol
ES6引入了一种新的原始数据类型Symbol,表示独一无二的值.它是JS语言的第七种数据类型,前六种:undefined.null.布尔值(Boolean),字符串(String),数值(Number),对象(Object).
let s = Symbol();
typeof s //"symbol"
注意:Symbol函数前不能使用new命令,否则会报错,这是因为生成的Symbol是一个原始类型的值,不是对象.
Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串,比较容易区分.
let s1 = Symbol('foo');
let s2 = Symbol('bar');
s1 // Symbol(foo)
s2 // Symbol(bar)
s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"
如果Symbol的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后才生成一个Symbol值.
const obj = {
toString() {
return 'abc';
}
};
const sym = Symbol(obj);
sym // Symbol(abc)
Symbol值不能与其他类型的值进行运算,会报错.但是,Symbol值可以显示转为字符串,也可以转为布尔值,但是不能转为数值.
作为属性名的Symbol
let mySymbol = Symbol();
//第一种写法
let a={};
a[mySymbol]='Hello!';
//第二种写法
let a={
[mySymbol]:'Hello!'
};
//第三种写法
let a={};
Object.defineProterty(a,mySymbol,{value:'Hello!'});
//以上写法都得到同样结果
a[mySymbol]//"Hello!"
属性名的遍历
Symbol作为属性名,该属性不会出现在for…in,for…of循环中,也不会被Object.keys(),Object.getOwnPropertyNames(),JSON.stringify()返回.但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有Symbol属性名.
Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的Symbol值.
cont obj = {};
let a = Symbol('a');
let b = Symbol('b');
obj[a] = 'Hello';
obj[b] = 'World';
const objectSymbols = Object.getOwnPropertySymbols(obj);
objectSymbols
//[Symbol(a),Symbol(b)]
另一个新的API,Reflect.ownKeys方法可以返回所有类型的键名,包括常规键名和Symbol键名.
let obj = {
[Symbol('my_key')]:1,
enum:2,
nonEnum:3
};
Reflect.ownKeys(obj)
// ["enum","nonEnum",Symbol(my_key)]
作用:由于以Symbol值作为名称的属性,不会被常规的方法遍历到.我们可以利用这个特性,为对象定义一些非私有的,但又希望只用于内部的方法.