JavaScript原型(中)

原型链实现继承

在面向对象的语言中都存在一个大家熟悉的词 —— 继承。那在JS中也同样存在继承这个概念,在ES6之前,JS还没有提出class的概念。

这时候,JS是通过什么来实现继承的呢?答案就是:原型链

通过一段代码认识下原型链

function Father() {
  this.fatherage = 45;
};
Father.prototype.getFatherAge = function() {
  return this.fatherage;
}
function Son() {
  this.sonage = 20;
}

// 继承 Father
/* 
  Son.prototype对象成为了Father的实例
*/
Son.prototype = new Father();

Son.prototype.getSonAge = function() {
  return this.sonage;
}

let son_instance = new Son();
console.log(son_instance.getFatherAge()); // 45
/* 
  这里是因为改变委托关系导致的 constructor 属性的指向改变
  下一篇 => JS原型(下)中会讲到
*/
console.log(Son.prototype.constructor);  // Father()构造函数
console.log(son_instance.__proto__.__proto__.__proto__);  // Object.Prototype
console.log(son_instance.__proto__.__proto__.__proto__.constructor);  // Object()构造函数

我们可以通过代码发现,我构建的son_instance是Son的实例,但是我却可以通过son_instance调用Father原型对象中的方法,这究竟是如何实现的呢?以及后面几行代码为何打印出这些?

看懂这张图,你就能理解上面的问题:
image-20210705072308494

扩展问题(new原理)

在上面的代码与图结合发现,其中实现Son继承Father的关键代码是Son.prototype = new Father();,也就是让Son的原型成为Father的实例,这样我们就创造了一个如图所示的关系,让原本指向Object的原型对象的Son.prototype属性指向了Father.Prototype,那你认为我将代码改为Son.prototype.__proto__ = Father.prototype; 能实现继承吗?

那到底行不行,我们可以通过代码来观察

function Father() {
  this.fatherage = 45;
};
Father.prototype.getFatherAge = function() {
  console.log('我被调用了');
  console.log(this);  
  return this.fatherage;
}

function Son() {
  this.sonage = 20;
}

// 继承Father
// 方式一:
// Son.prototype = new Father();

// 方式二:为什么这样不能实现继承?
Son.prototype.__proto__ = Father.prototype;

console.log(Son.prototype.__proto__);   // {getFatherAge: ƒ (), constructor: ƒ Father()}

let son1 = new Son();
console.log(son1.getFatherAge());   
// "我被调用了"  
// Son {sonage: 20} 
// undefined

这里的最后一行代码显示的是undefined,这说明我们并没有实现继承,无法通过Son的实例去调用到Father的原型中的方法

那这是为什么呢?我们的继承不就是用[[Prototype]]属性把Son.PrototypeFather.Prototype联系起来的吗?

答案就藏在这个new关键字里,因为我们解读出new关键字究竟做了些什么!

new原理

var fn = function () { };
var fnObj = new fn();
  1. 在内存中创建一个新对象
var obj = new object();
  1. 这个新对象的[[Prototype]](也就是__proto__)属性指向构造函数的原型对象
obj._proto_ = fn.prototype;
  1. 将构造函数中的this指向这个新对象

  2. 执行构造函数内部的代码(给新对象添加属性)

var result = fn.call(obj);  
  1. 如果函数没有返回其他对象,那么返回新创建的这个对象
if (typeof(result) == "object"){  
    fnObj = result;  
} else {  
    fnObj = obj;
} 

属性设置与屏蔽

属性赋值并不简单!!

我们将分三种情况

第一种

let car = new Object();

// 原型上添加wheel属性
Object.prototype.wheel = 'prototype wheel';

// 此时原型对象上已存在wheel属性,为底层同名属性赋值
car.wheel = 4;

console.log(car.wheel);  // 4 (取原型链最底层的wheel属性)

此时取原型链最底层的属性!

第二种

let car = new Object();

// 原型上添加wheel属性并设置为只读
Object.defineProperty(Object.prototype, "wheel", {
  value: 'prototype wheel',
  // 将wheel属性设置为只读
  writable: false
})

// 此赋值语句被忽略(非严格模式)
car.wheel = 4;

console.log(car.wheel);  // prototype wheel 

第二种和第一种的区别在于将原型对象上的wheel属性设置为只读,此时我们在car对象上赋值wheel属性时,此赋值语句会被忽略

原因:这样做时为了模拟类属性的继承,你可以把原型对象中的wheel属性看作是父类中的属性,它会被car继承(复制),这样一来car中的wheel属性也是只读,所以无法创建

let car = new Object();

// 原型上添加wheel属性并设置为只读
Object.defineProperty(Object.prototype, "wheel", {
  value: 'prototype wheel',
  // 将wheel属性设置为只读
  writable: false
})

// 换一种定义方式
Object.defineProperty(car, "wheel", {
  value: 4
})

console.log(car.wheel);  // 4 

但是我们如果不通过=car.wheel赋值,而是通过==Object.defineProperty==去设置的话,此时又可以获取到car对象上的wheel属性

第三种

// car 的原型对象
let vehicle = {
  // 给 wheel 属性定义setter
  set wheel(value) {
    // 注意这里是 _wheel_
    this._wheel_ = value;
  }
};

let car = Object.create(vehicle);

car.wheel = 4;

console.log(car);  // {_wheel_: 4}

原型对象上存在wheel属性并且它是一个setter,那就一定会调用这个setterwheel不会被添加到car,也不会重新定义wheel这个setter

隐式屏蔽

var anotherObj = {a: 2};

var obj = Object.create(anotherObj);

anotherObj.a;   //2
obj.a;  //2

anotherObj.hasOwnProperty("a"); //true
obj.hasOwnProperty("a");    //false

obj.a++;

anotherObj.a;   //2
obj.a;  //3

obj.hasOwnProperty("a");    //true

这里的obj.a++;就是obj.a = obj.a + 1;分两步就是:

  1. 先从整个原型链获取= 右边的obj.a属性的值并加1
  2. 接着用将值3赋值给obj对象中新建的屏蔽属性a(也就是赋值给=的左值)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值