一篇搞定令人疑惑的原型和原型链

2,508 阅读6分钟


本文已参与「新人创作礼」活动,一起开启掘金创作之路。

学习的旅程就是不断学习接受新的知识,不久前我们一起学习过作用域和作用域链,今天我们来探索探索原型和原型链。

原型

原型的定义

原型([[Prototype]]) 是javascript中函数function对象的一个特殊内置属性,其实就是对于其他对象的引用,它定义了构造函数制造出来的对象的公共祖先,通过构造函数产生的对象,可以继承到该原型的属性和方法。原型也是对象。

接下来我们通过一个🌰来直观的感受一下函数的原型:

function Person(){
    this.name = "wn";
}

var person = new Person();
console.log(person.name); // 'wn'

从结果可以直观的看出当我们构造的Person函数里面有name这个属性那么打印时会直接打印出来
Person里的name属性的值“wn”,但是当我们的Person函数里面没有name属性,再给person的原
型上加上一个name属性,再次打印时会打印出来什么呢?

image.png

没错,和你想的结果一样,会打印出来Person原型上的name属性的值,这样是不是直观理解了“原型定义了构造函数制造出来的对象的公共祖先,通过构造函数产生的对象,可以继承到该原型的属性和方法。”这个概念。

那么如何来理解原型也是一个对象呢?
Car.prototype = {
    Carname : 'BMW',
    height : 1400,
    lang : 4900
}
function Car(color, owner) {
    this.color = color;
    this.owner = owner;
}

var car = new Car('red', 'xuan');

console.log(car.Carname); // 'BMW'
console.log(car.height); // 1400
console.log(car.lang); // 4900

当我们给函数的原型加上属性时可以用花括号{}直接添加多个属性,这样是不是就理解了为什么我
们说原型也是一个对象了。

利用原型的特点和概念,可以实现公有属性。

显式原型与隐式原型

这个时候我们要注意一下,以上我们所谈的原型是 prototype. 是函数的原型,我们也称之为(显式原型),那么就会有人问既然是显式原型那么相对应的应该也有隐式的原型吧,对的你猜的没错;__proto__ (我们一般会读成:底杠proto)是对象的原型,我们也称之为(隐式原型)

我们再来看个🌰,你就能大概悟了什么是显式原型什么是隐式原型:
Person.prototype.name = 'wn'
function Person(){

}
var person = new Person()

var obj = {
    name: '蜗牛'
}

console.log(person.__proto__);

可能就会有同学心里犯嘀咕了,你这也没说明隐式原型是什么就直接打印这个实例对象的隐式原
型,那我怎么知道结果是什么呢?

// { name:'wn'} 

输出结果就是构造函数显式原型。

此时我们就能得出一个结论:实例对象的隐式原型 === 构造函数的显式原型。

本质区别:对象的原型(__proto__)与构造函数的原型(prototype)属性之间性质不一样。前者是每个实例上都有的属性,后者是构造函数的属性。

原型的基础操作

当我们理清楚了原型的概念以及特点时,我们来一段原型的基础操作之“增、删、改、查。”

(在上面讲解原型的概念时我们就已经学会了增加原型和查看原型),不信你回头细品一品,所以
接下来我们主要讲解一下原型的删和改的操作。

先来个原型删除的🌰:
Person.prototype.lastName = '张'
function Person(name){
    this.name = name;
}
var Person = new Person('三');
delete person.lastName;
//对象属性的删除使用
console.log(person.lastName);

image.png

诶,你不是用delete删除了Person原型里的lastName属性吗?这又是一个小细节,原型上的属性只能通过原型来删除,所以我们应该delete Person.prototype.lastName,通过原型操作来删除原型上的属性。

image.png

一些逻辑强一些的同学就能立马反应过来,那么原型的属性修改操作应该也是在原型上进行的吧,
没错就是这样的。

image.png

image.png

通过这两个运行结果我们能清楚的看到,当我们直接person.lastName= '李'时相当于给构造函数里直接添加了一个lastName属性,值为“李”,但是我们能直观地看到原型里的lastName属性,值仍然为“张”,而当我们person.prototype.lastName= '李'就修改了原型里的lastName属性,值为“李”。

我们得出一个结论:原型的增删改:不能通过实例对象修改,只能通过原型对象修改。 同时在上面的结果中有一个constructor:它为了让构造函数构造出来的所有的对象都能找到自己的构造器。

原型链

原型链的定义

在原型上加一个原型,再加一个原型...把原型连成链,访问顺序和这个链的顺序一致,与作用域链类似,叫做原型链。

那如何来分析原型链呢,我们一起来分析分析一下这张图:

image.png

我们从上到下来分析一下这张原型链的图,理解之后你会和我一样有一种恍然大悟的感觉;

首先function Foo()(函数Foo的显式原型就是)Foo.prototype(函数Foo的隐式原型)就应该是构造函数Function.prototype

(所有function函数是通过Function函数构造出来的)

Foo.prototype也是一个对象所以它的隐式原型( __ proto __ )就是Object.prototype (实例对象的隐式原型 === 构造函数的显式原型。),.. = new Foo()(构造函数new出来的实例对象的隐式原型就是)Foo.prototype

那么接下来就是重头戏了为什么function Function()Function.prototype会形成环装结构,首先因为Function也是一个函数,函数是一种对象,也有隐式原型属性。既然是函数,那么它一定是被Function创建。所以function Function()的显式原型一定是Function.prototype,我相信这个大家都没什么问题,怎么理解function Function()的隐式原型还是Function.prototype呢?

既然是函数那么就是被Function函数构造出来的,那么我们可以把function Function()看成一个实例对象,实例对象的隐式原型就是构造函数Function的显式原型;而Function.prototype也是一个对象所以它的隐式原型就是Object.prototype

最后由于对象也是被构造函数创建出来的,所以function Object()的隐式原型就是构造函数的显式原型Function.prototype,当然函数对象的显式原型就是Object.prototype,.. = new Object()(构造函数new出来的实例对象的隐式原型就是)Object.prototype; 俗话说万物皆对象,所以Object.prototype就是顶端了,它的隐式原型就指向了null

结语

通过这篇文章的分享我相信你对原型以及原型链都有了一定的了解,最后这个原型链的图值得我们细细品味,希望这篇文章能够帮助到你们理解原型及原型链,文章若发现任何纰漏欢迎评论区批评指正,谢谢!