likes
comments
collection
share

干货分享- 一文带你搞懂原型与原型链

作者站长头像
站长
· 阅读数 25

一、前言

在js这门语言中,原型和原型链是一个很重要的知识点,只有理解了它们,咱们才能更深入的理解js,今天小编就带大家来聊清楚理解透彻原型和原型链这两个知识点来。

二、构造函数

构造函数和普通函数本质上没有什么区别,只不过是使用new 关键字创建对象的函数,所以被称做构造函数。构造函数的首字母一般是大写,用以区分普通函数。不大写也没关系。

function Car(color, owner) {
   	this.name = "su7;
   	this.lang = 5000;
        this.height=1400;
        this.color=color;
        this.owner=owner;
        this.say = function () { 
        console.log("Hello");
        }
}
let tian = new Car('black', "taintian");
let xaun = new Car('red', "xaunxuan");

三、原型对象

在js中,每一个函数类型的数据,都有一个叫做prototype的属性,这个属性指向的是一个对象,就是原型对象。

干货分享- 一文带你搞懂原型与原型链

对于原型对象来说,它有个constructor属性,指向它的构造函数。

干货分享- 一文带你搞懂原型与原型链 那么这个原型对象有什么用呢?最主要的作用就是用来存放实例对象的公有属性和公有方法。

在上面那个例子中name、lang、height属性和say方法对于所有实例对象来说都一样,放在构造函数里,那每创建一个实例对象,就会重复创建一次相同的属性和方法,浪费内存,没有必要。这时候,如果把这些公有的属性和方法放在原型对象里共享,就会好很多。

Car.prototype.name="su7"
Car.prototype.lang=5000
Car.prototype.height=1400
Car.prototype.say= function () { 
        console.log("Hello");
     }
function Car(color,owner){
    this.color=color
    this.owner=owner
}
let p1 = new Car('black', "taintian");
let p2 = new Car('red', "xaunxuan");
console.log(p1,p2)
p1.say()
p2.say()

可是这里的name和lang、height属性和say方法不是实例对象自己的,为什么可以直接用点运算符访问?这是因为在js中,对象如果在自己的这里找不到对应的属性或者方法,就会查看构造函数的原型对象,如果原型对象上有这个属性或方法,就会返回属性值或调用方法。 所以这里我们可以得出一个结论:构造函数new出来的对象会隐式继承到构造函数原型上的属性。

既然是公共属性,我们是否可以对其进行操作,我们下面可以试试看

p1.name = '保时捷'
console.log(p1.name)
console.log(p2.name)
输出结果
"保时捷"
"su7"

输出结果没毛病,你买了车改你的车标,我买的车的车标肯定不会因此受影响。 我们可以把构造函数Car比作一个大车厂,原型相当于总部,我们私下买了车可以对车进行改装,但是别人买的车肯定还是原来的样子。如果咱们就是想要从车厂买到保时捷的车,咱们应该怎么做呢?直接去总部改

Car.prototype.name = '保时捷'

同样,我们实例对象改不动原型的属性,要改得原型自己改,要删得原型自己删。 也就是说 实例对象可以修改继承到的属性,但是无法修改隐式继承到的属性。

刚刚讲的函数原型对象的属性constructor,这个单词是创建者的意思,每个函数原型(对象)都有一个constructor属性,并且他指向函数自身。大家可以去百度的控制台试试看,当你实例化一个car的时候,再输入这个实例对象的名字,他会返回一个Car{},你可以把他展开,里面会有一个原型,原型也可以展开,里面有个constructor属性,这个属性的内容就是构造函数本身。大家可以去试试看

function Car(){
}
let car =new Car()
console.log(car.constructor);

干货分享- 一文带你搞懂原型与原型链

既然如此,constructor是个属性并且值为构造函数本身,我们是否可以进行更改呢?比如我再来个构造函数Bus,让Car的原型中的constructor值变成Bus这个函数体可以吗?我们下面试试

function Bus(){
}
Car.prototype = {
    constructor: Bus
}
function Car(){
}
let car =new Car()
console.log(car.constructor);

输出结果:

干货分享- 一文带你搞懂原型与原型链 神奇!竟然还能强行修改。

