ECMAScript6基础
内容参考自: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(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
属性指示的索引,不尝试从任何后续的索引开始匹配。
y
与g
的区别:
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
sticky
与flags
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
-
箭头函数
箭头函数语法比函数表达式语法更简洁,而且没有
this
、arguments
、super
和new.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}
在拷贝时原始类型会被包装为对象,null
和undefined
会被忽略:
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);
判断两个值是否是相同的值,如果满足下列条件,则两个值相同:
-
两个值都是
undefined
、null
、true
或false
-
两个值是由相同个数的字符按相同顺序组成的字符串
-
两个值都是数值而且它们都是正零
+0
、负零-0
、NaN
或除零和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]])
此方法用来浅复制数组的一部分到数组自身中的另一位置,并返回它而不修改它的大小。其中:
参数
target
、start
、end
必须为整数。-
target
目标位置,如果为负数,将从末尾开始计算。如果
target
大于等于arr.length
,则不拷贝;如果target
在start
之后,复制的数组序列将被修改以符合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个参数value
、start
、end
,分别表示:用来填充数组元素的值、起始索引(默认值为0)和结束索引(默认值为数组的长度)。start
和end
如果为负数,则表示从末尾开始。
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}