ES6---對象新增方法
1.Object.is()
ES5 比較兩個值是否相等,只有兩個運算符:相等運算符()和 嚴格相等運算符(=)。它們都有缺點,前者會自動轉換數據類型,后者的NaN不等於自身,以及+0等於-0。JavaScript 缺乏一種運算,在所有環境中,只要兩個值是一樣的,它們就應該相等。
ES6 提出“Same-value equality”(同值相等)算法,用來解決這個問題。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
ES5 可以通過下面的代碼,部署Object.is
Object.defineProperty(Object, 'is', {
value: function(x, y) {
if (x === y) {
// 針對+0 不等於 -0的情況
return x !== 0 || 1 / x === 1 / y;
}
// 針對NaN的情況
return x !== x && y !== y;
},
configurable: true,
enumerable: false,
writable: true
});
2.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()方法的第一個參數是目標對象,后面的參數都是源對象。
注意,如果目標對象與源對象有同名屬性,或多個源對象有同名屬性,則后面的屬性會覆蓋前面的屬性。
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
如果只有一個參數,Object.assign()會直接返回該參數。
const obj = {a: 1};
Object.assign(obj) === obj // true
如果該參數不是對象,則會先轉成對象,然后返回。
typeof Object.assign(2) // "object"
由於undefined和null無法轉成對象,所以如果它們作為參數,就會報錯。
Object.assign(undefined) // 報錯
Object.assign(null) // 報錯
其他類型的值(即數值、字符串和布爾值)不在首參數,也不會報錯。但是,除了字符串會以數組形式,拷貝入目標對象,其他值都不會產生效果。
const v1 = 'abc';
const v2 = true;
const v3 = 10;
const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
上面代碼中,v1、v2、v3分別是字符串、布爾值和數值,結果只有字符串合入目標對象(以字符數組的形式),數值和布爾值都會被忽略。這是因為只有字符串的包裝對象,會產生可枚舉屬性。
Object.assign()拷貝的屬性是有限制的,只拷貝源對象的自身屬性(不拷貝繼承屬性),也不拷貝不可枚舉的屬性(enumerable: false)。
注意點:
1.Object.assign()方法實行的是淺拷貝,而不是深拷貝。也就是說,如果源對象某個屬性的值是對象,那么目標對象拷貝得到的是這個對象的引用。
2.對於這種嵌套的對象,一旦遇到同名屬性,Object.assign()的處理方法是替換,而不是添加。
3.Object.assign()可以用來處理數組,但是會把數組視為對象。
4.Object.assign()只能進行值的復制,如果要復制的值是一個取值函數,那么將求值后再復制。
常見用途:
1.為對象添加屬性
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}
上面方法通過Object.assign()方法,將x屬性和y屬性添加到Point類的對象實例。
2.為對象添加方法
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
});
// 等同於下面的寫法
SomeClass.prototype.someMethod = function (arg1, arg2) {
···
};
SomeClass.prototype.anotherMethod = function () {
···
};
上面代碼使用了對象屬性的簡潔表示法,直接將兩個函數放在大括號中,再使用assign()方法添加到SomeClass.prototype之中。
4.克隆對象
function clone(origin) {
return Object.assign({}, origin);
}
上面代碼將原始對象拷貝到一個空對象,就得到了原始對象的克隆。
不過,采用這種方法克隆,只能克隆原始對象自身的值,不能克隆它繼承的值。如果想要保持繼承鏈,可以采用下面的代碼.
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto), origin);
}
//The Object.create() method creates a new object, using an existing object as the prototype of the newly created object.
4.合並多個對象
將多個對象合並到某個對象。
const merge =
(target, ...sources) => Object.assign(target, ...sources);
如果希望合並后返回一個新對象,可以改寫上面函數,對一個空對象合並。
const merge =
(...sources) => Object.assign({}, ...sources);
5.為屬性指定默認值
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};
function processContent(options) {
options = Object.assign({}, DEFAULTS, options);
console.log(options);
// ...
}
上面代碼中,DEFAULTS對象是默認值,options對象是用戶提供的參數。Object.assign()方法將DEFAULTS和options合並成一個新對象,如果兩者有同名屬性,則options的屬性值會覆蓋DEFAULTS的屬性值。
注意,由於存在淺拷貝的問題,DEFAULTS對象和options對象的所有屬性的值,最好都是簡單類型,不要指向另一個對象。否則,DEFAULTS對象的該屬性很可能不起作用。
3.Object.getOwnPropertyDescriptors()
ES5 的Object.getOwnPropertyDescriptor()方法會返回某個對象屬性的描述對象(descriptor)。ES2017 引入了Object.getOwnPropertyDescriptors()方法,返回指定對象所有自身屬性(非繼承屬性)的描述對象。
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 } }
上面代碼中,Object.getOwnPropertyDescriptors()方法返回一個對象,所有原對象的屬性名都是該對象的屬性名,對應的屬性值就是該屬性的描述對象。
該方法的實現非常容易。
function getOwnPropertyDescriptors(obj) {
const result = {};
for (let key of Reflect.ownKeys(obj)) {
result[key] = Object.getOwnPropertyDescriptor(obj, key);
}
return result;
}
//靜態方法 Reflect.ownKeys() 返回一個由目標對象自身的屬性鍵組成的數組。
該方法的引入目的,主要是為了解決Object.assign()無法正確拷貝get屬性和set屬性的問題
const source = {
set foo(value) {
console.log(value);
}
};
const target1 = {};
Object.assign(target1, source);
Object.getOwnPropertyDescriptor(target1, 'foo')
// { value: undefined,
// writable: true,
// enumerable: true,
// configurable: true }
上面代碼中,source對象的foo屬性的值是一個賦值函數,Object.assign方法將這個屬性拷貝給target1對象,結果該屬性的值變成了undefined。這是因為Object.assign方法總是拷貝一個屬性的值,而不會拷貝它背后的賦值方法或取值方法。
這時,Object.getOwnPropertyDescriptors()方法配合Object.defineProperties()方法,就可以實現正確拷貝。
4.__proto__屬性,Object.setPrototypeOf(),Object.getPrototypeOf()
1.__proto__屬性
__proto__屬性(前后各兩個下划線),用來讀取或設置當前對象的原型對象(prototype)。目前,所有瀏覽器(包括 IE11)都部署了這個屬性。
// es5 的寫法
const obj = {
method: function() { ... }
};
obj.__proto__ = someOtherObj;
// es6 的寫法
var obj = Object.create(someOtherObj);
obj.method = function() { ... };
該屬性沒有寫入 ES6 的正文,而是寫入了附錄,原因是__proto__前后的雙下划線,說明它本質上是一個內部屬性,而不是一個正式的對外的 API,只是由於瀏覽器廣泛支持,才被加入了 ES6。標准明確規定,只有瀏覽器必須部署這個屬性,其他運行環境不一定需要部署,而且新的代碼最好認為這個屬性是不存在的。因此,無論從語義的角度,還是從兼容性的角度,都不要使用這個屬性,而是使用下面的Object.setPrototypeOf()(寫操作)、Object.getPrototypeOf()(讀操作)、Object.create()(生成操作)代替。
實現上,__proto__調用的是Object.prototype.proto,具體實現如下。
Object.defineProperty(Object.prototype, '__proto__', {
get() {
let _thisObj = Object(this);
return Object.getPrototypeOf(_thisObj);
},
set(proto) {
if (this === undefined || this === null) {
throw new TypeError();
}
if (!isObject(this)) {
return undefined;
}
if (!isObject(proto)) {
return undefined;
}
let status = Reflect.setPrototypeOf(this, proto);
if (!status) {
throw new TypeError();
}
},
});
function isObject(value) {
return Object(value) === value;
}
2.Object.setPrototypeOf()
Object.setPrototypeOf方法的作用與__proto__相同,用來設置一個對象的原型對象(prototype),返回參數對象本身。它是 ES6 正式推薦的設置原型對象的方法。
// 格式
Object.setPrototypeOf(object, prototype) //設置object的原型對象是prototype,了解即可
// 用法
const o = Object.setPrototypeOf({}, null);
3.Object.getPrototypeOf()
該方法與Object.setPrototypeOf方法配套,用於讀取一個對象的原型對象。
function Rectangle() {
// ...
}
const rec = new Rectangle();
Object.getPrototypeOf(rec) === Rectangle.prototype
// true
Object.setPrototypeOf(rec, Object.prototype);
Object.getPrototypeOf(rec) === Rectangle.prototype
// false