原型链与继承
当谈到继承时,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的行为保持一致。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。