首页 > 编程语言 > 详细

理解 JavaScript 对象原型、原型链如何工作、如何向 prototype 属性添加新的方法。

时间:2019-04-07 11:16:59      阅读:127      评论:0      收藏:0      [点我收藏+]

JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推(这里的你可能还是懵的状态,先别管其他的,就知道原型是一层一层的就可以)。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

也就是说,我们没有特意去给一个实例对象添加属性和方法,但是这个实例对象却有属性和方法,这是因为这个实例对象继承了定义在构造器函数(我们事先定义好的一个函数,这个函数中有属性和方法,prototype就是特殊的属性,因为特殊所以它叫原型)中的属性和方法以及prototype。

那么这种继承是怎么实现的呢?实例对象和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。

理解对象的原型(可以通过Object.getPrototypeOf(obj)或者已被弃用的__proto__属性获得)与构造函数的prototype属性之间的区别是很重要的。前者是每个实例上都有的属性,后者是构造函数的属性。也就是说,Object.getPrototypeOf(new Foobar())Foobar.prototype指向着同一个对象。

来看个例子:

创建一个构造函数Person,实例化这个Person为person1:

function Person(name, age) {
  
 this. name = name
    this.age = age
  
};
Person.prototype.sex = "male"
var person1 = new Person(‘zs‘, 18)

console.log(person1.__proto__, person1.__proto__.__proto__)

技术分享图片

结果显示两个框中的值是一样的,就是说person1.__proto__继续上溯一层__proto__,会找到上层的__proto__,也就是 person1.__proto__.__proto__,这两个量指向的同一个对象。那么这个上溯的过程就称为原型链。

现在我们去找person1.valueOf():

function Person(name, age) {
  
 this. name = name
    this.age = age
  
};
Person.prototype.sex = "male"
var person1 = new Person(‘zs‘, 18)

console.log(person1.valueOf())

结果:

技术分享图片

valueOf() 这个方法仅仅返回了被调用对象的值。在这个例子中发生了如下过程:

  • 浏览器首先检查,person1 对象是否具有可用的 valueOf() 方法。
  • 如果没有,则浏览器检查 person1 对象的原型对象(即 Person构造函数的prototype属性所指向的对象)是否具有可用的 valueof() 方法。
  • 如果也没有,则浏览器检查 Person() 构造函数的prototype属性所指向的对象的原型对象(这个例子指 Object构造函数的prototype属性所指向的对象)是否具有可用的 valueOf() 方法。这里有这个方法,于是该方法被调用。

 我们来验证一下是不是这么去上溯的:

function Person(name, age) {
  
 this. name = name
    this.age = age
  
};
Person.prototype.sex = "male"
var person1 = new Person(‘zs‘, 18)

console.log(person1.__proto__, Person.prototype)

结果:

技术分享图片

 

我可以得出结论,实例对象 person1的__proto__指向的就是person1的构造函数的prototype属性(Person的原型),这两个量指向的是同一个对象,在这个对象中第一层并没有valueOf()方法,所以就找不到,找不到就上溯,Person是从内置对象Object实例出来的,因此就要继续使用Person的__proto__到Object的prototype中去找valueOf()方法。来看代码:

function Person(name, age) {
  
 this. name = name
    this.age = age
  
};
Person.prototype.sex = "male"
var person1 = new Person(‘zs‘, 18)

console.log(person1.__proto__, Person.prototype, Object.prototype)

结果:

技术分享图片

这个时候,Object的prototype中有valueOf()方法,找到了!执行并打印出person1的内容:

技术分享图片

JavaScript 中到处都是通过原型链继承的例子。比如,你可以尝试从 StringDateNumber和 Array 全局对象的原型中寻找方法和属性。它们都在原型上定义了一些方法,因此当你创建一个字符串时:

var myString = ‘This is my string.‘;

myString 立即具有了一些有用的方法,如 split()indexOf()replace() 等。

prototype 属性大概是 JavaScript 中最容易混淆的名称之一。你可能会认为,this 关键字指向当前对象的原型对象,其实不是(还记得么?原型对象是一个内部对象,应当使用 __proto__ 访问)。prototype 属性包含(指向)一个对象,你在这个对象中定义需要被继承的成员。

 再来看,原型对象的修改:

function Person(name, age) {
  
 this. name = name
    this.age = age
  
};

Person.prototype.sex = "male"

var person1 = new Person(‘zs‘, 18)

Person.prototype.farewell = function() {
  console.log(‘this is farewell fn!‘);
}

person1.farewell()

结果:

技术分享图片

这时,我们看到,farewell已经被继承到了person1上。也就是说,旧有对象实例的可用功能被自动更新了。这证明了先前描述的原型链模型。这种继承模型下,上游对象的方法不会复制到下游的对象实例中;下游对象本身虽然没有定义这些方法,但浏览器会通过上溯原型链、从上游对象中找到它们。这种继承模型提供了一个强大而可扩展的功能系统。

 当然这个person1对象实例拥有了farewell方法,如果我们再实例化出来多个新的对象实例,那这些新的对象实例也都有farewell方法,这个时候如果farewell显示的一个人的姓名,那是不是所有对象实例中的姓名都一样了?但是现实中,不可能所有人都有一样的名字,不过还好,人可以按“姓”划成不同的类,所以我们可以把同一个姓的的这个属性,写在原型中,把名写在对象实例上,这样用一个构造函数,就能实例化出这个姓氏下的所有人的姓名:

function Person(lastName, age) {
  
     this.lastName = lastName
    this.age = age
};

Person.prototype.firstName = ‘wang‘
var person1 = new Person(‘wu‘, 18)
var person2 = new Person(‘liu‘, 20)

console.log(person1.fullName =  person1.firstName + person1.lastName)
console.log(person2.fullName =  person2.firstName + person2.lastName)

技术分享图片

好。“性”这个属性,已经被person1和person2都继承到了。是不是不用每次都定义“姓”这个属性了?省了不少事。

水平有限,前端小学生,文章中或有不当之处,欢迎批评指正!

 

理解 JavaScript 对象原型、原型链如何工作、如何向 prototype 属性添加新的方法。

原文:https://www.cnblogs.com/whq920729/p/10664301.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!