内容参考自:ECMAScript入门MDN

一、变量

1、let

let与var类似,都是用来声明变量的,与var不同的是,let声明的变量只在let所在的代码块内有效(块级作用域)。

  • 样例一
(function varTest(){
	var x = 1;
	if(true){
		var x = 2;
		console.log(x); //2
	}
	console.log(x); //2
})();
(function letTest(){
	let x = 1;
	if(true){
		let x = 2;
		console.log(x); //2
	}
	console.log(x); //1
})();
  • 样例二
var arr = [];
for(var i = 0; i < 3; i++){
	arr[i] = function(){
		console.log(i);
	};
}
arr[1](); //3
console.log(i); //3
var arr = [];
for(let i = 0; i < 3; i++){
	arr[i] = function(){
		console.log(i);
	};
}
arr[1](); //1
console.log(i); //3

ES5只有全局作用域和函数作用域,let实际上是增加了块级作用域,例如:let很适合用在for循环的计数器中,这样就不会导致将循环变量泄漏为全局变量。

  • 暂存死区

在同一个作用域中不允许用let重复定义一个变量。

(function(){
    let foo = 1;
    let foo = 2; //Error
})();

同样,不能在函数内部重新声明参数:

(function(arg){
    let arg = 1; //Uncaught SyntaxError: Identifier 'arg' has already been declared
})();

但可以在创建新的作用域块中重新声明:

(function(arg){
    {
        let arg = 1;
    }
})();
let x = 1;
switch (x) {
  case 0:
	let foo;
	break;

  case 1:
	let foo; // SyntaxError for redeclaration.
	break;
}

可以在case语句后创建新的作用域块,形成新的词法环境,这样就不会产生上面的重复声明错误:

let x = 1;
switch (x) {
  case 0:{
	let foo;
	break;
  }
  case 1:
	let foo;
	break;
}
  • 不存在变量提升
(function do_something() {
	console.log(bar); // undefined
	console.log(foo); // ReferenceError: foo is not defined
	var bar = 1;
	let foo = 2;
})();

  • 函数

在块级作用域中声明函数,在不同的环境下执行结果不同:

function f() { 
    console.log('out'); 
}
(function () {
    if (false) {
        //重复声明一次
        function f() { console.log('in'); }
    }
    f();
}());

上面的代码在ES5环境中输出:in,在ES6环境中报错:Uncaught TypeError: f is not a function

因此,应避免在块级作用域中声明函数,如果确实需要,应该使用函数表达式:

function f() { 
    console.log('out'); 
}
(function () {
    if (false) {
       let f = function(){ console.log('in'); }
    }
    f();
}());

改为使用函数表达式后,在ES6环境下输出:out

2、const

const用来声明常量,常量不能重新赋值且不能重新声明。const与let一样,也有暂存死区,而且声明的常量(let声明的变量)不会变为全局对象的属性。

const NUM = 10;
//报错
let NUM = 20;
//也会报错
var NUM = 20;
const NUM = 10;

if(NUM == 10){
	//正常
	let NUM = 20;
	//输出20
	console.log(NUM);
	//被提升到全局上下文,报错:Identifier 'NUM' has already been declared
	var NUM = 50;
}

对象属性不在赋值保护范围:

const MY_OBJECT = {'key':'value'};
//正常
MY_OBJECT.key = 'null';
//Uncaught TypeError: Assignment to constant variable.
MY_OBJECT = {};

数组与对象类似:

const ARR = [];
ARR[0] = 'A';
ARR.push('B');
//Uncaught TypeError: Assignment to constant variable.
ARR = ['X'];

3、变量的解构赋值

使用解构赋值可以将值从数组、属性从对象提取到不同的变量中。解构赋值使用了和字面量相同的语法,不同的是在表达式的左边定义了要从原变量中取出什么变量。

