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

原型链与继承

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

新建函数,并建立对象浏览器

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

这时咱们的脑海里应该有这样一张图:函数

clipboard.png

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

基于原型链的继承

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

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

接着上述代码prototype

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

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

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

继续写代码blog

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的行为保持一致。

相关文章
相关标签/搜索