constructor与__proto__个人理解及作用

proto

1、js中,除去null外任何对象内部都会自带proto属性
2、proto指向的是构造出自身对象的函数的prototype
3、因为prototype指向的是一个原型对象,所以prototype也拥有自己的proto

上一章说到如果对象去访问自身属性方法时,如果自身没有,则会上它的原型对象找,其js内部原理也就是通过proto属性实现的。当访问自身没有的属性或方法时,因为基于每个对象都有一个proto内部属性原理,而它保存的是自身原型对象的引用,所以它会沿着proto找到自己的原型,如果原型对象上也没有则返回undefined

Zoo.prototype.fierce = 'tiger'
function Zoo() {
    this.lovely = 'panda'
}
var zoo = new Zoo()
console.log(zoo.fierce)   // tiger

这里写图片描述
这是在控制台打印的zoo实例,可以看到自己本生的lovely属性,而fierce属性则存在于proto指向的原型对象中,因为原型也是一个对象,所以它自己也有一个proto属性,它指向的是Object的原型

这里写图片描述
整体展开下来就是这样,因为Object是所有对象的“祖宗”,所以它自己原型上的proto指向的null(火狐控制台因此也没有罗列出来)这也正是为什么 typeof(null) 为”object”的原因,因为null被当做了空对象的占位符。所以这里我们能衍生出一条链式关系的访问顺序:“当访问自身属性或方法时,如果没有,则会通过自身的proto找到自身的原型,如果原型上没有,则找到原型对象的原型。。。一直找到Object.prototype终止”。所以上面代码的第一条,我们改成:

Object.prototype.fierce = 'tiger'

同样能访问到fierce属性。像这种通过原型衍生出的链式关系,称为原型链


constructor

constructor是 js 原型对象上的一个内部属性(仅存在于函数的 prototype 指向的对象中),与proto有点类似,constructor保存了构造出自身对象的那个函数的引用。

function Zoo() {}
var zoo = new Zoo()
console.log(zoo.constructor)  // function Zoo()

这里写图片描述
控制台通过proto访问到zoo原型上的constructor方法,返回了创建此对象的Zoo函数

使用constructor需要注意的地方,看例子:

Person.prototype = {
    skinColor: 'yellowSkin',
    say: function() {
    console.log(this.name)
    }
}

function Person(name, age) {
    this.name = name;
    this.age = age;
}

var person = new Person('li', 19)
console.log(person.constructor)   // function Object()

按照上面的定义,“应该打印出function Person()”,而这里却不是,回到之前我们给一个构造函数添加原型属性或方法时,都是直接 ×××.prototype.×× = ××,每添加一条,就要写上重复的代码。其实提倡以我现在写的方法,至少不用写上重复部分,让人一目了然,代码不显得臃肿,但正因为这么写,导致Person的原型被重新赋值(Person.prototype = {} 等同于 Person.prototype = new Object()),所以person.constructor实际上是访问的Object()实例原型上的constructor,打印出的肯定是function Object()

解决办法:
手动更改原型上constructor属性保存的引用

将上面的代码更改为:

Person.prototype = {
    constructor: Person,
    skinColor: 'yellowSkin',
    say: function() {
    console.log(this.name)
    }
}

function Person(name, age) {
    this.name = name;
    this.age = age;
  //this.constructor: Person   写在构造函数上也可以,但为了严谨尽量写在原型上
}

var person = new Person('li', 19)
console.log(person.constructor)   // function Person()

通过原型链实现js的“继承”

上面只是简单的讲述了一下两个属性的个人理解,但他们功能远不止这些,用的最多的所属通过原型链实现js的继承。要知道js作为一种弱类型语言,它不像其它后端语言拥有“类”一说(这里抛开ES6),想实现类似继承也变得比较繁琐了。言归正传,上面例子让我们知道原型的指向是可以更改的,那如果让一个构造函数的原型更改为另一个构造函数的实例,不就实现了“继承”吗

Person.prototype = {
    constructor: Person,
    skinColor: 'yellowSkin',
    say: function() {
    console.log(this.name)
    }
}

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.arr = [1, 2]
}

Student.prototype = new Person('星儿子')  // 构造函数的原型赋值为另一个构造函数的实例
function Student(){}

var student1 = new Student()
var student2 = new Student()

console.log(student1.name)   // "星儿子"
console.log(student2.name)   // "星儿子"
console.log(student1.say())  // "星儿子"

student1.arr.push(3)
console.log(student1.arr)  // [1, 2, 3]
console.log(student2.arr)  // [1, 2, 3]

可以看到,这里Student的两个实例所访问的属性和方法都是从原型获取的,它们访问的是用一个原型。单独通过原型实现继承是有弊端的

1.无法给父类传参数,即便传了,子类继承的也是死参数,没有独立的属性
2.实例去更改”继承”过来的引用值时,实际上是更改的它原型上的arr,导致所有实例的arr都被更改


通过call方法“继承”

构造继承:在子函数中执行父函数,通过call()或者apply()方法,更改父函数中的this指向,从而实现new出来的实例拥有父函数里的属性和方法。还是上面的例子

Person.prototype = {
    constructor: Person,
    skinColor: 'yellowSkin',
    say: function() {
    console.log(this.name)
    }
}

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.arr = [1, 2]
}

function Student(name, age, wages){
    this.wages = wages;
    Person.call(this, name, age);
    this.printWages = function() {
        return this.wages
    }
}

var student1 = new Student("星儿子", '25', '12k')
var student2 = new Student("阿尿", '26')

console.log(student1.name)   // "星儿子"
console.log(student2.name)   // "阿尿"
console.log(student1.printWages())  // "12k"
console.log(student1.say())  // TypeError: student1.say is not a function

student1.arr.push(3)
console.log(student1.arr)  // [1, 2, 3]
console.log(student2.arr)  // [1, 2]

这里写图片描述
构造继承使得每个实例都有自己独立的属性和方法,这就比单独用原型继承强多了,但是单独用构造继承也是有弊端的

1.父函数原型上的属性或方法无法访问到
2.每构造出一个子函数实例,父函数里的所有方法都被拷贝到了实例中,导致大量公用方法重复


组合继承

组合继承融入了原型链继承与构造继承各自的优点,我们将公共的的属性和方法写入父函数的原型中,将实例特有的属性写入构造继承。同样是上面的代码,我就不重复写了,只需要将子类原型赋值成父类的实例就可以了

    Student.prototype = new Person()

这里写图片描述
通过控制台能看到,两个实例沿着原型链都能找到父函数原型上的属性和方法。但这种继承还有一个小小的弊端

实例化一次,父函数被调用了两次(原型继承调一次,构造继承调一次)


“圣杯模式”继承

听到这个高大上的名字,大家也能猜到这一定是最完美的继承方式了。如何实现的呢?我们可以定义一个普通函数F作中间成,将这个F的原型指向父函数的原型,然后再将子函数的原型指向F的实例,有点绕。。。看例子:

Person.prototype.name = '星儿子'
function Person(){}

function F(){}    // 中间层函数
F.prototype = Person.prototype  // F的原型指向父函数的原型
Student.prototype = new F()  // 将子函数原型指向 F 函数实例,现在子函数的原型指向的一个空对象
Student.prototype.constructor = Student  // 给空对象里添加constructor属性,并指向自己

function Student(){}
var student = new Student()

这种写法就只调了构造继承的那一次,最后,我们将这种继承模式封装一下

var inherit = (function() {
    var F = function() {};  // 将中间层作为必包
    return function(Target, Origin) {  // 返回一个函数
        F.prototype = Origin.prototype;  // F原型指向原始函数
        Target.prototype = new F();   // 目标函数指向F函数的空实例
        Target.prototype.constructor = Target;   // 给空对象添加 constructor 属性
        Target.prototype.uber = Origin.prototype;  // 记录Target函数真正继承自谁(可选)
    }
}())

这里将上面例子直接复制下来使用一下:

// 父函数原型
Person.prototype = {
    constructor: Person,
    skinColor: 'yellowSkin',
    say: function() {
    console.log(this.name)
    }
}

// 父函数
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.arr = [1, 2]
}

// 子函数
function Student(name, age, wages){
    this.wages = wages;
    Person.call(this, name, age);
    this.printWages = function() {
        return this.wages
    }
}

// Student.prototype = new Person();  之前写法
inherit(Student, Person)     //  调用“圣杯函数”

var student1 = new Student("星儿子", '25', '12k')
var student2 = new Student("阿尿", '26')

console.log(student1.name)   // "星儿子"
console.log(student2.name)   // "阿尿"
console.log(student1.printWages())  // "12k"
console.log(student1.say())  // "星儿子"

student1.arr.push(3)
console.log(student1.arr)  // [1, 2, 3]
console.log(student2.arr)  // [1, 2]

控制台打印两个实例:
这里写图片描述


继承这一块我也就知道这么点东西了,不用整得很复杂,能满足需求就够了。最后还是以上面的例子写一些指向上的判断题,如果说你知道答案并能缕清它的由来,那么说明原型链这一块已经掌握的不错了,至少今后碰到这类问题不会出错,或许还能应付下面试

var inherit = (function() {
    var F = function() {};  // 将中间层作为必包
    return function(Target, Origin) {  // 返回一个函数
        F.prototype = Origin.prototype;  // F原型指向原始函数
        Target.prototype = new F();   // 目标函数指向F函数的空实例
        Target.prototype.constructor = Target;   // 给空对象添加 constructor 属性
        Target.prototype.uber = Origin.prototype;  // 记录Target函数真正继承自谁(可选)
    }
}())


// 父函数原型
Person.prototype = {
    constructor: Person,
    skinColor: 'yellowSkin',
    say: function() {
    console.log(this.name)
    }
}

// 父函数
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.arr = [1, 2]
}

// 子函数
function Student(name, age, wages){
    this.wages = wages;
    Person.call(this, name, age);
    this.printWages = function() {
        return this.wages
    }
}

inherit(Student, Person)
var student1 = new Student("星儿子", '25', '12k')

// 下面在控制台输出的答案全都为 true
student1.__proto__ === Student.prototype
student1.__proto__.__proto__ === Person.prototype
student1.__proto__.__proto__.__proto__ === Object.prototype
student1.__proto__.__proto__.__proto__.__proto__ === null

student1.__proto__.constructor === Student
student1.__proto__.constructor.__proto__ === Function.prototype
student1.__proto__.constructor.__proto__.__proto__ === Object.prototype
student1.__proto__.constructor.__proto__.__proto__.__proto__ === null


猜你喜欢

转载自blog.csdn.net/qq_42395080/article/details/82051637