面试官:怎么理解原型、原型链?

带着问题出发:

  • 什么是原型、原型链?
  • 原型和原型链有什么关系?
  • __proto__和prototype分别是什么?
  • 构造函数、实例、对象原型之间的关系?

不妨先想想上面的问题,动一下大脑,是否能回答的上~

定义理解

  1. 原型
  • 构造函数的定义:
    构造函数是用于创建特定类型对象的。像Object和Array这样的原型构造函数,运行时可以直接在执行环境使用。
    构造函数也是函数,按照惯例,构造函数名称的首字母都是要大写的;创建构造函数的实例应该使用 new 操作符;在实例化时,如果不想传参数,那么构造函数后面的括号可加可不加。
  • 原型的定义:
    每个函数都会创建一个prototype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。

实际上,prototype属性的这个对象就是通过调用构造函数创建的对象的原型。使用原型对象的好处是,在它上上面定义的属性和方法可以被对象实例共享。(普通对象没有prototype属性,有__proto__属性)

function Person(){}

Person.prototype.name = "guava"
Person.prototype.sayName = function(){
  console.log(this.name)
}

let person1 = new Person()
person1.sayName() // "guava"

let person2 = new Person()
person2.sayName() // "guava"

console.log(person1.sayName === person2.sayName) // true

理解原型:

创建函数时,会创建一个prototype属性(指向原型对象);默认情况下,原型对象都会有一个 constructor 的属性(指向与之关联的构造函数),就上面的例子而言就是:Person.prototype.constructor === Person,而其自带的其他方法都继承于Object(如toString)。

每次调用构造函数创建新实例时,实例内部的[[Prototype]]指针就会赋值为构造函数的原型对象,而脚本中没有访问这个[[Prototype]]特性的标准方式,但一些浏览器Firefox、Safari和Chrome会在每个对象中暴露了__proto__属性,通过这个属性可访问到原型对象。

关键的在于理解一点:实例与构造函数原型之间有直接的联系,但实例与构造函数之间没有。

function Person(){}

console.log(Person.prototype.constructor === Person) // true

/**
*	正常的原型链都会终止于Object的原型对象
* Object的原型对象是null
*/
console.log(Person.prototype === Object.prototype) // true
console.log(Person.prototype.constructor === Object) // true
console.log(Person.prototype.__proto__.__proto__ === null) // true

let person1 = new Person()

/**
*	构造函数、原型对象和实例,是三个完全不同的对象
*/
console.log(person1 !== Person) // true
console.log(person1 !== Person.prototype) // true
console.log(Person.prototype !== Person) // true

/**
*	实例的__proto__,指向原型对象(实际上指向隐藏特性[[Prototype]])
* 构造函数的prototype,链接到原型对象(可以理解成Prototype就是原型对象)
* 实例与构造函数没有直接联系,与原型对象有直接联系
*/
console.log(person1.__proto__ === Person.prototype) // true
console.log(person1.__proto__.constructor === Person) // true

let person2 = new Person()

/**
*	同一个构造函数创建的多个实例,会共享同一个一个原型对象
*/
console.log(person2.__proto__ === Person.prototype) // true
console.log(person1.__proto__ === person2.__proto__) // true

原型层级:

通过对象访问属性时,搜索开始与实例本身,如果没有找到这个属性,则沿着指针(proto)进入原型对象找到属性后,并返回对应的值。hasOwnProperty()方法用于确认某个属性是否再实例上还是再原型对象上。

function Person(){}

Person.prototype.name = 'guava'

let person1 = new Person()
console.log(person1.hasOwnProperty('name')) // false

person1.name = 'sixteen'
console.log(person1.hasOwnProperty('name')) // true

delete person1.name
console.log(person1.name) // "guava"
console.log(person1.hasOwnProperty('name')) // false
原型和 in 操作符:
有两种方式使用 in 操作符:单独使用和 for-in 使用。in 操作符可以访问实例或原型上的属性。
function Person(){}

Person.prototype.name = "guava"
Person.prototype.age = 25

let person1 = new Person()
console.log("name" in person1) // true 来自原型

person1.name = "sixteen"
console.log("name" in person1) // true 来自实例

for(let key in person1){
  console.log(key)
}
//name
//age

其他原型语法:

在重写原型对象后,原型的constructor属性不再指向其原来的构造函数,会指向到完全不同的新对象(Object 构造函数)。重写构造上的原型之后再创建的实例,才会引用新的原型,而在此之前创建的实例仍然会引用最初的原型。

function Person(){}

//重写原型对象
Person.prototype = {
  name:"guava" 
}

let person1 = new Person()

//重写原型对象后,构造函数指向了新对象(Object 构造函数)
console.log(person1.__proto__.constructor === Person) // false
console.log(person1.__proto__.constructor === Object) // false

Person.prototype.sayHi = function(){
  console.log("Hi!")
}

let person2 = new Person()

person2.sayHi() // "Hi!"

//person1是 添加原型方法sayHi 前实例化的,所以指向的是最初的原型,所以没有sayHi方法
person1.sayHi() // 报错
  1. 原型链
    ECMA-262把原型链定义为ECMAScript的主要继承方式。其基本思想就是通过原型继承多个引用类型的属性和方法。每个构造函数都有一个原型对象,原型有一个属性指向构造函数,而实例有一个内部指针指向原型。如果原型是另一个类型的实例,则原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。
function SuperType(){
  this.property = true
}

SuperType.prototype.getSuperValue = function(){
  return this.property
}

function SubType(){
  this.subProperty = false
}

SubType.prototype = new SuperType()

SubType.prototype.getSubValue = function(){
  return this.subProperty
}

let instance = new SubType()
console.log(instance.getSuperValue(),instance.getSubValue()) // true , false

代码中可以看到分别定义了两个类型 SuperType和SubType,并各自定义了一个属性和方法,SubType通过创建SuperType的实例并将其赋值给自己的原型Subtype.prototype实现了对SuperType的继承。还要注意,由于SubType.prototype的constructor属性被重写为指向SuperType,所以instance.constructor指向了SuperType。

总结

  1. 在创建构造函数时,自动生成一个prototype属性,此属性即原型对象,原型对象中默认生成一个constructor属性,指向其构造函数。
  2. 而通过构造函数创建实例时,内部会生成一个[[Prototype]]的特性(指向原型对象),脚本中没有标准的方式访问该属性,所以一些浏览器在每个对象中暴露了__proto__的属性来访问其原型对象。
  3. 访问对象属性时,会从实例上开始查找,如果没有存在该属性,会访问原型对象获取属性。如果查找对象属性的实例指向的原型,是其他类型的实例,则又会从该实例和其指向原型继续查找,这样不断向上查找就形成了原型链的基本思想。

参考

《JavaScript高级程序设计》

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值