2022年,在学一遍JS原型、原型链

·  阅读 4679
2022年,在学一遍JS原型、原型链

全面了解原型、原型链

原型和继承是js中非常重要的两大概念。深入了解原型,也是学好继承的前提。 本文将从 普通对象、引用类型对象、内置对象三个方向全面了解原型和原型链。

普通对象的原型:

我们都知道,访问一个对象实例上的属性和方法时,首先在实例本身查找。 如果没有找到,会到实例对象的隐式属性__proto__上查找,就是该实例的构造函数的原型对象。如果还没有,会继续访问原型对象的原型,直到Object.prototype.__proto__(null)为止。

这就是所有对象都含有toString方法的原因,因为toString是Object.prototype上面的方法。所有对象沿着原型链最后都会找到Object.prototype。

function Person(name) {
  this.name = name 
}
Person.prototype.getName = function() {
  console.log(this.name)
}
let person = new Person('Jack')
//在实例上直接找到了name属性。
console.log(person.name) 
//在实例本身没有找到getName方法,在person.__proto__ 上找到了getName方法
person.getName() 
//在实例本身没有找到getName方法,在person.__proto__ 上也没有,继续查找person.__proto__.__proto__ 也就是Object.prototype 找到了toString方法
console.log(person.toString())
复制代码

沿着__proto__查找属性的这一链条,就是我们说的原型链。那么在js中,实例是怎样访问到构造函数原型对象上面的方法?实例又是怎么通过__proto__沿着原型链向上追溯的?

我们知道在js中:

  • 每个函数都存在prototype属性,指向函数的原型对象。js中可以使用构造函数实例化来创建一个对象,构造函数含有prototype属性,会指向构造函数的原型对象。
  • 每个对象都存在__proto__属性,来描述对象的原型。并且对象的原型会指向“实例该对象的构造函数的prototype”,即 instance.__proto__ === construction.prototype
  • 函数的原型对象也是对象,所有函数的原型对象的__proto__会指向Object.prototype

上图说话:

原型链.png

构造函数的原型对象上有一个constructor属性,会指向构造函数本身。

控制台打印:

console.log(person.__proto__)
console.log(Person.prototype)
console.log(person.__proto__ === Person.prototype)
console.log(Person.prototype.constructor === Person)
复制代码

wecom-temp-421a317d1959d4bc9c7dbee60fd01af0.png

引用类型对象的原型:

上面的例子,我们了解了普通对象和构造函数的原型原型链。在js中,万物皆对象,引用类型的值(数组,对象,函数)同样也是对象,是对象就有__proto__属性,会指向对象的原型,对应的构造函数的原型对象。

数组,对象,函数等引用类型对应的构造函数就是js中内置的一些函数Array、Object、Function。可以通过这些构造函数去创建引用类型的值。只是在平时开发中,我们习惯使用字面量的方式声明一个数组或者对象。但是无论用哪种方式去创建一个引用类型,他们的原型链都是一样的。

let arr = new Array(1,2,3)
let obj = new Object({name: 'Jack'})
let fn = new Function('a', 'b', 'return a + b');
alert(fn(1, 2) ); // 3
复制代码

根据之前说的到普通对象的__proto__属性会指向其构造函数的prototype,我们可以知道,这些引用类型的对象跟其构造函数的关系:

console.log(arr.__proto__ === Array.prototype) //true
console.log(obj.__proto__ === Object.prototype) //true
console.log(fn.__proto__ === Function.prototype) //true
复制代码

这也是为什么,所有函数都有call、apply、bind方法。因为这些方法是在Function.prototype上面,函数调用call,自身没有找到方法,沿着原型链,访问fn.__proto__,找到call方法。

Function.prototype.hasOwnProperty('call') // true
复制代码

内置函数对象的原型:

我们通过数组、对象等引用类型的值,知道了他们的原型和一些内置构造函数的关系。

那么内置的这些函数Object、Function、Number、String、Array、Boolean,他们都是函数,那么同样也是对象,他们的__proto__又指向何处呢?

我们知道,普通函数可以通过new Function()创建,所以普通函数的__proto__指向Function.prototype。那么内置的这些函数同样属于函数,也是通过new Function()创建出来的函数对象,同理,他们的__proto__也指向Function.prototype

// 下面打印都是true
console.log(Object.__proto__ === Function.prototype)
console.log(Array.__proto__ === Function.prototype)
console.log(Number.__proto__ === Function.prototype)
console.log(String.__proto__ === Function.prototype)
console.log(Boolean.__proto__ === Function.prototype)
console.log(Date.__proto__ === Function.prototype)
复制代码

不管是普通函数,还是内置的构造函数,他们的__proto__都指向Function.prototype。那Function自己的__proto__指向哪里呢?

Function是所有函数的构造函数,但是Function也是函数,那Function的__proto__应该指向他的构造函数的prototype。然而实际并不是想的那样。

打印发现Function的__proto__ 指向Function的prototype。 这就引发了js中的哲学思考,先有鸡还是先有蛋?或者说是Function就是js中的神,他不仅可以构造出其他函数,同时自己创造了自己?

wecom-temp-63bfd91aec73a66910ec1b6a47e93fa3.png

总结:

  • 访问对象的一个属性,先在自身查找,如果没有,会访问对象的__proto__,沿着原型链查找,一直找到Object.prototype.__proto__
  • 每个函数都有prototype属性,会指向函数的原型对象。
  • 所有函数的原型对象的__proto__,会指向Object.prototype
  • 每个对象都有__proto__属性,会指向该对象的构造函数的原型对象。
  • 引用类型的值,他们的__proto__,会指向对应构造函数的prototype。
  • 一些内置的构造函数,如Object、Array、String、Boolean,他们的__proto__属性,指向Function.prototype
  • Function.__proto__ 指向 Function.prototype
  • 原型链的尽头是Object.prototype.__proto__,为null。

下面附上网上很全的一张原型链图解。如果这张图看明白了,那就明白了原型原型链,同时知道了js中所有对象的关系。

wecom-temp-d797662a983961acd72115d957a212dc.png

如果对原型、原型链已经清楚,但是对JS中的继承不太了解的同学,可以查看作者的另一篇文章: # 2022年,在学一遍JS继承、class类,其中对JS常见的继承方式,es6的class继承做了讲解。

分类:
前端
分类:
前端
收藏成功!
已添加到「」, 点击更改