[javaScript]原型与原型链的总结

224 阅读3分钟

一、现象

平时开发中:

  • 一般声明的变量无论是基本类型(除null、undefined)还是引用类型(除特殊操作),能在其本身调用__proto__属性得到一个对象
  • 声明的函数function,能在其本身调用prototype属性得到一个对象
  • 通过构造函数实例化出来的对象实例,能在其本身调用__proto__属性得到一个对象
let a = 'hello'
a.__proto__ // 一个String对象

function b () {
    console.log('hello')
}
b.prototype // 一个带有constructor属性指向其函数自身的对象

function C (key) {
    this.key = key
}
C.prototype.come = function () {
    console.log('come')
}
let c = new C('2020')
c // 构造函数C的实例
c.__proto__ // 一个带有constructor属性指向其函数自身、come方法的对象

二、prototype与__proto__

我们说原型,比较多的是构造函数上的prototype,还有对象上的__proto__

函数的prototype属性指向一个对象,这个对象是调用该构造函数而创建的实例所依据的原型

对象的__proto__属性指向一个对象,这个对象是创建的该对象所依据的原型

通过一段代码可以比较清晰的知道他们的关系:

function Person(name) {
    this.name = name
}
Person.prototype.say = function () {
    console.log('你好')   
}
Person.prototype.lang = 'zh'

let person1 = new Person('tom')
let person2 = new Person('joh')

console.log(person1.name, person1.lang, person1.say()) // 'tom', 'zh', '你好'
console.log(person2.name, person2.lang, person2.say()) // 'joh', 'zh', '你好'

person1.__proto__ // {lang: "zh", say: ƒ, constructor: ƒ}
person2.__proto__ // {lang: "zh", say: ƒ, constructor: ƒ}

Person.prototype === person1.__proto_ // true
Person.prototype === person2.__proto_ // true

通过构造函数Person创建出的实例,会关联到构造函数Person的prototype原型,并保存到其自身的__proto__属性中。多个实例可以共享其原型上的方法和属性(毕竟大家所指向的是同一个对象,所以也才有如果在某一实例上修改其原型的属性和方法,其他实例在调用上也会受到影响

可用一张图表示:

以上便是在javaScript原型的体现

三、constructor

在上面的打印,在构造函数的原型中会看到constructor这个属性,其是原型与对应构造函数的关联

Person === Person.prototype.constructor // true

所以便有:

四、原型的原型

上面提到function的prototype和对象的__proto__都指向其原型,它是一个对象

既然是一个对象,那它也会有__proto__属性,即有它的原型,我们可以打印出来看一下

Person.prototype.__proto__ // {constructor: ƒ, __defineGetter__: ƒ, ...}

展开我们可以看到它的原型的constructor指向Object构造函数的,即它的表达式为Object.prototype

那么我们可以得知:其原型对象是通过Object构造函数生成的,结合上面说的,我们可以轻松得到以下关系:

五、原型链

在读取一个对象的某个属性时,如果在该对象中找不到,就会查找与该对象关联的原型中的属性。如果在原型中仍查不到,那么便会去原型的原型中查找,一直到最顶层为止(什么时候是最顶层呢,下面有讲~)

上面的查找的过程走的路线,便是原型链

function Person() {
    
}
Person.prototype.lang = 'zh'

let person = new Person()
console.log(person.lang) // 'zh'
person.lang = 'en'
console.log(person.lang) // 'en'
delete person.lang
console.log(person.lang) // 'zh'

在这个例子中,我们给实例对象person添加了lang属性,当我们打印person.lang的时候,读取到'en'

当我们删除了person的lang属性时,读取person.lang,从person对象中找不到name属性,就从person的原型person.__proto__/Person.prototype中查找,然后就找到了lang属性,为'zh'

六、原型链最顶层

  • 基层 person
  • 第一层 person.proto (也是Person.prototype)
  • 第二层 person.proto.proto (也是Person.prototype.proto,由四可知:也为Object.prototype)

上面的例子走了两层到了person -> person.__proto__ => person.__proto__.__proto__即Object.prototype,那么它的原型是什么呢

person.__proto__.__proto__.__proto__ // null
Object.prototype.__proto__ // null

null表示"该处没有值",也就意味着查找属性的时候查到Object.prototype就可以停止查找了,即到了最顶层的时候了

相互关联的原型组成的链状结构就是原型链,蓝色那条线就是原型链

七、参考文章

JavaScript深入之从原型到原型链

感谢~

如哪里有讲得不对的地方,还请指出,再次感谢~