这篇文章主要讲解原型的查找、变动、判断和删除,附带着对原型的做用方式作一下回顾。前端
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,带你解读前端技术,掌握最本质的技能。关注公众号能够拉你进讨论群,有任何问题都会回复。