资讯专栏INFORMATION COLUMN

深入理解 js 之继承与原型链

xingqiba / 1132人阅读

摘要:原型链与继承当谈到继承时,只有一种结构对象。如果对该图不怎么理解,不要着急,继续往下看基于原型链的继承对象是动态的属性包指其自己的属性。当使用操作符来作用这个函数时,它就可以被称为构造方法构造函数。

原型链与继承

当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象(object )都有一个私有属性(称之为proto)指向它的原型对象(prototype)。该原型对象也有一个自己的原型对象(proto) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

新建函数,并创建对象

function Car() {
    this.name = "BMW"
    this.price = 95800
}
let carBMW = new Car()

这时我们的脑海里应该有这样一张图:

或许你跟我初次接触一样。如果对该图不怎么理解,不要着急,继续往下看!!!

基于原型链的继承

JavaScript 对象是动态的属性“包”(指其自己的属性)。JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

从 ECMAScript 6 开始,[[Prototype]] 可以通过Object.getPrototypeOf()和Object.setPrototypeOf()访问器来访问。这个等同于 JavaScript 的非标准但许多浏览器实现的属性 __proto__。

接着上述代码

console.log(carBMW)  // *Car {name: "BMW", price: 95800}*

在 Car() 函数的原型上定义属性

Car.prototype.price = 200000
Car.prototype.speed = 300

console.log(carBMW.__proto__)  // {price: 200000, speed: 300, constructor: ƒ}
console.log(carBMW.__proto__.__proto__ == Object.prototype)  // true
console.log(carBMW.__proto__.__proto__.__proto__) // null

综合上述代码,可以给出如下原型链

{name: "BMW", price: 95800} ---> {price: 200000, speed: 300, constructor: ƒ} ---> Object.prototype ---> null

继续写代码

console.log(carBMW.name)   // BMW
// name 为 carBMW 自身的属性

console.log(carBMW.price)  // 95800
// price 为 carBMW 自身的属性,原型上也有一个"price"属性,但是不会被访问到,这种情况称为"属性遮蔽 (property shadowing)"

console.log(carBMW.speed)  // 300
// speed 不是 carBMW 自身的属性,但是 speed 位于该原型链上,因此我们依然可以取到该值

// 当然如果你试着访问一个不存在原型链上的属性时,这时候会给你返回一个undefined

当我们给对象设置一个属性时,创建的属性称为对象的自有属性。

函数的继承与其他的属性继承没有差别,包括上面的“属性遮蔽”(这种情况相当于其他语言的方法重写)。

当继承的函数被调用时,this 指向的是当前继承的对象,而不是继承的函数所在的原型对象。
let car = {
  price: 95800,
  getPrice: function(){
    return this.a
  }
}

console.log(car.getPrice()); // 95800
// 当调用 car.getPrice() 时,"this"指向了car.

let bmw = Object.create(car);
// bmw 是一个继承自 car 的对象

bmw.price = 400000; // 创建 bmw 的自身属性 price
console.log(bmw.getPrice()); // 400000
// 调用 bmw.getPrice() 时, "this"指向 bmw. 
// 又因为 bmw 继承 car 的 getPrice 函数
// 此时的"this.price" 即 bmw.a,即 price 的自身属性 "price" 

虽然有点绕,细读之后逻辑并不是很复杂

多种方法创建对象 基于字面量创建对象
也就是根据相应的语法结构直接进行创建
let car = {
  price: 95800,
  getPrice: function(){
    return this.a
  }
}
// car 为一个对象,因此相应的原型链应该如下
// car ---> Object.prototype ---> null

let cars = ["BMW","Audi","WulingHongguang"]
// cars 为一个数组对象,相应的原型链应该如下
// cars ---> Array.prototype ---> Object.prototype ---> null
基于构造函数创建对象
在 JavaScript 中,构造器其实就是一个普通的函数。当使用 new 操作符 来作用这个函数时,它就可以被称为构造方法(构造函数)。
function Car() {
    this.name = "BMW"
    this.price = 95800
}
Car.prototype.speed = 300
let car = new Car()
// 可以知道,car 的自身属性 {name: "BMW", price: 95800}, 位于原型链上的属性有 speed .
基于Object.create()创建对象
ECMAScript 5 中引入了一个新方法:Object.create()。可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数
var car = {price: 10000}; 
// car ---> Object.prototype ---> null

