包含手写
new
以及 手写Object.create
(≖ᴗ≖)✧
起因
在整理原型这部分知识时,发现了两种修改原型的写法,好像并不影响测试结果
function Fruit() {
this.type = 'fruit'
}
function Apple() {
this.name = 'apple'
}
// Apple.prototype = new Fruit()
Apple.prototype = Object.create(Fruit.prototype);
var a = new Apple()
console.log(a instanceof Apple) // true
console.log(a instanceof Fruit) // true
new
运算符的结果返回的是对象,Object.create()
方法返回的也是 它俩的__proto__
指向都是原有原型对象Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的proto new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例 —— MDN
那… 这俩啥区别啊???
过程
1. 找茬
function Fruit() {
this.type = 'fruit'
}
function AppleNew() {
this.name = 'apple'
}
function AppleCreate() {
this.name = 'apple'
}
AppleNew.prototype = new Fruit();
AppleCreate.prototype = Object.create(Fruit.prototype);
console.log(AppleNew.prototype, AppleCreate.prototype)
console.log(AppleNew, AppleCreate)
2. 溯源两个都是 Fruit
的实例,但使用 Object.create
新建的对象,把属性值弄丢了
new
干了点啥呢?回顾笔记:
function myNew() {
// 1. 新建对象
// var obj = new Object();
// 2. 连接原型链
// 2.1 取得构造函数
var Constructor = [].shift.call(arguments);
// 2.2 为新建对象的 [[prototype]] 赋值
// obj.__proto__ = Constructor.prototype;
// Object.setPrototypeOf(obj, Constructor.prototype);
var obj = Object.create(Constructor.prototype === null ? Object.prototype : Constructor.prototype);
// 3. 调用构造函数,并改变 this 指向
// var ret = Constructor.apply(obj, arguments);
// 4. 如构造函数返回结果为对象,则直接返回,否则返回新建对象,忽略返回值
// return typeof ret === 'object' ? ret : obj;
return obj;
}
警告: 由于现代 JavaScript 引擎优化属性访问所带来的特性的关系,更改对象的 [[Prototype]]在各个浏览器和 JavaScript 引擎上都是一个很慢的操作。其在更改继承的性能上的影响是微妙而又广泛的,这不仅仅限于 obj.proto = … 语句上的时间花费,而且可能会延伸到任何代码,那些可以访问任何[[Prototype]]已被更改的对象的代码。如果你关心性能,你应该避免设置一个对象的 [[Prototype]]。相反,你应该使用 Object.create()来创建带有你想要的[[Prototype]]的新对象。使用 Object.create
进行关联原型的操作,是因为 MDN 的一段建议:
从 new
的内部原理可以发现,new
中的某一步就完成了 Object.create(Constructor.prototype)
的作用,两者的差别主要在于后续 this
的指向,使用 Object.create
创建的实例 this
没有指向实例。
再来扒一扒 Object.create
的实现,翻阅 MDN 后,可以窥见一二,改写后,代码如下:
function myCreate(proto, propertiesObject) {
// 1. 验证 proto 为构造函数/对象(/null)
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an Object: ' + proto);
// } else if (proto === null) {
// 在 ES5 中 Object.create支持设置为[[Prototype]]为null,但因为那些ECMAScript5以前版本限制,此 polyfill 无法支持该特性。
// throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument.");
}
// 2. 内部新建一构造函数
function F() { }
// 3. 将构造函数的原型指向传入的原型对象
F.prototype = proto;
// 4. 新建一构造函数的实例
var obj = new F();
// 5. 如为 null, 需要再将实例的原型指向 null。否则,使用 new 新建实例时,会将原型指向 Object.prototype
if (proto === null) {
obj.__proto__ = null;
}
// 6. 为实例添加属性
if (propertiesObject) {
Object.defineProperties(obj, propertiesObject)
}
return obj;
};
总结:用图表示它们的关系,就会变成:前文最开始的,感觉两种写法好像没有区别的例子也就自然解释了。 根据图示,两种方法创建的实例都可以在原型链上找到 Fruit.prototype
两者的区别主要在于:
new | Object.create | |
接受形式 | 构造函数 | 构造函数、普通对象,null |
继承性 | 有 | 无(不会继承原有构造函数中的属性) 对象:则会继承原对象的属性 |
其它 | 可以借 Object.create 实现没有原型的空对象 |