一、构造函数
(1)啥是构造函数
constructor返回创建实例对象时构造函数的引用,此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串
function Person() {}
var p = new Person()
console.log(p.constructor === Person);//true
console.log(p.constructor === Object);//false
构造函数本身就是一个函数,与普通函数没有任何区别,不过为了规范一般将其首字母大写。
构造函数与普通函数的区别在于:使用new生成的实例的函数就是构造函数,直接调用的就是普通函数
普通函数创建的实例一定没有constructor属性吗? 不一定
function parent2() {}
var p2 = parent2() //普通函数创建的实例
console.log(p2.constructor);//报错,无constructor属性
function parent3(age) {
return {
age : age
}
}
var p3 = parent3(50)
console.log(p3.constructor);//Object,有constructor属性
(2)Symbol是构造函数吗
Symbol是基本的数据类型,但作为构造函数来讲,它并不完整,因为它并不支持语法 new Symbol(),Chrome认为其不是构造函数,如果要生成实例直接使用Symbol()
new Symbol(123)//Symbol is not a constructor
Symbol(123)
虽然是基本的数据类型,但是Symbol(123)实例可以获取constructor属性值
var sym = Symbol(123)
console.log(sym.constructor);//[Function: Symbol]
这里的constructor属性来自哪里?
其实是Symbol原型上的,即Symbol.prototype.constructor返回实例原型的函数,默认为Symbol函数
(3)constructor只读吗
对于引用类型来说,constructor属性值是可以更改的,但是对于基本类型来说是只读的
引用类型:
function Foo() {//构造函数Foo
this.value = 42
}
Foo.prototype = {//Foo的原型对象
method: function() {}
}
function Bar() {} //构造函数Bar
//设置Bar的prototype属性为Foo的实例对象
Bar.prototype = new Foo() //这里将Bar.prototype.constructor的指向由Bar()改为[Function: Object]
Bar.prototype.foo = "Hello World"
console.log(Bar.prototype.constructor);//[Function: Object]
//修正Bar.prototype.constructor为Bar()
Bar.prototype.constructor = Bar//这里代表constructor属性是可修改的
var test = new Bar()
console.log(test);
基本类型(当然null和undefined是没有constructor的):
function Type() {}
var types = [1,"hhh",'true',Symbol(123)]
for(let 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,hhh
// function String() { [native code] },false,true
// function Symbol() { [native code] },false,Symbol(123)
以上代码可知,constructor更改不了,因为创建它们的是只读的原生构造函数
二、原型
javaScript是一种基于原型的语言
每个对象拥有一个原型对象,对象以其原型为模板,从原型继承方法和属性,这些属性和方法定义在对象的构造器函数的prototype属性上,而非对象实例本身
从这张图中可以发现,Parent对象中有一个原型对象Parent.prototype,其上有两个属性,分别是constructor和__proto__,其中__proto__已经被弃用
构造函数Parent有一个指向原型的指针,原型Parent.prototype有一个指向构造函数的指针Parent.prototype.constructor,如上图所示,就是一个循环引用
假设p为构造函数的实例,则构造函数Parent、Parent.prototype和p的关系如下图:
__proto__ : 这是一个访问器属性(即getter函数和setter函数),通过它可以访问到对象内部的[[Prototype]](一个对象或null)
[[Prototype]] 是对象的一个内部属性,外部代码无法直接访问
注意点:
__proto__
属性在ES6时才被标准化,以确保Web浏览器的兼容性,但是不推荐使用,除了标准化外还有性能的原因,为了更好的支持外,推荐使用Object.getPrototypeOf()
如果要读取或修改对象的[[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)
console.log(Object.getPrototypeOf(child));//Parent()
console.log(child.__proto__);//Parent()
这里的child是一个空对象,有一个指向对象p的指针__proto__
优化实现new(手动实现new,可参考)
正如上面介绍的不建议使用__proto__,所以我们使用Object.create()来模拟实现,优化后的代码如下:
function myNew() {
let Con = [].shift.call(arguments)
let obj = Object.create(Con.prototype)
let ret = Con.apply(obj,arguments)
return ret instanceof Object ? ret : obj
}
三、原型链
每个对象拥有一个原型对象,通过__proto__指针指向上一个原型,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层的,最终指向null。这种关系成为原型链,通过原型链一个对象会拥有定义在其他对象中的属性和方法
function Parent(age) {
this.age = age
}
var p = new Parent(50)
console.log(p.constructor);//true
那么是否由以上的代码可知,p实例存在constructor属性?并不是
由此图我们可以看到实例对象本身没有constructor属性,是通过原型链向上查找__proto__,最终查找到constructor属性,该属性指向Parent
function Parent(age) {
this.age = age
}
var p = new Parent(50)
console.log(p);//Parent { age: 50 }
console.log(p.__proto__ === Parent.prototype);//true
console.log(p.__proto__.__proto__ === Object.prototype);//true
console.log(p.__proto__.__proto__.__proto__);//null
下图中展示了原型链的运行机制:
四、小结
1、Symbol 作为构造函数来说并不完整,因为不支持语法 new Symbol(),但其原型上拥有 constructor 属性,即 Symbol.prototype.constructor。
2、引用类型 constructor 属性值是可以修改的,但是对于基本类型来说是只读的,当然 null 和 undefined 没有 constructor 属性。
3、__proto__ 是每个实例上都有的属性,prototype 是构造函数的属性,在实例上并不存在,所以这两个并不一样,但 p.__proto__ 和 Parent.prototype 指向同一个对象。
4、__proto__ 属性在 ES6 时被标准化,但因为性能问题并不推荐使用,推荐使用 Object.getPrototypeOf()。
5、每个对象拥有一个原型对象,通过 __proto__ 指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null,这就是原型链