更新:谢谢你们的支持,最近折腾了一个博客官网出来,方便你们系统阅读,后续会有更多内容和更多优化,猛戳这里查看前端
------ 如下是正文 ------webpack
前端进阶系列已经到第 5 期啦,本期正式开始原型 Prototype
系列。git
本篇文章重点介绍构造函数、原型和原型链相关知识,若是你还不知道 Symbol
是否是构造函数、constructor
属性是否只读、prototype
、[[Prototype]]
和 __proto__
的区别、什么是原型链,建议你好好阅读本文,但愿对你有所帮助。github
下图是本文的思惟导图,高清思惟导图和更多文章请看个人 Github。web
constructor
返回建立实例对象时构造函数的引用。此属性的值是对函数自己的引用,而不是一个包含函数名称的字符串。面试
// 木易杨
function Parent(age) {
this.age = age;
}
var p = new Parent(50);
p.constructor === Parent; // true
p.constructor === Object; // false
复制代码
构造函数自己就是一个函数,与普通函数没有任何区别,不过为了规范通常将其首字母大写。构造函数和普通函数的区别在于,使用 new
生成实例的函数就是构造函数,直接调用的就是普通函数。算法
那是否是意味着普通函数建立的实例没有 constructor
属性呢?不必定。跨域
// 木易杨
// 普通函数
function parent2(age) {
this.age = age;
}
var p2 = parent2(50);
// undefined
// 普通函数
function parent3(age) {
return {
age: age
}
}
var p3 = parent3(50);
p3.constructor === Object; // true
复制代码
MDN 是这样介绍 Symbol
的浏览器
The
Symbol()
function returns a value of type symbol, has static properties that expose several members of built-in objects, has static methods that expose the global symbol registry, and resembles a built-in object class but is incomplete as a constructor because it does not support the syntax "new Symbol()
".安全
Symbol
是基本数据类型,但做为构造函数来讲它并不完整,由于它不支持语法 new Symbol()
,Chrome 认为其不是构造函数,若是要生成实例直接使用 Symbol()
便可。(来自 MDN)
// 木易杨
new Symbol(123); // Symbol is not a constructor
Symbol(123); // Symbol(123)
复制代码
虽然是基本数据类型,但 Symbol(123)
实例能够获取 constructor
属性值。
// 木易杨
var sym = Symbol(123);
console.log( sym );
// Symbol(123)
console.log( sym.constructor );
// ƒ Symbol() { [native code] }
复制代码
这里的 constructor
属性来自哪里?实际上是 Symbol
原型上的,即 Symbol.prototype.constructor
返回建立实例原型的函数, 默认为 Symbol
函数。
这个得分状况,对于引用类型来讲 constructor
属性值是能够修改的,可是对于基本类型来讲是只读的。
引用类型状况其值可修改这个很好理解,好比原型链继承方案中,就须要对 constructor
从新赋值进行修正。
// 木易杨
function Foo() {
this.value = 42;
}
Foo.prototype = {
method: function() {}
};
function Bar() {}
// 设置 Bar 的 prototype 属性为 Foo 的实例对象
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';
Bar.prototype.constructor === Object;
// true
// 修正 Bar.prototype.constructor 为 Bar 自己
Bar.prototype.constructor = Bar;
var test = new Bar() // 建立 Bar 的一个新实例
console.log(test);
复制代码
对于基本类型来讲是只读的,好比 一、“muyiy”、true、Symbol
,固然 null
和 undefined
是没有 constructor
属性的。
// 木易杨
function Type() { };
var types = [1, "muyiy", true, Symbol(123)];
for(var i = 0; i < types.length; i++) {
types[i].constructor = Type;
types[i] = [ types[i].constructor, types[i] instanceof Type, types[i].toString() ];
};
console.log( types.join("\n") );
// function Number() { [native code] }, false, 1
// function String() { [native code] }, false, muyiy
// function Boolean() { [native code] }, false, true
// function Symbol() { [native code] }, false, Symbol(123)
复制代码
为何呢?由于建立他们的是只读的原生构造函数(native constructors
),这个例子也说明了依赖一个对象的 constructor
属性并不安全。
说到这里就要聊聊 new
的实现了,实现代码以下。
// 木易杨
function create() {
// 一、建立一个空的对象
var obj = new Object(),
// 二、得到构造函数,同时删除 arguments 中第一个参数
Con = [].shift.call(arguments);
// 三、连接到原型,obj 能够访问构造函数原型中的属性
Object.setPrototypeOf(obj, Con.prototype);
// 四、绑定 this 实现继承,obj 能够访问到构造函数中的属性
var ret = Con.apply(obj, arguments);
// 五、优先返回构造函数返回的对象
return ret instanceof Object ? ret : obj;
};
复制代码
以前写过一篇文章解析 new
的模拟实现过程,若是你对实现过程还不了解的话点击阅读。「【进阶3-5期】深度解析 new 原理及模拟实现」
prototype
JavaScript
是一种基于原型的语言 (prototype-based language),这个和 Java
等基于类的语言不同。
每一个对象拥有一个原型对象,对象以其原型为模板,从原型继承方法和属性,这些属性和方法定义在对象的构造器函数的 prototype
属性上,而非对象实例自己。
从上面这张图能够发现,Parent
对象有一个原型对象 Parent.prototype
,其上有两个属性,分别是 constructor
和 __proto__
,其中 __proto__
已被弃用。
构造函数 Parent
有一个指向原型的指针,原型 Parent.prototype
有一个指向构造函数的指针 Parent.prototype.constructor
,如上图所示,其实就是一个循环引用。
__proto__
上图能够看到 Parent 原型( Parent.prototype
)上有 __proto__
属性,这是一个访问器属性(即 getter 函数和 setter 函数),经过它能够访问到对象的内部 [[Prototype]]
(一个对象或 null
)。
__proto__
发音 dunder proto,最早被 Firefox使用,后来在 ES6 被列为 Javascript 的标准内建属性。
[[Prototype]]
是对象的一个内部属性,外部代码没法直接访问。
遵循 ECMAScript 标准,someObject.[[Prototype]] 符号用于指向 someObject 的原型。
这里用 p.__proto__
获取对象的原型,__proto__
是每一个实例上都有的属性,prototype
是构造函数的属性,这两个并不同,但 p.__proto__
和 Parent.prototype
指向同一个对象。
// 木易杨
function Parent() {}
var p = new Parent();
p.__proto__ === Parent.prototype
// true
复制代码
因此构造函数 Parent
、Parent.prototype
和 p
的关系以下图。
__proto__
属性在 ES6
时才被标准化,以确保 Web 浏览器的兼容性,可是不推荐使用,除了标准化的缘由以外还有性能问题。为了更好的支持,推荐使用 Object.getPrototypeOf()
。
经过改变一个对象的
[[Prototype]]
属性来改变和继承属性会对性能形成很是严重的影响,而且性能消耗的时间也不是简单的花费在obj.__proto__ = ...
语句上, 它还会影响到全部继承自该[[Prototype]]
的对象,若是你关心性能,你就不该该修改一个对象的[[Prototype]]
。
若是要读取或修改对象的 [[Prototype]]
属性,建议使用以下方案,可是此时设置对象的 [[Prototype]]
依旧是一个缓慢的操做,若是性能是一个问题,就要避免这种操做。
// 木易杨
// 获取
Object.getPrototypeOf()
Reflect.getPrototypeOf()
// 修改
Object.setPrototypeOf()
Reflect.setPrototypeOf()
复制代码
若是要建立一个新对象,同时继承另外一个对象的 [[Prototype]]
,推荐使用 Object.create()
。
// 木易杨
function Parent() {
age: 50
};
var p = new Parent();
var child = Object.create(p);
复制代码
这里 child
是一个新的空对象,有一个指向对象 p 的指针 __proto__
。
正如上面介绍的不建议使用 __proto__
,因此咱们使用 Object.create()
来模拟实现,优化后的代码以下。
// 木易杨
function create() {
// 一、得到构造函数,同时删除 arguments 中第一个参数
Con = [].shift.call(arguments);
// 二、建立一个空的对象并连接到原型,obj 能够访问构造函数原型中的属性
var obj = Object.create(Con.prototype);
// 三、绑定 this 实现继承,obj 能够访问到构造函数中的属性
var ret = Con.apply(obj, arguments);
// 四、优先返回构造函数返回的对象
return ret instanceof Object ? ret : obj;
};
复制代码
每一个对象拥有一个原型对象,经过 __proto__
指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null
。这种关系被称为原型链 (prototype chain),经过原型链一个对象会拥有定义在其余对象中的属性和方法。
咱们看下面一个例子
// 木易杨
function Parent(age) {
this.age = age;
}
var p = new Parent(50);
p.constructor === Parent; // true
复制代码
这里 p.constructor
指向 Parent
,那是否是意味着 p
实例存在 constructor
属性呢?并非。
咱们打印下 p
值就知道了。
由图能够看到实例对象 p
自己没有 constructor
属性,是经过原型链向上查找 __proto__
,最终查找到 constructor
属性,该属性指向 Parent
。
// 木易杨
function Parent(age) {
this.age = age;
}
var p = new Parent(50);
p; // Parent {age: 50}
p.__proto__ === Parent.prototype; // true
p.__proto__.__proto__ === Object.prototype; // true
p.__proto__.__proto__.__proto__ === null; // true
复制代码
下图展现了原型链的运做机制。
Symbol
做为构造函数来讲并不完整,由于不支持语法 new Symbol()
,但其原型上拥有 constructor
属性,即 Symbol.prototype.constructor
。constructor
属性值是能够修改的,可是对于基本类型来讲是只读的,固然 null
和 undefined
没有 constructor
属性。__proto__
是每一个实例上都有的属性,prototype
是构造函数的属性,这两个并不同,但 p.__proto__
和 Parent.prototype
指向同一个对象。__proto__
属性在 ES6
时被标准化,但由于性能问题并不推荐使用,推荐使用 Object.getPrototypeOf()
。__proto__
指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null
,这就是原型链。进阶系列文章汇总以下,内有优质前端资料,以为不错点个star。
我是木易杨,网易高级前端工程师,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!