let x = [1, 2, 3, 4, 5];
let [y, z] = x;
console.log(y); //1
console.log(z); //2
  • 解构数组

    • 变量声明并赋值

        let x = ['A', 'B', 'C'];
        let [a, b, c] = x;
        console.log(a); //A
        console.log(b); //B
        console.log(c); //C
      
    • 变量先声明后赋值

        let a, b, c = 3;
        [a, b] = [1, 2];
        console.log(a); //1
        console.log(b); //2
      
    • 默认值

      为了防止从数组中取出undefined的值,可以为变量设置默认值:

        let [a = 5, b = 10] = [1];
        console.log(a); //1
        console.log(b); //10
      
    • 交换变量的值

        let a = 1, b = 5;
        [a, b] = [b, a];
        console.log(a); //5
        console.log(b); //1
      
    • 函数返回值赋值

        function f(){
            return [1, 2];
        }
      
        let [a, b] = f();
        console.log(a); //1
        console.log(b); //2
      
    • 赋值时忽略某些值

        let [a, , , b] = [1, 2, 3, 4];
        console.log(a); //1
        console.log(b); //4
      
    • 将剩余数组赋给一个变量

      注意:剩余元素(…x)必须是数组的最后一个元素

        //...b必须放到数组最后
        let [a, ...b] = [1, 2, 3, 4];
        console.log(a); //1
        console.log(b); //[2, 3, 4]
      
    • 正则提取值

      使用正则表达式的exec()方法匹配字符串会返回一个数组,该数组的第一个值是完全匹配正则表达式的字符串,数组的其他值是匹配正则表达式括号内的内容。

        let url = "https://developer.mozilla.org/en-US/Web/JavaScript";
        let parsedURL = /^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec(url);
        //赋值时忽略第一个值
        let [, protocol, fullhost, fullpath] = parsedURL;
      
        console.log(protocol); //https
        console.log(fullhost); //developer.mozilla.org
        console.log(fullpath); //en-US/Web/JavaScript
      
  • 解构对象

    • 基本赋值

        let user = {name: 'albert', age: 26};
        let {name, age} = user;
        console.log(name); //albert
        console.log(age); //26
      
    • 给新的变量赋值

        let user = {name: 'albert', age: 26};
        let {name: foo, age: bar} = user;
        console.log(foo); //albert
        console.log(bar); //26
      
    • 默认值

        let {a = 1, b = 2} = {a: 5};
        console.log(a); //5
        console.log(b); //2
      
    • 给新的变量赋默认值

        let {name: foo = 'albert', age: bar = 26} = {name: 'tom'};
        console.log(name); //albert
        console.log(foo); //tom
        console.log(age); //26
        console.log(bar); //26
      
    • 函数参数默认值{: #func_args_defval}

        function f({size = 'big', cords = {x: 0, y: 0}, radius}){
            console.log(size, cords, radius);
        }
        f({radius: 25});
      

      输出:big {x: 0, y: 0} 25

    • For of迭代解构

        let students = [
            {
                name: 'Tome',
                address: {
                    city: 'BeiJing',
                    country: 'China'
                }
            },
            {
                name: 'Mike',
                address: {
                    city: 'New York',
                    country: 'America'
                }
            }
        ];
        for(let {name, address: {city: c}} of students){
            console.log(name, c);
        }
      

      输出:

        Tome BeiJing
        Mike New York
      
    • 对象计算属性解构

        let key = 'name';
        let {[key]: foo} = {name: 'albert'};
        console.log(foo); //albert
      
    • 对象剩余属性赋值

        let {a, b, ...rest} = {a: 1, b: 2, c: 3, d: 4};
        console.log(a); //1
        console.log(b); //2
        console.log(rest); //{c: 3, d: 4}
      
    • 字符串的解构赋值

        let [a, b, c] = 'tom';
        console.log(a, b, c); //t o m
      
        let {length: len} = 'hi';
        console.log(len); //2
      
    • 解构赋值的值复制是浅复制

        let obj = {a: {b: 1}};
        let {...x} = obj;
        console.log(x.a.b);//1
        x.a.b = 5;
        console.log(obj.a.b);//5
      

二、扩展

1、字符串(String)的扩展

  • includes

语法:str.includes(searchString[, position])

从str中的指定位置(position可选)查找searchString,如果找到返回true,否则返回false。此方法参数searchString区分大小写。

console.log('Hello'.includes('el'));//true
console.log('Blue Whale'.includes('blue'));//false
  • startsWith

语法:str.startsWith(searchString [, position]);

其中position为在str中查找searchString的开始位置,默认为0。此方法参数searchString区分大小写。

console.log('Hello World!'.startsWith('He'));//true
console.log('Hello World!'.startsWith('W', 6));//true
  • endWith

语法:str.endsWith(searchString [, position]);

其中position为str的长度,默认值为str.length。此方法参数searchString区分大小写。

console.log('Blue Whale'.endsWith('ale'));//true
console.log('Blue Whale'.endsWith(' ', 5));//true
  • repeat

语法:let resultString = str.repeat(count);

此方法返回原字符串str重复count次的新字符串,其中count的值为大于等于0的整数。

console.log('hi'.repeat(0));//''
console.log('hi'.repeat(3));//hihihi
  • padStart

语法:str.padStart(targetLength [, padString])

使用padString填充str,使str的长度达到targetLength。如果targetLength小于str的长度,则返回str。padString的默认值为’‘。

console.log('abc'.padStart(10));
console.log('abc'.padStart(10, 'hello'));
console.log('abc'.padStart(5, 'world'));
console.log('abc'.padStart(1));

输出:

'       abc'
'helloheabc'
'woabc'
'abc'
  • padEnd

语法:str.padEnd(targetLength [, padString])

padStart类似,只不过填充方向相反。

console.log('abc'.padEnd(10));
console.log('abc'.padEnd(10, 'hello'));
console.log('abc'.padEnd(5, 'world'));
console.log('abc'.padEnd(1));

输出:

'abc       '
'abchellohe'
'abcwo'
'abc'
  • 模板字符串

    模板字符串是允许嵌入表达式的字符串,它使用反引号(\)包含字符串内容,其中可以使用${expression}`占位符。

    • 多行字符串

        console.log(`line1: hello
        line2: world!`);
      

      输出:

        line1: hello
        line2: world!
      
    • 使用表达式

        let name = 'albert';
        console.log(`Hi, ${name}`);//Hi, albert
      
    • 嵌套模板

        let [isLargeScreen, isPC] = [true, false];
        let classes = `header ${ isLargeScreen ? `icon-${isPC ? 'pc-style' : 'pad-style'}` : ''}`;
        console.log(classes);//header icon-pad-style
      
    • 标签模板

      标签模板是函数调用的一种特殊形式,’标签’指函数,其后面的模板字符串就是函数的参数。标签模板定义类似于:

        function tag(stringArr, ...values){
      
        }
      

      其中,tag函数的第一个参数是字符串数组,数组成员是模板字符串中没有变量替换的部分,其他参数是模板字符串中变量被替换后的值。

        let {name, age} = {name: 'Mike', age: 26};
      
        function myTag(strings, name, age){
            console.log(strings);
            console.log(name);
            console.log(age);
        }
      
        myTag`My name is ${name}, I'm ${age} years old.`;
      

  • String.raw

语法:

String.raw(callSite, ...substitutions)
String.raw `templateString`

String.raw是用来获取一个模板字符串的原始字面量值的,也是唯一一个内置的模板字符串标签函数。通常不需要将它看成一个普通函数,而是使用上面的第二种方式,将它放在模板字符串前面即可。

String.raw `Hi\n!`;//"Hi\n!"
let name = "Bob";
String.raw `Hi\n${name}!`//"Hi\nBob!"

2、数值的扩展

  • 二进制和八进制

二进制和八进制分别用前缀0b(或0B)和0o(或0O)表示,在严格模式中,八进制不再允许使用前缀0表示。

//二进制
console.log(0b11);//3
console.log(Number('0b11'));//3
//八进制
console.log(011);//9
console.log(0o11);//9
console.log(Number('0o11'));//9
//十六进制
console.log(0x11);//17
console.log(Number('0x11'));//17
  • Number.isFinite(value)

判断给定的值是不是一个有穷数,与全局函数isFinite()相比,此方法不会强制将参数转换为数值,只有参数是数值类型且值是有穷的时才返回true。

console.log(isFinite('0'));//true
console.log(Number.isFinite('0'));//false

console.log(Number.isFinite(Infinity));//false
console.log(Number.isFinite(NaN));//false
console.log(Number.isFinite(123));//true
  • Number.isNaN(value)

判断给定的值是不是NaN,与全局函数isNaN()相比,此方法不会强制将参数转换为数值,只有参数是数值类型且值为NaN时才返回true。

console.log(NaN == NaN);//false
console.log(NaN === NaN);//false
console.log(Number.isNaN(NaN));//true

console.log(isNaN('NaN'));//true
console.log(Number.isNaN('NaN'));//false

console.log(isNaN(undefined));//true
console.log(Number.isNaN(undefined));//false

console.log(isNaN({}));//true
console.log(Number.isNaN({}));//false

console.log(isNaN('blabla'));//true
console.log(Number.isNaN('blabla'));//false
  • Number.isInteger(value)

判断给定的参数是否是整数,由于JavaScript数值存储的精度原因,此方法可能误判,如果对数据精度要求较高,不建议使用此方法。

console.log(Number.isInteger(0));//true
console.log(Number.isInteger('1'));//false

console.log(Number.isInteger(0.1));//false
console.log(Number.isInteger(5.0));//true

console.log(Number.isInteger(Infinity));//false
console.log(Number.isInteger(-Infinity));//false
  • Number.isSafeInteger(testValue)

检测参数是否是安全整数,安全整数范围为: [-253 - 1, 253 - 1]

console.log(Number.isSafeInteger(Math.pow(2, 53) - 1));//true
console.log(Number.isSafeInteger(Math.pow(2, 53)));//false

console.log(Number.isSafeInteger(3));//true
console.log(Number.isSafeInteger("3"));//false

console.log(Number.isSafeInteger(3.0));//true
console.log(Number.isSafeInteger(3.1));//false

console.log(Number.isSafeInteger(NaN));//false
console.log(Number.isSafeInteger(Infinity));//false
  • Math.sign(x)

判断x的符号,参数会被隐式转换成数值类型,返回值有5种:1、-1、0、-0、NaN,分别表示:正数、负数、正零、负零和NaN。

console.log(Math.sign(5));//1
console.log(Math.sign('5'));//1
console.log(Math.sign(-5));//-1

console.log(Math.sign(0));//0
console.log(Math.sign(-0));//-0

console.log(Math.sign(NaN));//NaN
console.log(Math.sign('foo'));//NaN
  • Math.cbrt(x)

计算x的立方根,参数会被自动转换成数值类型。

console.log(Math.cbrt(0));//0
console.log(Math.cbrt(1));//1
console.log(Math.cbrt(8));//2

console.log(Math.cbrt(Infinity));//Infinity
console.log(Math.cbrt(null));//0
console.log(Math.cbrt(NaN));//NaN
  • Math.hypot([value1[,value2, ...]])

返回所有参数的平方和的平方根。如果不传入任何参数,则返回0;如果参数中至少有一个不能被转换为数字,则返回NaN; 如果只传一个参数,效果等同于Math.abs(x)

console.log(Math.hypot(3, 4));//5
console.log(Math.hypot());//0
console.log(Math.hypot(-5));//5
console.log(Math.hypot(1, 'a'));//NaN
  • 指数运算符**

指数运算符是从右向左结合。

console.log(2 ** 2);//4
console.log(2 ** 3);//8
console.log(2 ** 2 ** 3);//256

let a = 3;
a **= 2;
console.log(a);//9

3、正则的扩展

  • y标志

表示粘性匹配,仅匹配目标字符串中正则表达式lastIndex属性指示的索引,不尝试从任何后续的索引开始匹配。

yg的区别:

lastIndex属性指定每次搜索的开始位置,g标志从此位置向后搜索,直到发现匹配项为止;y标志必须在lastIndex指定的位置上发现匹配项。

let text = 'First line\nSecond line\n...\nThird line';
let regex = /(\S+) line\n?/y;

let match = regex.exec(text);
console.log(match[1]);//First
console.log(regex.lastIndex);//11

match = regex.exec(text);
console.log(match[1]);//Second
console.log(regex.lastIndex);//23

match = regex.exec(text);
console.log(match === null);//true
console.log(regex.lastIndex);//0
let regex = /(\S+) line\n?/g;

let match = regex.exec(text);
console.log(match[1]);//First
console.log(regex.lastIndex);//11

match = regex.exec(text);
console.log(match[1]);//Second
console.log(regex.lastIndex);//23

match = regex.exec(text);
console.log(match[1]);//Third
console.log(regex.lastIndex);//37
  • stickyflags
let regex = /(\S+) line\n?/yi;

//RegExp.prototype.sticky
console.log(regex.sticky);//true
//RegExp.prototype.flags
console.log(regex.flags);//iy
  • 命名捕获组

可以在正则表达式分组的圆括号内的最前面添加?<组名>来为此分组命名:

let regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
let match = regex.exec('2018-12-21');
let group = match.groups;
console.log(group.year);//2018
console.log(group.month);//12
console.log(group.day);//21

4、函数的扩展

  • 参数默认值

在定义函数时,可以为参数指定默认值:

function greet(name, greetings = 'Hi'){
	console.log(`${greetings}, ${name}!`);
}
greet('Tom');//Hi, Tom!
greet('张三', '你好');//你好, 张三!

如果参数传入undefined,则该参数使用默认值,null则不同:

function print(x = 1, y = 2){
    console.log(x, y);
}

print(undefined, null);//1 null

解构赋值-函数参数默认值

  • 参数作用域

如果设置了参数的默认值,函数在初始化时,参数会形成一个单独的作用域,初始化结束时此作用域会消失。

let x = 1;
function f(x, y = x){
	console.log(x, y);
}
f(2);//2 2

function f1(x = 5, y = x){
	console.log(x, y);
}
f1();//5 5 

function f2(y = x){
	console.log(y);
}
f2();//1
let x = 1;
function f(x, y = function(){ x = 2;}){
    y();
    console.log(x);
}
f();//2

function f1(x, y = function(){ x = 2;}){
	//此处使用let定义x会报错
	var x = 5;
    y();
    console.log(x);
}
f1();//5
  • 剩余参数
function(a, b, ...theArgs){
	//...
}

此语法允许将不定数量的参数表示为一个数组,它与arguments不同,是一个真正的数组。

function sum(...theArgs){
	return theArgs.reduce(function(previous, current){
		return previous + current;
	});
}
console.log(sum(1, 2, 3));//6
console.log(sum(1, 2, 3, 4));//10

function multiply(multiplier, ...theArgs){
	return theArgs.map(function(element){
		return multiplier * element;
	});
}
console.log(multiply(2, 1, 2, 3));//[2, 4, 6]
  • 解构剩余参数
function f(...[a, b, c]){
	return a + b + c;
}
f(1, 2, 3);//6
f(1, 2, 3, 4);//6
  • 箭头函数

    箭头函数语法比函数表达式语法更简洁,而且没有thisargumentssupernew.target。这种表达式更适用于匿名函数,但是不能用作构造函数。

    *语法:

      (param1, param2, , paramN) => { statements }
    
      (param1, param2, , paramN) => expression
      等价于
      (param1, param2, , paramN) => { return expression; } 
    

    当只有一个参数时,圆括号是可选的:

      (singleParam) => { statements }
      
      singleParam => { statements }
    

    //没有参数的函数要保留圆括号

      () => { statements }
    
      let f = () => {a: 1};
      console.log(f());//undefined
    
      let f0 = () => ({a: 1});
      console.log(f0());//{a: 1}
    

    带括号的函数体返回对象字面量:

      params => ({foo: bar}) 
    

    支持剩余参数和默认参数:

      (param1, param2, ...rest) => { statements } 
      (param1 = defaultValue1, param2, , paramN = defaultValueN) => { statements } 
    

    参数同样支持解构:

      let f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c;
      f(); // 6
    
  • 不绑定this

let obj = {
	a: 10,
	b: function(){
		console.log(this.a);
	},
	c: () => console.log(this.a)
};
obj.b();//10
obj.c();//undefined

在ECMAScript 3/5中,通过将this值分配给封闭的变量来解决this问题:

function Person() {
	var that = this;
	that.age = 0;

	setTimeout(function growUp() {
		//that为person对象
		that.age++;
	}, 1000);
}

箭头函数不会创建自己的this,只会从它的作用域链的上一层继承this

function Person(){
	this.age = 0;

	setTimeout(() => {
		//此处this指向person对象
		this.age++; 
	}, 1000);
}

由于箭头函数没有自己的this指针,通过call()apply()调用函数时,只能传递参数,它们的第一个参数会被忽略。

let obj = {
  num: 1,
  add: function(x){
      return (v => v + this.num)(x);
  },
  callAdd: function(x){
      let o = {num: 5};
      return (v => v + this.num).call(o, x);
  }
};
console.log(obj.add(1));//2
console.log(obj.callAdd(1));//2

箭头函数不绑定arguments

let f = () => console.log(arguments);
f();

let arguments = [1, 2, 3];
let f1 = () => arguments[0];
f1();

function foo(n){
	//arguments隐式绑定foo函数的arguments对象
	let f = () => arguments[0] + 1;
	return f();
}
console.log(foo(5));//6
  • new操作符和prototype属性

箭头函数不能用作构造函数,和new一起使用会报错;而且箭头函数没有prototype属性:

  • 箭头函数内的变量及其作用域

箭头函数内使用let或var定义的变量或参数中的变量是局部变量,不加任何变量声明命令直接定义的变量是全局变量:

let f = (name = 'Tom') => {
	var now = new Date();
	var age = 22;
	sex = 'male';
	return `${name} ${age} ${sex}`;
};
f();
console.log(typeof now);//undefined
console.log(typeof age);//undefined
console.log(typeof sex);//string
console.log(now);//Uncaught ReferenceError: now is not defined
  • 箭头函数的闭包
let f = (i = 0) => {return () => ++i};//或 let f = (i = 0) => () => ++i;
let inc = f();
console.log(inc());
console.log(inc());

5、对象的扩展

  • 属性和方法的简洁定义

ES6提供了更简洁的定义对象属性和方法的方式:允许在对象中直接写变量,这种情况下,属性名即为变量名,属性值为变量值。

let foo = 'hello';
let bar = {foo};//等价于 let bar = {foo: foo};
console.log(bar);//{foo: "hello"}

let f = (x,y) => ({x, y});
console.log(f(1, 2));//{x: 1, y: 2}

let obj = {
    greet(name){
      return `Hi, ${name}`; 
    }
};
console.log(obj.greet('albert'));//Hi, albert
let sex = 'male';
let person = {
    age: 20,
    sex,
    say(){
        console.log('Hello!');
    }
};
console.log(person);
  • 表达式属性名

可以使用表达式作为对象的属性名或方法名:

let key = 'foo';
let hi = 'Hello';
let obj = {
	[key]: 'bar',
	['say' + hi](){
		console.log('Hi!');
	}
};
  • 对象展开(spread)

可以将已有对象的所有可枚举(enumerable)属性拷贝到新构造的对象中。与Object.assign()函数不同,展开语法不会触发setters

语法:

let objClone = { ...obj };
let base = {name: 'albert', age: 22};
let adv = {sex: 'male', hobby: 'play games'};
//复制
let clonedObj = {...base};
console.log(clonedObj);//{name: "albert", age: 22}
//合并
let mergedObj = {...base, ...adv};
console.log(mergedObj);//{name: "albert", age: 22, sex: "male", hobby: "play games"}
//同名属性覆盖
let overridedObj = {...base, ...{name: 'Tom'}};
console.log(overridedObj);//{name: "Tom", age: 22}

如果...后面是一个空对象则没有任何效果;如果后面不是对象,则会自动将其转为对象;如果后面是字符串,则会转为类似数组的对象:

console.log({...{}, a: 1});//{a: 1}
//{...Object(1)}
console.log({...1});//{}
//{...Object(true)}
console.log({...true});//{}
//{...Object(undefined)}
console.log({...undefined});//{}
//{...Object(null)}
console.log({...null});//{}
console.log({...'hello'});//{0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}

...后面也可以跟表达式:

let x = 3;
let obj = {...(x > 1 ? {type: 'cat'} : {type: 'dog'})};
console.log(obj);//{type: "cat"}
  • Object.assign(target, ...sources)

此方法将一个或多个源对象的所有可枚举属性的值复制到目标对象,并返回目标对象。

let obj = {a: 1};
let copy = Object.assign({foo: 'bar'}, obj);
console.log(copy);//{foo: "bar", a: 1}

//合并相同属性的对象
let o1 = {a: 1, b: 2, c: 3};
let o2 = {b: 5, c: 10};
let o3 = {c: 33};
let mergedObj = Object.assign({c: 123}, o1, o2, o3);
console.log(mergedObj);//{c: 33, a: 1, b: 5}

Object.assign()是浅拷贝:

let obj = {a: 0, b: {foo: 'bar'}};
let copy = Object.assign({}, obj);

copy.a = 5;
console.log(obj);//{a: 0, b: {foo: 'bar'}};
console.log(copy);//{a: 5, b: {foo: 'bar'}};

copy.b.foo = 'hello';
console.log(obj);//{a: 0, b: {foo: 'hello'}};
console.log(copy);//{a: 5, b: {foo: 'hello'}};

使用JSON.parse与JSON.stringify方法实现深拷贝:

let obj = {a: 0, b: {foo: 'bar'}};
let deepCopy = JSON.parse(JSON.stringify(obj));
deepCopy.a = 5;
deepCopy.b.foo = 'hello';
console.log(obj);//{a: 0, b: {foo: 'bar'}};

继承属性和不可枚举的属性是不能拷贝的:

let obj = Object.create(
    {foo: 1},//foo是继承的属性
    {
        bar:{value: 2},//bar是不可枚举的属性
        baz:{
            value: 3,
            enumerable: true//baz是可以枚举的属性
        }
    }
);
console.log(obj);//{baz: 3, bar: 2}
console.log(obj.foo);//1

let copy = Object.assign({}, obj);
console.log(copy);//{baz: 3}

在拷贝时原始类型会被包装为对象,nullundefined会被忽略:

let v1 = 'abc';
let v2 = true;
let v3 = 10;

let obj = Object.assign({}, v1, v2, v3, null, undefined);
console.log(obj);//{0: "a", 1: "b", 2: "c"}

在出现错误的情况下,例如:属性不可写,则会引发TypeError,错误发生之前的属性,还是可以拷贝到target对象中的。

let target = Object.defineProperty({}, 'foo', {
    value: 1, 
    writable: false
});
try{
    Object.assign(target, {x: 'bar'}, {a: 1, foo: 2, b: 3}, {c: 4});
}catch(e){
    //TypeError: Cannot assign to read only property 'foo' of object '#<Object>'
    console.error(e);
}
console.log(target);//{x: "bar", a: 1, foo: 1}

访问器拷贝{: #copy_getter_setter}:

let obj = {
    foo: 1,
    get bar(){
        return 2;
    }
};
console.log(obj);//{foo: 1}

let copy = Object.assign({}, obj);
//copy.bar的值来自于obj.bar的getter函数的返回值
console.log(copy);//{foo: 1, bar: 2}

console.log(Object.getOwnPropertyDescriptor(obj, 'bar'));
console.log(Object.getOwnPropertyDescriptor(copy, 'bar'));

  • Object.is(value1, value2);

    判断两个值是否是相同的值,如果满足下列条件,则两个值相同:

    • 两个值都是undefinednulltruefalse

    • 两个值是由相同个数的字符按相同顺序组成的字符串

    • 两个值都是数值而且它们都是正零+0、负零-0NaN或除零和NaN外的其他同一个数字

    • 两个值指向同一个对象

此方法与==不同,==或对操作数做隐式类型转换;此方法也与===不同,===将数值+0-0认为是相等的,且NaN不等于NaN

console.log(Object.is('foo', 'foo'));//true
console.log(Object.is('foo', 'bar'));//false

console.log(0 === -0);//true
console.log(Object.is(0, -0));//false
console.log(Object.is(-0, -0));//true
let num = 0/0;
console.log(NaN === num);//false
console.log(Object.is(NaN, num));//true

let obj = {a: 1};
console.log(Object.is(obj, obj));//true
console.log(Object.is([], []));//false
  • Object.getPrototypeOf(object)

返回指定对象的原型(内部prototype属性),如果没有继承属性,则返回null;如果参数不是对象类型,则会被强制转换为对象。

let proto = {};
let obj = Object.create(proto);
console.log(Object.getPrototypeOf(obj) === proto);//true

let reg = /a/;
console.log(Object.getPrototypeOf(reg) == RegExp.prototype);//true

let num = 10;
console.log(Object.getPrototypeOf(num) == Number.prototype);//true
  • Object.setPrototypeOf(obj, prototype)

设置指定对象的原型为另一个对象或null。如果对象的原型(prototype)被修改为不可扩展(extensible),则会抛出TypeError异常。

由于设置原型可能会有性能问题,因此不建议设置一个对象的原型,而是使用Object.create()来创建自己想要的新对象

let obj = {};
Object.setPrototypeOf(obj, null);
console.log(Object.getPrototypeOf(obj));//null
  • Object.getOwnPropertyDescriptors(obj)

获取一个对象所有自身属性的描述符,如果没有自身属性,则返回空对象。此方法可以解决Object.assign()无法正确拷贝getter/setter的问题。

let obj = {
    foo: 1,
    get bar(){
        return 2;
    }
};
console.log(obj);//{foo: 1}

let copy = {};
Object.defineProperties(copy, Object.getOwnPropertyDescriptors(obj));
console.log(copy);//{foo: 1}

console.log(Object.getOwnPropertyDescriptor(obj, 'bar'));
console.log(Object.getOwnPropertyDescriptor(copy, 'bar'));

此方法还可以与Object.create()一起使用,实现将对象属性克隆到一个新对象(浅拷贝)。

let shallowClone = obj => Object.create(
	Object.getPrototypeOf(obj),
	Object.getOwnPropertyDescriptors(obj)
);
let o = {a: 1, b: 2};
console.log(shallowClone(o));//{a: 1, b: 2}
  • Object.keys(obj)

返回指定对象自身的所有可枚举属性的属性名组成的字符串数组。如果参数不是对象,则会强制转换为一个对象。

//数组
let arr = ['x', 'y', 'z'];
console.log(Object.keys(arr));//["0", "1", "2"]
//类似数组对象
let arrObj = {0: 'a', 10: 'b', 20: 'c'};
console.log(Object.keys(arrObj));//["0", "10", "20"]
//对象
let obj = {name: 'albert', age: 22, sex: 'male'};
console.log(Object.keys(obj));//["name", "age", "sex"]
//非对象
console.log(Object.keys('foo'));//["0", "1", "2"]
  • Object.values(obj)

返回指定对象自身的所有可枚举属性的值组成的数组。如果参数不是对象,则会强制转换为一个对象。

//数组
let arr = ['x', 'y', 'z'];
console.log(Object.values(arr));//["x", "y", "z"]
//类似数组对象
let arrObj = {0: 'a', 10: 'b', 20: 'c'};
console.log(Object.values(arrObj));//["a", "b", "c"]
//对象
let obj = {name: 'albert', age: 22, sex: 'male'};
console.log(Object.values(obj));//["albert", 22, "male"]
//非对象
console.log(Object.values('foo'));//["f", "o", "o"]
  • Object.entries(obj)

返回指定对象自身的所有可枚举属性名和属性值组成的键值对数组。

console.log(Object.entries({foo: 'bar', baz: 55}));
//非对象
console.log(Object.entries('foo'));
//数组
let arr = ['x', 'y', 'z'];
console.log(Object.entries(arr));
//类似数组对象
let arrObj = {0: 'a', 10: 'b', 20: 'c'};
console.log(Object.entries(arrObj));
//对象
let obj = {name: 'albert', age: 22, sex: 'male'};
console.log(Object.entries(obj));

for(let [key, value] of Object.entries(obj)){
    console.log(`${key}: ${value}`);
}

Object.entries(obj).forEach(([key, value]) => {
    console.log(`${key}: ${value}`);
});

  • Object.fromEntries(iterable);

此方法是Object.entries(obj)的逆操作,用于将一个键值对数组转为对象。

详细参考:[Object.fromEntries() MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries)

6、数组的扩展

  • 数组展开
console.log(...[1, 2, 3]);//1 2 3
console.log(1, ...[2, 3, 4], 5);//1 2 3 4 5

let f = (a, b, c) => console.log(a, b, c);
f(...[1, 2, 3]);//1 2 3

数组展开如果在括号中且不是函数调用时会报错:

let abc = (...[1, 2, 3]);//Uncaught SyntaxError: Unexpected number

数组展开可以替代apply()

function f(x, y, z){
	console.log(x, y, z);
}
var args = [1, 2, 3];

//ES5
f.apply(null, args);

//ES6
f(...args);

var nums = [5, 1, 33, 20];//Math.max(5, 1, 33, 20);
//ES5
Math.max.apply(null, nums);

//ES6
Math.max(...nums);
  • Array.from(arrayLike[, mapFn[, thisArg]])

根据一个类似数组(有length属性和若干索引属性的任意对象)或可迭代对象(可以获取对象中的元素,例如:Map、Set)创建一个新的数组实例。

此方法可以对最后生成的数组再执行一次map方法(mapFn)后再返回,thisArg表示执行mapFn回调函数时的this对象。

Array.from(obj, mapFn, thisArg)
//等价于
Array.from(obj).map(mapFn, thisArg)
//字符串
console.log(Array.from('foo'));//["f", "o", "o"]
//Set
let set = new Set(['foo', window]);
console.log(Array.from(set));//["foo", Window]
//Map
let map = new Map([['name', 'albert'], ['age', 24]]);
console.log(Array.from(map));//[['name', 'albert'], ['age', 24]]
//函数参数
function f(){
    return Array.from(arguments);
}
console.log(f(1, 2, 3));//[1, 2, 3]
//使用mapFun
let ret = Array.from([1, 2, 3], x => x * 2);
console.log(ret);//[2, 4, 6]
  • Array.of(element0[, element1[, ...[, elementN]]])

此方法用于将一组值转为数组。Array.of()Array构造函数之间的区别在于:Array.of(3)表示创建了一个包含3这个元素的长度为1的数组;而Array(3)表示创建了一个长度为3的空数组。

console.log(Array.of(3));//[3]
console.log(Array(3));//[] length为3
console.log(Array.of(1, 2, 3));//[1, 2, 3]
console.log(Array.of(undefined));//[undefined]
  • arr.copyWithin(target[, start[, end]])

    此方法用来浅复制数组的一部分到数组自身中的另一位置,并返回它而不修改它的大小。其中:

    参数targetstartend必须为整数。

    • target

      目标位置,如果为负数,将从末尾开始计算。如果target大于等于arr.length,则不拷贝;如果targetstart之后,复制的数组序列将被修改以符合arr.length

    • start

      开始复制元素的起始位置,如果为负数,将从末尾开始计算,如果start被忽略,将从0开始复制。

    • end

      开始复制元素的结束位置(不包括此位置的元素),如果为负数,将从末尾开始计算;如果end被忽略,将会复制到arr.length

console.log([1, 2, 3, 4, 5].copyWithin(1));//[1, 1, 2, 3, 4]
console.log([1, 2, 3, 4, 5].copyWithin(-2));//[1, 2, 3, 1, 2]
console.log([1, 2, 3, 4, 5].copyWithin(0, 2, 4));//[3, 4, 3, 4, 5]
console.log([1, 2, 3, 4, 5].copyWithin(3, 0));//[1, 2, 3, 1, 2]
  • arr.find(callback[, thisArg])

    此方法返回数组中满足callback函数的第一个元素的值,如果找不到,则返回undefined

    参数:

      * callback
    
          对数组的每一个元素执行的函数(直到某次调用函数返回true),有3个参数`element`、`index`、`array`,分别表示:当前遍历的元素、当前遍历的索引、数组本身。
    	
      * thisArg
    
          指定`callback`函数的上下文`this`对象的值。
    
let inventory = [
    {name: 'apples', quantity: 2},
    {name: 'bananas', quantity: 5},
    {name: 'cherries', quantity: 1}
];
console.log(inventory.find(element => element.name === 'cherries').quantity);//1
  • arr.findIndex(callback[, thisArg])

此方法与find()方法类似,只不过此方法返回找到的数组元素的索引。

let inventory = [
    {name: 'apples', quantity: 2},
    {name: 'bananas', quantity: 5},
    {name: 'cherries', quantity: 1}
];
console.log(inventory.findIndex(element => element.name === 'cherries'));//2
  • arr.includes(searchElement[, fromIndex])

判断一个数组中是否包含指定的元素,如果包含返回true,否则返回false。也可以指定查找元素的开始索引,fromIndex默认为0,如果为负,表示从末尾开始;如果fromIndex大于等于数组长度,则直接返回false,不会搜索。如果fromIndex为负值,且使用 arr.length + fromIndex计算出的值小于0,则整个数组都会被搜索。

let arr = [1, 2, 3, 4];
console.log(arr.includes(2));//true
console.log(arr.includes(2, 1));//true
console.log(arr.includes(2, 2));//false
console.log(arr.includes(2, 10));//false

let pets = ['cat', 'dog', 'fish'];
console.log(pets.includes('fish'));//true
console.log(pets.includes('bat'));//false
  • arr.fill(value[, start[, end]])

使用固定值填充数组从起始索引到结束索引(不包括结束索引)范围内的全部元素。

此方法有3个参数valuestartend,分别表示:用来填充数组元素的值、起始索引(默认值为0)和结束索引(默认值为数组的长度)。startend如果为负数,则表示从末尾开始。

console.log([1, 2, 3].fill(0));//[0, 0, 0]
console.log([1, 2, 3].fill(0, 1));//[1, 0, 0]
console.log([1, 2, 3].fill(0, 0, 2));//[0, 0, 3]
console.log([1, 2, 3].fill(0, -2, -1));//[1, 0, 3]
console.log([1, 2, 3].fill(0, 3, 5));//[1, 2, 3]
console.log([1, 2, 3].fill(0, NaN, NaN));//[1, 2, 3]
console.log(Array(3).fill(0));//[0, 0, 0]

let arr = Array(3).fill({});
arr[0].hi = 'hello';
console.log(arr);

  • arr.entries()

此方法返回一个新的Array Iterator对象,该对象包含数组中每个索引的键值对。

let arr = ['a', 'b', 'c'];
let iterator = arr.entries();
for(let e of iterator){
    console.log(e);
}

输出:

[0, "a"]
[1, "b"]
[2, "c"]
let arr = ['a', 'b', 'c'];
let iter = arr.entries();
for(let i = 0; i < arr.length; i++){
    let next = iter.next();
    //迭代是否完成
    console.log(next.done);
    if(next.done !== true){
        console.log(next.value);
    }
}

  • arr.keys()

此方法返回一个包含数组中每个索引键(key)的Array Iterator对象。

let arr = ['a', 'b', 'c'];
let iter = arr.keys();
for(let key of iter){
    console.log(key);
}

输出:

0
1
2

此方法与Object.keys()相比,会包含数组中没有对应元素的索引:

let arr = ['a', , 'c'];
console.log(Object.keys(arr));//["0", "2"]
console.log([...arr.keys()]);//[0, 1, 2]
  • arr.values()

此方法返回一个包含数组中每个元素值(value)的Array Iterator对象。

let arr = ['a', 'b', 'c'];
let iter = arr.values();
for(let val of iter){
    console.log(val);
}

输出:

a
b
c
let arr = ['a', 'b', 'c'];
let iter = arr.values();
console.log(iter.next());//{value: "a", done: false}
console.log(iter.next());//{value: "b", done: false}
console.log(iter.next());//{value: "c", done: false}
附: