难搞的原型家族

152 阅读4分钟

[[Prototype]]:原型

在聊原型大家族之前,我们需要了解一下其中的重要成员[[Prototype]].在JavaScript中,每一个对象有一个特殊的[[Prototype]]内置属性,实际上这是对于其他对象的引用。几乎所有的对象在创建[[Prototype]]时都会被赋予一个非空的值。实际上,[[Prototype]]也是一个对象,里面也具有属性和方法。

在JavaScript中,我们这样定义: 原型是函数function对象的一个属性,它定义了构造函数制造出来的对象的公共祖先 通过构造函数产生的对象,可以继承到原型的方法和属性。

看到这里,是不是会想到类的继承?确实,二者有异曲同工之妙,但是class类的继承需要使用关键字extends和super关键字来实现,而原型是一个对象,它是对象的引用,自身也是一个对象,二者要区分开。

共有属性

利用原型的特点和概念,我们可以提取共有的属性

Person.prototype.say = function(){

    console.log('hello');

}
function Person(){

    this.name = '小明'

}

var person = new Person()

var person1 = new Person()

console.log(Person.name);//小明

console.log(person1.name);//小明

//它定义了构造函数制造出来的对象的公共祖先 方法say()

person.say()

person2.say()

定义一个构造函数Person,name属性赋值为‘小明’,在其原型对象上添加方法say(),则只要是Person定义出来的对象,都会具有say这个方法,并通过对象.say()的方式来调用

增添删改

我们不仅可以对对象进行增添删改,还能对原型对象进行增添删改

Person.prototype.lastName = '王'

function Person(name){

    this.name = name

}

person.lastName = '李';      //相当于给Person类创建的对象person加了一个属性

console.log(person.lastName); //李

console.log(Person.prototype.lastName); //王

Person.prototype.lastName = '李'  //原型得到修改

console.log(Person.prototype.lastName); //李

delete person.lastName //删除person对象的属性

console.log(person.lastName); //李

delete Person.prototype.lastName //删除原型属性

console.log(Person.prototype.lastName);//undefined

上述代码我们定义了一个构造函数,定义属性name,需要传入参数。同时我们在对象的原型对象上也增添一个属性lastName,将其赋值为‘王’。我们定义一个对象person,为person对象增添一个属性lastName,注意,这里相当于是给Person类创建的对象person加了一个属性,而后打印属性lastName,很显然是 ‘李’。之后修改原型上的lastName的值为 ‘李’,打印结果便是 ‘李’。然后删除了person对象的属性,但是打印结果仍然是 ‘李’,这是为何呢?结果在下方揭晓,这里留个悬念。最后删除原型的属性,打印结果很显然为undefined。

显式原型

每个对象都有隐式原型,就是__proto__

隐式原型

每个对象也具有显式原型,就是prototype

Person.prototype.name = '王'

            function Person(){

            }

             var Person = new Person()

             console.log(Person);//{} 是由Person{}创建的

             console.log(Person.__proto__);

             console.log(Person.__proto__.name);
             
             var obj = {

                 name:'李'

             }

             Person.__proto__ = obj

             console.log(Person.__proto__); //

             console.log(Person.__proto__.name);//wn  前往obj去寻找

1.jpg

可以看到,全局中定义了一个Person构造函数,构造出Person对象,打印Person.__ proto__,我们发现它就是一个对象,其中就包含了name,以及它自身的构造函数,里面同样包含了prototype属性。而利用__proto__属性,可以改变对象的引用,让其指向新的对象obj,从而前往obj去寻找lastname,输出相应的值。

值得注意的是,构造函数的显式原型是个对象,对象的隐式原型Person.__ proto__ 也是一个对象

构造函数 与 对象

从上面的例子中,我们就容易看出原型prototype和__proto__存在着某种莫大的联系:对象的隐式原型等于构造函数的显式原型.用代码来说,就是person.__ proto__ == Person.prototype

Person.prototype.say = function(){

    console.log('aaaaa');

}
function Person(name){

    this.name = name
    
  return this

}

let p = new Person('袁')

p.say( )

为什么p可以拿到say ()这个方法呢?实际上在构造函数创建对象是,构造函数对对象内部做了以下操作

var this = {

    name: name
     
    __proto__:Person.prototype

}

return this

构造函数三部曲

1 创建this对象

2 挂上对象该有的属性值,name属性传值,对象的隐式原型绑定构造函数显示原型

3 返回this对象

原型链

在这里,我们又要引出一个概念,原型链:在原型上加一个原型,再加一个原型,把原型连成链,访问顺序按照链的顺序执行,叫原型链.

所有对象都有自己的原型对象,而原型对象也是对象,所以它也有自己的原型,原型同样也具有原型对象,依次嵌套,形成了一个原型链,也就是说,对象最终都会回溯到Object.prototype上 ,而Object.prototype的原型 对象就是null

从上述代码中来说,如果自身没有该属性,那么JavaScript就会去原型对象中找,若是有,则将其赋值,否则,就要去原型的原型对象中,又是这样一种嵌套,一直沿着原型链,最终回溯到Object.prototype上,若是仍然没有,那么结果就是undefined。

小白的见解,希望可以帮到大家,谢谢!