原型对象指的是对一个构造函数实例化之后出现的对象,它也具有一个原型,这个原型我们称之为隐式原型,并且这个原型它能够继承构造函数中显示原型的属性和方法,所以对象的隐式原型===创建它的构造函数的显式原型,并且当你访问这个实例化对象属性的时候,他会先找对象显示具有的属性(也就是构造函数体内的属性),找不到再去对象的隐式原型(-prototype-)的属性,而这个隐式原型就是构造函数的显示原型。隐式原型为了区分显示原型,我们用__proto__表示。 来看下面这个代码理解一下:

Person.prototype.say = function(){
    console.log("hello " + this.name);
}
function Person(name){
    this.name = name;
}
var person1 = new Person("Tom")
person1.say()
console.log(person1.__proto__ === Person.prototype)

干货分享- 一文带你搞懂原型与原型链

我们现在看下这个语句的第一个输出,为什么构造函数原型也能访问到this?原来原型中this也是指向了调用函数的对象,第一个输出结果我们可以理解了。第二个呢?这里就是应证了实例对象的隐式原型等于构造函数的显示原型。

四、原型链

1.显示原型 显示原型就是利用prototype属性查找原型,只是这个是函数类型数据的属性。

2.隐式原型 隐式原型是利用__proto__属性查找原型,这个属性指向当前对象的构造函数的原型对象,这个属性是对象类型数据的属性,所以可以在实例对象上面使用

console.log(p1.__proto__ === Person.prototype); // true
console.log(p2.__proto__ === Person.prototype); // true

根据上面,咱们就可以得出constructor、prototype和__proto__之间的关系了:

干货分享- 一文带你搞懂原型与原型链

既然这个是对象类型的属性,而原型对象也是对象,那么原型对象就也有这个属性,但是原型对象的__proto__又是指向哪呢?

我们来分析一下,既然原型对象也是对象,那我们只要找到对象的构造函数就能知道__proto__的指向了。而js中,对象的构造函数就是Object(),所以对象的原型对象,就是Object.prototype。既然原型对象也是对象,那原型对象的原型对象,就也是Object.prototype。不过Object.prototype这个比较特殊,它没有上一层的原型对象,或者说是它的__proto__指向的是null。

所以上面的关系图可以拓展成下面这种:

干货分享- 一文带你搞懂原型与原型链 到这里,咱们就可以回答之前那个问题了,如果某个对象查找属性,自己和原型对象上都没有,那就会继续往原型对象的原型对象上去找,这个例子里就是Object.prototype,js引擎在查找属性时,会顺着对象的隐式原性向上查找,找不到,则查找隐式原型,一直向上,直到找到null为止,就返回undefined。这种查找关系称为原型链。

可以看出,整个查找过程都是顺着__proto__属性,一步一步往上查找,形成了像链条一样的结构,这个结构,就是原型链。所以,原型链也叫作隐式原型链。

五、函数对象 函数在js中,也算是一种特殊的对象,所以,小伙伴们是不是想问,函数是不是也有一个__proto__属性?答案是肯定的。

console.log(Person.__proto__ === Function.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true
console.log(Function.__proto__ === Function.prototype); // true

于是便有了下面这张更全的图:

干货分享- 一文带你搞懂原型与原型链

六、面试官:所有的对象都有隐式原型吗?

我寻思我终于把原型的底层逻辑给学明白了,你问我个这么简单个问题是什么意思!瞧不起我吗,对象不都是最终指向Object.__proto__。肯定有隐式原型啊,所以肯定是对的啊!

实则答案却是错的,因为null除外!Object.create(null) 没有原型!!

let a = {
    name: 'b'
}
let obj = Object.create(a); // 创建一个新对象,这个对象的原型是a,隐式继承了a的属性
console.log(obj.__proto__);

干货分享- 一文带你搞懂原型与原型链 可以发现obj的隐式原型是我们传入的 a

而当我们传入一个null

let obj1 = Object.create(null);
console.log(obj1.__proto__);

干货分享- 一文带你搞懂原型与原型链

你会发现这种情况obj1没有原型,这也证明了所有对象都有原型 这句话是错误的。

今天的分享就结束了,主要讲了原型和原型链两部分,希望对大家有所帮助。可以点个免费的赞赞嘛,感谢感谢!

转载自:https://juejin.cn/post/7366818154821009459
评论
请登录