_prototype和__proto__

js中原型和继承相关知识

[[prototype]]

原型对象

js中一切皆对象,函数、数组等等都是对象。

对象默认有一个隐藏属性叫做**[[prototype]]**,它要么是null,要么指向另一个对象(原型对象),默认是隐藏的,不可以直接访问。

如果有一个obj对象,我们访问一个里面不存在的属性,那么js再obj中找不到,就会自动去原型对象中找。这叫做“原型继承

__proto__和prototype

为了访问和修改[[prototype]],这里有两个特殊的对象属性

  • obj.__proto__ 每一个对象都默认有这个属性,称为隐式原型
  • fun.prototype 每一个函数都默认有这个属性,称为显式原型(prototype是一个实实在在的属性,而[[prototype]]是一个隐藏的属性,二者有所区别)
  • 函数也是对象,因此函数既有__proto__ 也有prototype属性

_proto_:

__proto__ 用于访问[[prototype]],相当于[[prototype]]的访问器。(现在已经过时了,后面有其他方法

1
2
3
4
5
6
7
8
9
let person = {
height:180,
weight:70
}
let jack = {
name:'jack'
}
jack.__proto__=person
console.log(jack.height) //180,此时person就是原型对象

执行jack.height=160 并不改变person.height 的值,因为本质上是给jack添加了一个新的属性height。除非使用jack.__proto__.height=160,那么原型对象会改变,并且所有继承这个原型对象的对象都会受影响

for…in循环:for…in循环会将原型对象中的属性一起循环,可以使用内建方法obj.hasOwnProperty(key),判断这个当前循环的属性是不是当前对象的,如果是对象本身的返回true,如果是原型对象的返回false。

prototype:

prototype 是函数创建后自带的一个属性

我们可以用new关键字,从一个函数中创建一个对象,而函数prototype属性,就规定了new出来的对象的[[prototype]]指向哪个对象。

1
2
3
4
5
6
let Fun = function(){}
let obj = {tag:'ppp'}
Fun.prototype = obj
fun = new Fun() //用new关键字创建对象
console.log(fun.tag) //ppp fun的原型对象为obj
//let fun = new Fun()内部:this.__proto__ = Fn.prototype

prototype仅在new的时候才使用

默认的prototype:

默认的prototype对象只有一个属性 constructor 而constructor指向函数自身

1
2
let Fun = function(){}
console.log(Fun.prototype.constructor === Fun) //返回true

因此对一个函数的prototype不进行任何变动的话,所有new出来的对象都默认继承函数的constructor,因此有时可以通过对象的constructor来调用构造这个对象的函数。

注意:因为直接给prototype复制会覆盖prototype,同时也覆盖掉了默认的constructor属性,因此最好通过. 来为prototype添加新属性,而不是覆盖它

堆栈图

因为对象是一种引用类型,所以变量里存储的是地址,我们可以把上面函数创建对象并继承原型对象的过程,用图画出来
image-20230121105114300

原生的原型

内建方法Object()

除了用new fun()的方式创建一个对象,还可以用字面量的方式创建——let obj = {}

而所谓的字面量创建,其实是调用一个内建方法**Object()**,let obj = {} 相当于 let obj = new Object()

我们说过方法都有一个prototype 属性,指向一个对象,而Object()方法的prototype指向一个巨大的对象,这个对象包含.toString() 等对象可以直接调用的方法。

因此可以说所有字面量创建的对象都默认继承了Object.prototype
Object.prototype.__proto__的值为null ,因此,当我们访问对象中的一个属性或方法时,现在对象内部寻找,然后一直向上在原型对象中寻找,直到找到 Object.prototype.__proto__ 才会停止。

其它内建原型

除了字面量定义普通对象,还可以定义数组,方法,日期等。其实都是调用了一些内建方法,比如ArrayDateFunction,过程和Object 差不多。

这些内建方法都在prototype上挂载了属于自己的方法,比如数组操作函数操作的一些方法,而这个它们prototype对象又都继承自Object.prototype,因此都可以使用Object.prototype的方法。

image-20230121143420132

关于基本数据类型

js中的数字,布尔和字符串这些基本数据类型本不是对象,但是当我们调用它们的方法或属性时,js会创建临时包装器对象。这些对象用过内建方法String``Boolean Number 创建

nullundefined 没有临时包装器

intanceof

intanceof 判断左边的对象是不是右边函数的实例

1
2
3
4
5
6
function Fun(){}
let fun = new Fun()
console.log(fun instanceof Fun) //true
console.log(Fun instanceof Object) //true
console.log(fun instanceof Object) //true
console.log(Object instanceof Object) //true

判断原理: 右边函数的显式原型prototype 在左边对象的隐式原型链上,就返回true,否则返回false

Object()是一个方法,因此Object的隐式原型指向Function.prototype,而Object的显式原型是Object.prototype,而Function.prototype的隐式原型是Object.prototype,因此Object的显式原型在Object的原型链上,返回true

原型对象的属性

  • 如果中途替换构造函数的显式原型对象,替换之前生成的实例的隐式原型还是原来的对象
1
2
3
4
5
6
7
8
9
10
11
      function Fun(){
}
Fun.prototype.n=1
let A = new Fun()
Fun.prototype = {
n:3,
m:4
}
let B = new Fun()
console.log(A.n,A.m,B.n,B.m)
// 1 undefined 3 4
  • 内建原型对象是全局的,因此在上面挂载的属性和方法可以全局访问,但是不建议这样,因为不同库容易引发冲突
  • 构造函数的原型链上默认有Function.prototype,而构造函数创建的实例的原型链上默认没有Function.prototype,因为实例的原型默认继承的是构造函数的prototype,与Function.prototype无关。