ECMAScript 6 入门笔记(四)函数,对象

时间:2022-08-27 14:36:56

ECMAScript 6 入门原文 – 阮一峰
ECMAScript 6 入门笔记(一)let,const,解构
ECMAScript 6 入门笔记(二)String,RegExp
ECMAScript 6 入门笔记(三)数值,Array
ECMAScript 6 入门笔记(四)函数,对象

ECMAScript 6 入门笔记(五)异步promise,Generator,async
ECMAScript 6 入门笔记(六)Class
ECMAScript 6 入门笔记(七)Symbol,set和map
ECMAScript 6 入门笔记(八)Proxy,Reflect

函数

函数参数的默认值

function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}

var p = new Point();
p // { x: 0, y: 0 }

let x = 99;
function foo(p = x + 1) {
console.log(p);
}

foo() // 100

与解构赋值默认值结合使用
参数默认值可以与解构赋值的默认值,结合起来使用。

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

rest

function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10

扩展运算符
扩展运算符(spread)是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

console.log(...[1, 2, 3])
// 1 2 3

console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5

扩展运算符的应用 § ⇧
(1)合并数组

// ES6
[1, 2, ...more]
var arr1 =
['a', 'b'];
var arr2 =
['c'];
var arr3 =
['d', 'e'];
// ES6的合并数组
[...arr1, ...arr2, ...arr3]
//
[ 'a', 'b', 'c', 'd', 'e' ]

(2)与解构赋值结合
扩展运算符可以与解构赋值结合起来,用于生成数组。

// ES5
a = list[0], rest = list.slice(1);
// ES6
[a, ...rest] = list;

const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]

const [first, ...rest] = [];
first // undefined
rest // []:

const [first, ...rest] = ["foo"];
first // "foo"
rest // []

严格模式
《ECMAScript 2016标准》做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。

// 报错
function doSomething(a, b = a) {
'use strict';
// code
}

// 报错
const doSomething = function ({a, b}) {
'use strict';
// code
};

// 报错
const doSomething = (...a) => {
'use strict';
// code
};

const obj = {
// 报错
doSomething({a, b}) {
'use strict';
// code
}
};

两种方法可以规避这种限制。第一种是设定全局性的严格模式,这是合法的。

'use strict';
function doSomething(a, b = a) {
// code
}
const doSomething = (function () {
'use strict';
return function(value = 42) {
return value;
};
}());

箭头函数

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;
};

var getTempItem = id => ({ id: id, name: "Temp" });

使用注意点

**箭头函数有几个使用注意点。
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作Generator函数。**

(function() {
return [
(() => this.x).bind({ x: 'inner' })(); //this不会变换
];
}).call({ x: 'outer' });
// ['outer']

绑定 this
箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(call、apply、bind)。但是,箭头函数并不适用于所有场合,所以ES7提出了“函数绑定”(function bind)运算符,用来取代call、apply、bind调用。虽然该语法还是ES7的一个提案,但是Babel转码器已经支持。

函数绑定运算符是并排的两个双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。

foo::bar;
// 等同于
bar.bind(foo);

foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);

const hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
return obj::hasOwnProperty(key);
}

由于双冒号运算符返回的还是原对象,因此可以采用链式写法。

let { find, html } = jake;

document.querySelectorAll("div.myClass")
::find("p")
::html("hahaha");

尾调用优化
什么是尾调用?
尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。

function f(x){
return g(x);
}

function f(x) {
if (x > 0) {
return m(x)
}
return n(x);
}

对象

ES6允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

var foo = 'bar';
var baz = {foo};
baz // {foo: "bar"}

// 等同于
var baz = {foo: foo};

除了属性简写,方法也可以简写。

var o = {
method() {
return "Hello!";
}
};
// 等同于

var o = {
method: function() {
return "Hello!";
}
};

例子

var birth = '2000/01/01';

var Person = {

name: '张三',

//等同于birth: birth
birth,

// 等同于hello: function ()...
hello() { console.log('我的名字是', this.name); }

};

Object.is()

Object.is('foo', 'foo')
// true
Object.is({}, {})
// false
不同之处只有两个:一是+0不等于-0,二是NaN等于自身。

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

Object.assign
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

var target = { a: 1 };
var source1 = { b: 2 };
var source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

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

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

注意点
Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。

var obj1 = {a: {b: 1}};
var obj2 = Object.assign({}, obj1);

obj1.a.b = 2;
obj2.a.b // 2

Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]

属性的可枚举性
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。

let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }

属性的遍历
ES6一共有5种方法可以遍历对象的属性。

(1)for…in

for…in循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)。

(2)Object.keys(obj)

Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)。

(3)Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)。

(4)Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有Symbol属性。

(5)Reflect.ownKeys(obj)

Reflect.ownKeys返回一个数组,包含对象自身的所有属性,不管是属性名是Symbol或字符串,也不管是否可枚举。

以上的5种方法遍历对象的属性,都遵守同样的属性遍历的次序规则。

首先遍历所有属性名为数值的属性,按照数字排序。
其次遍历所有属性名为字符串的属性,按照生成时间排序。
最后遍历所有属性名为Symbol值的属性,按照生成时间排序。
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// [‘2’, ‘10’, ‘b’, ‘a’, Symbol()]
上面代码中,Reflect.ownKeys方法返回一个数组,包含了参数对象的所有属性。这个数组的属性次序是这样的,首先是数值属性2和10,其次是字符串属性b和a,最后是Symbol属性。

_proto_属性,Object.setPrototypeOf(),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

Object.getPrototypeOf()

function Rectangle() {
// ...
}

var rec = new Rectangle();

Object.getPrototypeOf(rec) === Rectangle.prototype
// true

Object.setPrototypeOf(rec, Object.prototype);
Object.getPrototypeOf(rec) === Rectangle.prototype
// false

Object.keys(),Object.values(),Object.entries()

let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };
for (let [key, value] of entries(obj)) {
console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}

var obj = { foo: 'bar', baz: 42 };
Object.values(obj)

Object.entries
Object.entries方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。

var obj = { foo: ‘bar’, baz: 42 };
Object.entries(obj)
// [ [“foo”, “bar”], [“baz”, 42] ]

const obj = {
foo: 123,
get bar() { return ‘abc’ }
};

Object.getOwnPropertyDescriptors(obj)
// { foo:
// { value: 123,
// writable: true,
// enumerable: true,
// configurable: true },
// bar:
// { get: [Function: bar],
// set: undefined,
// enumerable: true,
// configurable: true } }

上面代码中,Object.getOwnPropertyDescriptors方法返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象。