這篇文章主要講解原型的查找、變動、判斷和刪除,附帶着對原型的做用方式作一下回顧。前端
instanceof
instanceof
運算符用於檢測構造函數的 prototype
屬性是否出如今某個實例對象的原型鏈上。面試
即經過下面的操做來判斷:瀏覽器
object.__proto__ === Constructor.prototype ?
object.__proto__.__proto__ === Constructor.prototype ?
object.__proto__.__proto__....__proto__ === Constructor.prototype
複製代碼
當左邊的值是 null
時,會中止查找,返回 false
。閉包
實際是檢測 Constructor.prototype
是否存在於參數 object
的原型鏈上。app
用法:ide
object instanceof Constructor
複製代碼
看看下面的例子:函數
// 定義構造函數
function C(){}
function D(){}
var o = new C();
o instanceof C; // true,由於 Object.getPrototypeOf(o) === C.prototype
o instanceof D; // false,由於 D.prototype 不在 o 的原型鏈上
o instanceof Object; // true,由於 Object.prototype.isPrototypeOf(o) 返回 true
C.prototype instanceof Object // true,同上
C.prototype = {};
var o2 = new C();
o2 instanceof C; // true
o instanceof C; // false,C.prototype 指向了一個空對象,這個空對象不在 o 的原型鏈上.
D.prototype = new C(); // 繼承
var o3 = new D();
o3 instanceof D; // true
o3 instanceof C; // true 由於 C.prototype 如今在 o3 的原型鏈上
複製代碼
須要注意的是 Constructor.prototype
可能會因爲人爲的改動,致使在改動以前實例化的對象在改動以後的判斷返回 false
。 C.prototype = {};
直接更改了構造函數的原型對象的指向,因此後面再次執行 o instanceof C;
會返回 false
。佈局
再看看下面一組例子,演示 String
Date
對象都屬於 Object
類型。測試
var simpleStr = "This is a simple string";
var myString = new String();
var newStr = new String("String created with constructor");
var myDate = new Date();
var myObj = {};
var myNonObj = Object.create(null);
simpleStr instanceof String; // 返回 false, 檢查原型鏈會找到 undefined
myString instanceof String; // 返回 true
newStr instanceof String; // 返回 true
myString instanceof Object; // 返回 true
myObj instanceof Object; // 返回 true, 儘管原型沒有定義
({}) instanceof Object; // 返回 true, 同上
myNonObj instanceof Object; // 返回 false, 一種建立非 Object 實例的對象的方法
myString instanceof Date; //返回 false
myDate instanceof Date; // 返回 true
myDate instanceof Object; // 返回 true
myDate instanceof String; // 返回 false
複製代碼
instanceof
模擬實現ui
function simulateInstanceOf(left, right) {
if (right === null || right === undefined) {
throw new TypeError(`Right-hand side of ' instanceof ' is not an object`)
}
const rightPrototype = right.prototype
left = Object.getPrototypeOf(left)
while (left !== null) {
if (left === rightPrototype) return true
left = Object.getPrototypeOf(left)
}
return false
}
複製代碼
Symbol.hasInstance
Symbol.hasInstance
用於判斷某對象是否爲某構造器的實例。所以你能夠用它自定義 instanceof
操做符在某個類上的行爲。
class MyArray {
static [Symbol.hasInstance](instance) {
// instance 是左邊的參數
return Array.isArray(instance);
}
}
console.log([] instanceof MyArray); // true
複製代碼
Object.prototype.isPrototypeOf()
prototypeObj.isPrototypeOf(object)
isPrototypeOf()
方法用於測試一個對象是否存在於另外一個對象的原型鏈上。
function Foo() {}
function Bar() {}
function Baz() {}
Bar.prototype = Object.create(Foo.prototype);
Baz.prototype = Object.create(Bar.prototype);
var baz = new Baz();
console.log(Baz.prototype.isPrototypeOf(baz)); // true
console.log(Bar.prototype.isPrototypeOf(baz)); // true
console.log(Foo.prototype.isPrototypeOf(baz)); // true
console.log(Object.prototype.isPrototypeOf(baz)); // true
複製代碼
Object.getPrototypeOf
Object.getPrototypeOf(object)
Object.getPrototypeOf()
方法返回指定對象的原型(內部 [[Prototype]]
屬性的值)。若是沒有繼承屬性,則返回 null
。
var proto = {};
var obj = Object.create(proto);
Object.getPrototypeOf(obj) === proto; // true
var reg = /a/;
Object.getPrototypeOf(reg) === RegExp.prototype; // true
複製代碼
注意:Object.getPrototypeOf(Object)
不是 Object.prototype
Object
和 Function
都屬於函數對象,因此它們都是 Function
構造函數的實例,也就是說,會有下面的結果,具體緣由請看個人上一篇文章:
Object instanceof Function
// true
複製代碼
Object.getPrototypeOf( Object )
是把 Object
這一構造函數看做對象,返回的固然是函數對象的原型,也就是 Function.prototype
。
正確的方法是,Object.prototype
是構造出來的對象的原型。
var obj = new Object();
Object.prototype === Object.getPrototypeOf( obj ); // true
Object.prototype === Object.getPrototypeOf( {} ); // true
複製代碼
在 ES5 中,若是參數不是一個對象類型,將拋出一個 TypeError 異常。在 ES6 中,參數會被強制轉換爲一個 Object(使用包裝對象來獲取原型)。
Object.getPrototypeOf('foo');
// TypeError: "foo" is not an object (ES5)
Object.getPrototypeOf('foo');
// String.prototype (ES6)
複製代碼
該方法的模擬實現:
Object.getPrototypeOf = function(obj) {
if (obj === null || obj === undefined) {
throw new Error('Cannot convert undefined or null to object')
}
if (typeof obj === 'boolean' || typeof obj === 'number' || typeof obj === 'string') return Object(obj).__proto__
return obj.__proto__
}
複製代碼
Object.setPrototypeOf
Object.setPrototypeOf(obj, prototype)
Object.setPrototypeOf()
方法設置一個指定的對象的原型 ( 即, 內部 [[Prototype]]
屬性)到另外一個對象或 null
。
若是 prototype
參數不是一個對象或者 null
(例如,數字,字符串,boolean
,或者 undefined
),則會報錯。該方法將 obj
的 [[Prototype]]
修改成新的值。
對於 Object.prototype.__proto__
,它被認爲是修改對象原型更合適的方法。
該方法的模擬實現:
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
複製代碼
Object.create
Object.create(proto[, propertiesObject])
propertiesObject
對應 Object.defineProperties()
的第二個參數,表示給新建立的對象的屬性設置描述符。
若是 propertiesObject
參數是 null
或非原始包裝對象,則拋出一個 TypeError
異常。
Object.create()
方法建立一個新對象,使用現有的對象來提供新建立的對象的 __proto__
。
看下面的例子:
const person = {
isHuman: false,
printIntroduction: function () {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
}
};
const me = Object.create(person);
me.name = "Matthew"; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // inherited properties can be overwritten
me.printIntroduction();
// expected output: "My name is Matthew. Am I human? true"
複製代碼
上面的操做和咱們實例化一個新對象很相似。
下面咱們使用 Object.create()
實現繼承,Object.create()
用來構建原型鏈,使用構造函數給實例附加本身的屬性:
// Shape - 父類(superclass)
function Shape() {
this.x = 0;
this.y = 0;
}
// 父類添加原型方法
Shape.prototype.move = function(x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};
// Rectangle - 子類(subclass)
function Rectangle() {
// 讓子類的實例也擁有父類的構造函數中的附加的屬性
Shape.call(this); // call super constructor.
}
// 子類繼承父類
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
var rect = new Rectangle();
console.log('Is rect an instance of Rectangle?', rect instanceof Rectangle); // true
console.log('Is rect an instance of Shape?', rect instanceof Shape); // true
rect.move(1, 1); // Outputs, 'Shape moved.'
複製代碼
關於 Object.create
的 propertyObject
參數
若是不指定對應的屬性描述符,則默認都是 false
。描述符有如下幾個:
enumerable
可枚舉,默認 false
configurable
可刪除,默認 false
writable
可賦值,默認 false
value
屬性的值看下面的例子:
var 0;
o = Object.create(Object.prototype, {
name: {
value: 'lxfriday', // 其餘屬性描述符都是 false
},
age: {
value: 100,
enumerable: true, // 除了可枚舉,其餘描述符都是 false
}
})
複製代碼
從上面的結果能夠看出,描述符默認都是 false
,不可枚舉的屬性也沒法經過 ES6 的對象擴展進行淺複製。
Object.create
的模擬實現:
Object.create = function(proto, propertiesObject) {
const res = {}
// proto 只能爲 null 或者 type 爲 object 的數據類型
if (!(proto === null || typeof proto === 'object')) {
throw new TypeError('Object prototype may only be an Object or null')
}
Object.setPrototypeOf(res, proto)
if (propertiesObject === null) {
throw new TypeError('Cannot convert undefined or null to object')
}
if (propertiesObject) {
Object.defineProperties(res, propertiesObject)
}
return res
}
複製代碼
Object.assign
Object.assign(target, ...sources)
方法用於將全部可枚舉屬性的值從一個或多個源對象複製到目標對象。它將返回目標對象。它屬於淺拷貝,只會複製引用。
若是目標對象中的屬性具備相同的鍵,則屬性將被源對象中的屬性覆蓋。後面的源對象的屬性將相似地覆蓋前面的源對象的屬性。
Object.assign
方法只會拷貝源對象自身的而且可枚舉的屬性到目標對象。該方法使用源對象的 [[Get]]
和目標對象的 [[Set]]
,因此它會調用相關 getter
和 setter
。若是合併源包含 getter
,這可能使其不適合將新屬性合併到原型中。
String
類型和 Symbol
類型的屬性都會被拷貝。
當拷貝的中途出錯時,已經拷貝的值沒法 rollback,也就是說可能存在只拷貝部分值的狀況。
Object.assign
不會在那些 source
對象值爲 null
或 undefined
的時候拋出錯誤。
const o1 = { a: 1, b: 1, c: 1 };
const o2 = { b: 2, c: 2 };
const o3 = { c: 3 };
const obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
複製代碼
拷貝 symbol
類型的屬性
const o1 = { a: 1 };
const o2 = { [Symbol('foo')]: 2 };
const obj = Object.assign({}, o1, o2);
console.log(obj); // { a : 1, [Symbol("foo")]: 2 }
Object.getOwnPropertySymbols(obj); // [Symbol(foo)]
複製代碼
繼承屬性和不可枚舉屬性是不能拷貝的
const obj = Object.create({foo: 1}, { // foo 是個繼承屬性。
bar: {
value: 2 // bar 是個不可枚舉屬性。
},
baz: {
value: 3,
enumerable: true // baz 是個自身可枚舉屬性。
}
});
const copy = Object.assign({}, obj);
console.log(copy); // { baz: 3 }
複製代碼
原始類型會被包裝爲對象
const v1 = "abc";
const v2 = true;
const v3 = 10;
const v4 = Symbol("foo")
const obj = Object.assign({}, v1, null, v2, undefined, v3, v4);
// 原始類型會被包裝,null 和 undefined 會被忽略。
// 注意,只有字符串的包裝對象纔可能有自身可枚舉屬性。
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
複製代碼
異常會打斷後續拷貝任務
const target = Object.defineProperty({}, "foo", {
value: 1,
writable: false
}); // target 的 foo 屬性是個只讀屬性。
Object.assign(target, {bar: 2}, {foo2: 3, foo: 3, foo3: 3}, {baz: 4});
// TypeError: "foo" is read-only
// 注意這個異常是在拷貝第二個源對象的第二個屬性時發生的。
console.log(target.bar); // 2,說明第一個源對象拷貝成功了。
console.log(target.foo2); // 3,說明第二個源對象的第一個屬性也拷貝成功了。
console.log(target.foo); // 1,只讀屬性不能被覆蓋,因此第二個源對象的第二個屬性拷貝失敗了。
console.log(target.foo3); // undefined,異常以後 assign 方法就退出了,第三個屬性是不會被拷貝到的。
console.log(target.baz); // undefined,第三個源對象更是不會被拷貝到的。
複製代碼
拷貝訪問器
訪問器是一個函數, Object.assign
拷貝的時候會直接調用 getter
函數。
const obj = {
foo: 1,
get bar() {
return 2;
}
};
let copy = Object.assign({}, obj);
console.log(copy); // { foo: 1, bar: 2 } copy.bar的值來自obj.bar的getter函數的返回值
// 下面這個函數會拷貝全部自有屬性的屬性描述符
function completeAssign(target, ...sources) {
sources.forEach(source => {
let descriptors = Object.keys(source).reduce((descriptors, key) => {
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
return descriptors;
}, {});
// Object.assign 默認也會拷貝可枚舉的Symbols
Object.getOwnPropertySymbols(source).forEach(sym => {
let descriptor = Object.getOwnPropertyDescriptor(source, sym);
if (descriptor.enumerable) {
descriptors[sym] = descriptor;
}
});
Object.defineProperties(target, descriptors);
});
return target;
}
copy = completeAssign({}, obj);
console.log(copy);
// { foo:1, get bar() { return 2 } }
複製代碼
Object.assign
的模擬實現:
function assign(target, sources) {
if (target === null || target === undefined) {
throw new TypeError('Cannot convert undefined or null to object')
}
const targetType = typeof target
const to = targetType === 'object' ? target : Object(target)
for (let i = 1; i < arguments.length; i++) {
const source = arguments[i]
const sourceType = typeof source
if (sourceType === 'object' || sourceType === 'string') {
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
to[key] = source[key]
}
}
}
}
return to
}
Object.defineProperty(Object, 'assign', {
value: assign,
writable: true,
configurable: true,
enumerable: false,
})
複製代碼
new Constructor()
new constructor[([arguments])]
咱們使用 new
能夠創造一個指向構造函數原型的對象,而且讓該對象擁有構造函數中指定的屬性。
new
操做符的行爲有如下三點須要特別注意,當代碼 new Foo(...)
執行時,會發生如下事情:
Foo.prototype
的新對象被建立;Foo
,並將 this
綁定到新建立的對象。new Foo
等同於 new Foo()
,也就是沒有指定參數列表,Foo
不帶任何參數調用的狀況。new
表達式的結果。若是構造函數沒有顯式返回一個對象,則使用步驟1建立的對象。(通常狀況下,構造函數不返回值,可是用戶能夠選擇主動返回對象,來覆蓋正常的對象建立步驟)上面的第三步,返回 null
時,雖然 typeof
是 object
,可是仍然會返回步驟一中建立的對象。
new
的模擬實現:
function monitorNew(constructor, args) {
// 提取構造函數和參數,arguments 被處理以後不包含構造函數
const Constructor = Array.prototype.shift.call(arguments)
// 建立新對象,並把新對象的原型指向 Constructor.prototype
const target = Object.create(Constructor.prototype)
// 把新對象做爲上下文,執行 Constructor
const ret = Constructor.apply(target, arguments)
// 構造函數返回 null,則返回建立的新對象
if (ret === null) return target
// 若是是對象則返回指定的對象,不然返回建立的對象
return typeof ret === 'object' ? ret : target
}
複製代碼
往期精彩:
關注公衆號能夠看更多哦。
感謝閱讀,歡迎關注個人公衆號 雲影 sky,帶你解讀前端技術,掌握最本質的技能。關注公衆號能夠拉你進討論羣,有任何問題都會回覆。