浅谈原型和原型链的理解

一、前言

在 JavaScript 中,万物皆对象,每个对象(除了 nullundefined)被创建出来时都有一个对应的原型(prototype)属性,使对象共享原型的属性与方法,所以原型存在的意义就是解决属性与方法共享的问题,减少不必要的内存消耗,接下来我们就逐步讲解一下。

二、构造函数

构造函数和普通函数本质上没什么区别,只不过使用了new关键字创建对象的函数,被叫做了构造函数。构造函数命名通常采用首字母大写的方式,以便与普通函数和变量进行区分。

function Person(name,age){
    this.name = name
    this.age = age
}
let p1 = new Person('张三',18)
let p2 = new Person('李四',28)
console.log(p1) //{name: "张三", age: 18, say: f()}
console.log(p2) //{name: "李四", age: 28, say: f()}

三、原型对象

每一个构造函数的内部都有一个 prototype 属性,它的属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。当使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针被称为对象的原型。

Person.prototype.sing=function(){
  console.log('唱歌')
}
p1.sing() //唱歌
p2.sing() //唱歌

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

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

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

p1没有constructor属性–>查找Person的constructor属性
Person没有constructor属性–>查找Person.prototype的constructor属性

四、实例对象原型

构造函数可以创建实例对象,构造函数还有一个原型对象,一些公共的属性或方法放到这个原型对象身上,但是为什么实例对象可以访问原型对象里面的属性和方法呢?

JS 在创建对象后,每个实例都会有一个叫做 __ proto __ 的属性(内部指针),指向创建它的构造函数的原型对象,可以让实例方便访问到原型对象,从而使用原型对象里面的方法。__ proto __是非标准属性。

console.log(p1.__proto__=== Person.prototype) //true
构造函数、实例对象和原型的关系图

WechatIMG988.png

五、原型链

当通过对象访问一个属性或方法时,JavaScript 引擎首先在该对象自身查找,如果找不到,就会通过__proto__去对象的原型中查找对应的属性或方法。如果还找不到,就会继续在原型的原型上查找,直到找到该属性或方法,或者最终达到原型链的末端(null)。这种通过查找原型链来实现属性和方法的继承机制,就是原型链。

六、原型继承

通过将一个对象设置为另一个对象的原型,从而使得被设置的对象能够继承原型对象的属性和方法。

  • 把公共部分抽取出一个对象,把对象设置给不同类别的构造函数的原型对象(这个对象也是一个构造函数的实例)
const Person = {
  eyes: 2,
  say: function () {
    console.log("你好")

  },
}

function Student(name) {

  this.name = name

}

Student.prototype = Person
Student.prototype.age = 18

let stu1 = new Student("张三")

console.log(stu1.eyes) //2
stu1.say(); //你好
console.log(stu1.age) //18



function Worker(name) {

  this.name = name

}

Worker.prototype = Person
let work1 = new Worker("李四")

console.log(work1.eyes) //2

work1.say() //你好

console.log(work1.age) //18

WechatIMG991.png

这种方法是有弊端的。如果不同类别构造函数共享一个原型对象,其中一个改变了,另一个也会改变。

  • 创建Person的构造函数,new处理的对象有相同的公共属性和方法,并且是不同的对象
function Person() {
  this.eyes = 2
  this.say = function () {
    console.log("你好")

  }
}

function Student(name) {

  this.name = name

}

Student.prototype = new Person()
Student.prototype.age = 18

let stu1 = new Student("张三")

console.log(stu1.eyes); //2
stu1.say() //你好
console.log(stu1.age) //18



function Worker(name) {

  this.name = name

}

Worker.prototype = new Person()
let work1 = new Worker("李四")

console.log(work1.eyes) //2

work1.say() //你好

console.log(work1.age) //undefined

WechatIMG992.png

七、总结

当然原型继承还有其他的方法,这里只介绍构造函数和原型的组合,无论采用哪种方式,原型继承都是以对象的原型链为基础,在不同对象之间共享属性和方法。通过原型继承,可以实现代码的复用,并且对于动态的对象修改也能够得到自动更新。这是 JavaScript 中一种灵活而强大的继承机制。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYscuiDA' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片