关于构造函数、原型、原型链进阶版

本文借鉴于此,我只是在此基础上进行学习,总结

一、构造函数

(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,这就是原型链
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值