var carBMW = Object.create(car);
// carBMW ---> car ---> Object.prototype ---> null
console.log(carBMW.price); // 10000 (继承而来)
基于 Class 关键字创建对象
ECMAScript6 引入了一套新的关键字用来实现 class。实质上还是语法糖,底层原理依旧不变
class Point {
     constructor(x, y) {
        this.x = x;
        this.y = y;
     }
     toString() {
       return "(" + this.x + ", " + this.y + ")";
     }
}
var point = new Point(2, 3);
point.toString() // (2, 3)
point.hasOwnProperty("x") // true
point.hasOwnProperty("y") // true
point.hasOwnProperty("toString") // false
point.__proto__.hasOwnProperty("toString") // true

以上代码中,x和y都是实例对象point自身的属性(因为定义在this变量上),所以hasOwnProperty方法返回true,而toString是原型对象的属性(因为定义在Point类上),所以hasOwnProperty方法返回false。这些都与ES5的行为保持一致。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/99231.html

相关文章

  • 深入学习js——原型原型

    摘要:我们用一张图表示构造函数和实例原型之间的关系好了构造函数和实例原型之间的关系我们已经梳理清楚了,那我们怎么表示实例与实例原型,也就是或者和之间的关系呢。 开篇: 在Brendan Eich大神为JavaScript设计面向对象系统的时候,借鉴了Self 和Smalltalk这两门基于原型的语言,之所以选择基于原型的面向对象系统,并不是因为时间匆忙,它设计起来相对简单,而是因为从一开始B...

    FingerLiu 评论0 收藏0
  • 深入学习js——原型原型

    摘要:我们用一张图表示构造函数和实例原型之间的关系好了构造函数和实例原型之间的关系我们已经梳理清楚了,那我们怎么表示实例与实例原型,也就是或者和之间的关系呢。 开篇: 在Brendan Eich大神为JavaScript设计面向对象系统的时候,借鉴了Self 和Smalltalk这两门基于原型的语言,之所以选择基于原型的面向对象系统,并不是因为时间匆忙,它设计起来相对简单,而是因为从一开始B...

    xialong 评论0 收藏0
  • JavaScript深入各种继承

    摘要:通常有这两种继承方式接口继承和实现继承。理解继承的工作是通过调用函数实现的,所以是寄生,将继承工作寄托给别人做,自己只是做增强工作。适用基于某个对象或某些信息来创建对象,而不考虑自定义类型和构造函数。 一、继承的概念 继承,是面向对象语言的一个重要概念。通常有这两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。 《JS高程》里提到:由于函数没有签名,...

    tomlingtm 评论0 收藏0
  • 深入理解JavaScript

    摘要:深入之继承的多种方式和优缺点深入系列第十五篇,讲解各种继承方式和优缺点。对于解释型语言例如来说,通过词法分析语法分析语法树,就可以开始解释执行了。 JavaScript深入之继承的多种方式和优缺点 JavaScript深入系列第十五篇,讲解JavaScript各种继承方式和优缺点。 写在前面 本文讲解JavaScript各种继承方式和优缺点。 但是注意: 这篇文章更像是笔记,哎,再让我...

    myeveryheart 评论0 收藏0
  • 面向对象的 JavaScript

    摘要:是完全的面向对象语言,它们通过类的形式组织函数和变量,使之不能脱离对象存在。而在基于原型的面向对象方式中,对象则是依靠构造器利用原型构造出来的。 JavaScript 函数式脚本语言特性以及其看似随意的编写风格,导致长期以来人们对这一门语言的误解,即认为 JavaScript 不是一门面向对象的语言,或者只是部分具备一些面向对象的特征。本文将回归面向对象本意,从对语言感悟的角度阐述为什...

    novo 评论0 收藏0

发表评论

0条评论

xingqiba

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<