ES6语法笔记(函数、数组、对象)
**以下内容均摘自ECMAScript 6 入门——阮一峰
一、函数
1.函数的默认值与解构赋值
function foo({x, y = 5}) { console.log(x, y); } foo({}) // undefined 5 foo({x: 1}) // 1 5 foo({x: 1, y: 2}) // 1 2 foo() // TypeError: Cannot read property 'x' of undefined function foo({x, y = 5} = {}) { console.log(x, y); } foo() // undefined 5
// 写法一 function m1({x = 0, y = 0} = {}) { return [x, y]; } // 写法二 function m2({x, y} = { x: 0, y: 0 }) { return [x, y]; } // 函数没有参数的情况 m1() // [0, 0] m2() // [0, 0] // x 和 y 都有值的情况 m1({x: 3, y: 8}) // [3, 8] m2({x: 3, y: 8}) // [3, 8] // x 有值,y 无值的情况 m1({x: 3}) // [3, 0] m2({x: 3}) // [3, undefined] // x 和 y 都无值的情况 m1({}) // [0, 0]; m2({}) // [undefined, undefined] m1({z: 3}) // [0, 0] m2({z: 3}) // [undefined, undefined]
写法一有默认对象再对默认对象进行解构赋值,写法二只有默认对象。
2.定义了默认值的参数,应该是函数的尾参数,因为可以省略。
3.函数的length返回没有指定默认值的参数的个数。
(function (a) {}).length // 1 (function (a = 5) {}).length // 0 (function (a, b, c = 5) {}).length // 2 //如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。 (function (a, b = 1, c) {}).length // 1
4.一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。
var x = 1; function f(x, y = x) { console.log(y); } f(2) // 2
5.rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中
function push(array, ...items) { items.forEach(function(item) { array.push(item); console.log(item); }); } var a = []; push(a, 1, 2, 3)
rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
6.ES6中指定严格模式第一种是设定全局性的严格模式,这是合法的。第二种是把函数包在一个无参数的立即执行函数里面。
函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
7.函数的name
属性,返回该函数的函数名。
8.箭头函数
var f = v => v; // 等同于 var f = function (v) { return v; }; var f = () => 5; // 等同于 var f = function () { return 5 }; var sum = (num1, num2) => num1 + num2; // 等同于 var sum = function(num1, num2) { return num1 + num2; };
//如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。 //由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。 // 报错 let getTempItem = id => { id: id, name: "Temp" }; // 不报错 let getTempItem = id => ({ id: id, name: "Temp" });
*箭头函数使用注意事项
(1)函数体内的this
对象,就是定义时所在的对象,而不是使用时所在的对象。(箭头函数本身没有this,是引用的外层对象的this,是固定的)
(2)不可以当作构造函数,也就是说,不可以使用new
命令,否则会抛出一个错误。
(3)不可以使用arguments
对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield
命令,因此箭头函数不能用作 Generator 函数。
9.尾调用指某个函数的最后一步是调用另一个函数。只保留内层函数的调用帧,大大节省内存,这就叫做“尾调用优化”。
二、数组的拓展
1.拓展运算符(spread)是三个点(...
)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5
//由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。 // ES5 的写法 function f(x, y, z) { // ... } var args = [0, 1, 2]; f.apply(null, args); // ES6的写法 function f(x, y, z) { // ... } let args = [0, 1, 2]; f(...args);
2.扩展运算符的应用
扩展运算符提供了数组深度复制的简便写法。
const a1 = [1, 2]; // 写法一 const a2 = [...a1]; // 写法二 const [...a2] = a1;
扩展运算符提供了数组合并的简便写法。(浅拷贝)
const a1 = [{ foo: 1 }]; const a2 = [{ bar: 2 }]; const a3 = a1.concat(a2); const a4 = [...a1, ...a2]; a3[0] === a1[0] // true a4[0] === a1[0] // true
与结构赋值结合(只能位于最后一位)
const [first, ...rest] = [1, 2, 3, 4, 5]; first // 1 rest // [2, 3, 4, 5]
将字符串转化为真正的数组
[...'hello'] // [ "h", "e", "l", "l", "o" ]
任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组。
3.Array.from()将类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)转为真正的数组。
所谓类似数组的对象,本质特征只有一点,即必须有length
属性。
Array.from({ length: 3 }); // [ undefined, undefined, undefined ]
Array.from
还可以接受第二个参数,作用类似于数组的map
方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
let spans = document.querySelectorAll('span.name'); // map() let names1 = Array.prototype.map.call(spans, s => s.textContent); // Array.from() let names2 = Array.from(spans, s => s.textContent)
4.Array.of()方法用于将一组值,转换为数组。
Array.of() // [] Array.of(undefined) // [undefined] Array.of(1) // [1] Array.of(1, 2) // [1, 2]
5.copyWithin()
方法在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。
[1, 2, 3, 4, 5].copyWithin(0, 3) // [4, 5, 3, 4, 5]
6. find() 方法找出符合条件的值(找不到返回undefined
) findIndex()方法返回符合条件值的下标(找不到返回-1)。
接受三个参数,依次为当前的值、当前的位置和原数组。
[1, 5, 10, 15].find(function(value, index, arr) { return value > 9; }) // 10 [1, 5, 10, 15].findIndex(function(value, index, arr) { return value > 9; }) // 2
7.fill()方法以给定值填充一个数组。
//可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。 ['a', 'b', 'c'].fill(7, 1, 2) // ['a', 7, 'c']
8.entries(),keys() 和 values()与for..of配合遍历数组
for (let index of ['a', 'b'].keys()) { console.log(index); } // 0 // 1 for (let elem of ['a', 'b'].values()) { console.log(elem); } // 'a' // 'b' for (let [index, elem] of ['a', 'b'].entries()) { console.log(index, elem); } // 0 "a" // 1 "b"
9.includes()方法表示某个数组是否包含给定的值。
[1, 2, 3].includes(2) // true [1, 2, 3].includes(4) // false [1, 2, NaN].includes(NaN) // true
10.flat()方法用于拉平多维数组(默认拉平1层) flatMap()
方法对原数组的每个成员执行一个函数,然后对返回值组成的数组执行flat()
方法。
[1, 2, [3, [4, 5]]].flat() // [1, 2, 3, [4, 5]] [1, 2, [3, [4, 5]]].flat(2) // [1, 2, 3, 4, 5]
// 相当于 [[2, 4], [3, 6], [4, 8]].flat() [2, 3, 4].flatMap((x) => [x, x * 2]) // [2, 4, 3, 6, 4, 8] //只能展开一层 // 相当于 [[[2]], [[4]], [[6]], [[8]]].flat() [1, 2, 3, 4].flatMap(x => [[x * 2]]) // [[2], [4], [6], [8]]
11.数组中处理空位[,,]的规则非常不统一,应避免出现空位。
三、对象
1.属性与函数的简写形式
function f(x, y) { return {x, y}; } // 等同于 function f(x, y) { return {x: x, y: y}; } f(1, 2) // Object {x: 1, y: 2} const o = { method() { return "Hello!"; } }; // 等同于 const o = { method: function() { return "Hello!"; } };
2.属性名表达式。ES6 允许字面量定义对象时,用表达式作为对象的属性名,即把表达式放在方括号内。
let propKey = 'foo'; let obj = { [propKey]: true, ['a' + 'bc']: 123 }; let obj = { ['h' + 'ello']() { return 'hi'; } }; obj.hello() // hi
属性名表达式与简洁表示法,不能同时使用,会报错。
3.函数的name
属性,返回函数名。对象方法也是函数,因此也有name
属性。
const person = { sayName() { console.log('hello!'); }, }; person.sayName.name // "sayName"
4.属性的遍历方法
(1)for...in
循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
(2)Object.keys(obj)
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
(3)Object.getOwnPropertyNames(obj)
返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
(4)Object.getOwnPropertySymbols(obj)
返回一个数组,包含对象自身的所有 Symbol 属性的键名。
(5)Reflect.ownKeys(obj)
返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
5.super关键字,指向当前对象的原型对象。
const proto = { foo: 'hello' }; const obj = { foo: 'world', find() { return super.foo; } }; Object.setPrototypeOf(obj, proto); obj.find() // "hello"
目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法。其他方式使用super均会报错。
6.对象的解构赋值,将所有未分配,可遍历的属性分配到指定的对象上。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; x // 1 y // 2 z // { a: 3, b: 4 } //本质上是浅拷贝 let obj = { a: { b: 1 } }; let { ...x } = obj; obj.a.b = 2; x.a.b // 2 //不能复制继承自原型对象的属性 let o1 = { a: 1 }; let o2 = { b: 2 }; o2.__proto__ = o1; let { ...o3 } = o2; o3 // { b: 2 } o3.a // undefined
7.对象的扩展运算符(...
)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
let z = { a: 3, b: 4 }; let n = { ...z }; n // { a: 3, b: 4 } //扩展运算符可以用于合并两个对象。 let ab = { ...a, ...b }; // 等同于 let ab = Object.assign({}, a, b);
如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。
如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。
四、对象的拓展方法
1.Object.is()比较两个值是否严格相等
Object.is('foo', 'foo') // true Object.is({}, {}) // false +0 === -0 //true NaN === NaN // false Object.is(+0, -0) // false Object.is(NaN, NaN) // true
2.Object.assign()方法用于对象的合并
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
方法实行的是浅拷贝
const obj1 = {a: {b: 1}}; const obj2 = Object.assign({}, obj1); obj1.a.b = 2; obj2.a.b // 2
//Object.assign可以用来处理数组,但是会把数组视为对象。 Object.assign([1, 2, 3], [4, 5]) // [4, 5, 3]
3.Object.getOwnPropertyDescriptor()
方法会返回某个对象属性的描述对象(descriptor)
const obj = { foo: 123, get bar() { return 'abc' } }; Object.getOwnPropertyDescriptors(obj) // { foo: // { value: 123, // writable: true, // enumerable: true, // configurable: true }, // bar: // { get: [Function: get bar], // set: undefined, // enumerable: true, // configurable: true } }
4.__proto__属性(前后各两个下划线),用来读取或设置当前对象的prototype
对象(浏览器必须部署这个属性,其他运行环境不一定需要部署)
Object.setPrototypeOf
方法的作用与__proto__
相同,用来设置一个对象的prototype
对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。
Object.getPrototypeOf用于读取一个对象的原型对象。
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
5.Object.keys(),Object.values(),Object.entries()
Object.keys
方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
Object.values
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
//如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组。 Object.values('foo') // ['f', 'o', 'o'] //由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。所以,Object.values会返回空数组。 Object.values(42) // [] Object.values(true) // [
Object.entries()
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
Object.fromEntries()
方法是Object.entries()
的逆操作,用于将一个键值对数组转为对象。
const obj = { foo: 'bar', baz: 42 }; Object.entries(obj) // [ ["foo", "bar"], ["baz", 42] ] Object.fromEntries([ ['foo', 'bar'], ['baz', 42] ]) // { foo: "bar", baz: 42 }
Object.values Object.entries会忽略Symbol 属性
Object.entries({ [Symbol()]: 123, foo: 'abc' }); // [ [ 'foo', 'abc' ] ]