1、前言javascript
介绍构造函数,原型,原型链。好比说常常会被问道:symbol是否是构造函数;constructor属性是否只读;prototype、[[Prototype]]和__proto__的区别;什么是原型链?等等问题java
2、构造函数git
一、什么构造函数github
构造函数就是经过new关键词生成实例的函数。浏览器
js的构造函数和其余语言不同,通常规范都是首字母大写。安全
首先咱们来看一下这个栗子:ide
// saucxs function Parent(age) { this.age = age; } var p = new Parent(30); console.log(p); //见下图 console.log(p.constructor); // ƒ Parent(age){this.age = age;} p.constructor === Parent; // true p.constructor === Object; // false
这就是一个典型的构造函数,构造函数自己也是个函数,与普通区别不大,主要区别就是:构造函数使用new生成实例,直接调用就是普通函数。函数
二、constructor属性性能
返回建立实例对象的Object构造函数的引用。此属性的值对函数自己的引用,而不是一个包含函数名称的字符串。学习
全部对象都会从它的原型上继承一个constructor属性:
var o = {}; o.constructor === Object; // true var o = new Object; o.constructor === Object; // true var a = []; a.constructor === Array; // true var a = new Array; a.constructor === Array // true var n = new Number(3); n.constructor === Number; // true
那么普通函数建立的实例有没有constructor属性呢?
// saucxs // 普通函数 function parent2(age) { this.age = age; } var p2 = parent2(50); console.log(p2); // undefined // 普通函数 function parent3(age) { return { age: age } } var p3 = parent3(50); console.log(p3.constructor); //ƒ Object() { [native code] } p3.constructor === parent3; // false p3.constructor === Object; // true
上面代码说明:
(1)普通函数在内部有return操做的就有constructor属性,没有return的没有constructor属性;
(2)有constructor属性的普通函数的constructor属性值不是普通函数自己,是Object。
三、symbol是构造函数吗?
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(),若是要生成实例直接使用Symbol()就能够的。
// saucxs new Symbol(123); // Symbol is not a constructor Symbol(123); // Symbol(123)
虽然Symbol是基本数据类型,可是Symbol(1234)实例能够获取constructor属性值。
// saucxs var sym = Symbol(123); console.log( sym ); // Symbol(123) console.log( sym.constructor ); // ƒ Symbol() { [native code] } sym.constructor === Symbol; //true sym.constructor === Object; //false
这里的constructor属性来自哪里?实际上是Symbol原型上的,默认为Symbol函数。
四、constructor的值是只读的吗?
回答:若是是引用类型的constructor属性值是能够修改的,若是是基本类型的就是只读的。
引用类型的状况,修改这个很好理解,好比原型链继承的方案中,就是对constructor从新赋值的修正。
// saucxs 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);
对于基本类型来讲是只读的,好比:1, "saucxs", true, Symbol, null, undefined。null和undefined也是没有constructor属性的。
// saucxs 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属性并不安全。
3、原型
3.1 prototype属性
每个对象都拥有一个原型对象,对象以其原型为模板,从原型集成方法和属性,这些属相和方法都在对象的构造器函数的prototype属性上,而不是对象实例自己上。
上图发现:
一、Parent对象有一个原型对象Parent.prototype,原型对象上有两个属性,分别为:constructor和__proto__,其中__proto__已被弃用。
二、构造函数Parent有一个指向原型的指针constructor;原型Parent.prototype有一个指向构造函数的指针Parent.prototype.constrcutor,其实就是一个循环引用。
3.2 __proto__属性
上图中能够看到Parent原型(Parent.prototype)上有一个__proto__属性,这是一个访问器属性(即getter函数和setter函数)。做用:经过__proto__能够访问到对象的内部[[Prototype]](一个对象或者null)
`__proto__` 发音 dunder proto,最早被 Firefox使用,后来在 ES6 被列为 Javascript 的标准内建属性。
`[[Prototype]]` 是对象的一个内部属性,外部代码没法直接访问。
// saucxs function Parent(){}; var p = new Parent(); console.log(p); console.log(Parent.prototype);
一、p.__proto__获取的是对象的原型,__proto__是每个实例上都有的属性;
二、prototype是构造函数的属性;
三、p.__proto__和Parent.prototype指向同一个对象。
// saucxs function Parent() {} var p = new Parent(); p.__proto__ === Parent.prototype // true
因此构造函数Parent,Parent.prototype和p之间的关系,以下图所示:
注意1:`__proto__` 属性在 `ES6` 时才被标准化
以确保 Web 浏览器的兼容性,可是不推荐使用,除了标准化的缘由以外还有性能问题。为了更好的支持,推荐使用 `Object.getPrototypeOf()`。
若是要读取或修改对象的 `[[Prototype]]` 属性,建议使用以下方案,可是此时设置对象的 `[[Prototype]]` 依旧是一个缓慢的操做,若是性能是一个问题,就要避免这种操做。
若是要建立一个新对象,同时继承另外一个对象的 `[[Prototype]]` ,推荐使用 `Object.create()`。
// saucxs function Parent() { age: 50 }; var p = new Parent(); var child = Object.create(p);
这里 `child` 是一个新的空对象,有一个指向对象 p 的指针 `__proto__`。
4、原型链
每个对象拥有一个原型对象,经过__proto__指针指向上一个原型,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层层的,最终指向null。这种关系成为原型链(prototype chain),做用:经过原型链一个对象会拥有定义在其余对象中的属性和方法。
// saucxs 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
// saucxs 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
下图展现原型链运行机制。
5、总结
一、Symbol是基本数据类型,做为构造函数并不完整,由于不支持语法new Symbol(),可是原型上拥有constructor属性,即Symbol.prototype.constructor。
二、引用类型constructor属性值是能够修改的,可是对于基本类型的是只读的,固然null和undefined没有constructor属性。
三、__proto__是每一个实例上都有的属性,prototype是构造函数的属性,这两个不同,可是p.__proto__和Parent.prototype是指向同一个对象。
四、__proto__属性在ES6时被标准化,可是由于性能问题并不推荐使用,推荐使用Object.getPropertyOf()。
五、每一个对象拥有一个原型对象,经过__ptoto_指针指向上一个原型,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层已成的,最终指向null,这就是原型链。
6、参考
一、原型对象
二、Objcet.prototype.constructor
四、Symbol
五、原型
文章首发地址(sau交流学习社区)