JS-继承

JS的继承


首先需要知道call和apply的使用:
-call(执行环境对象,参数列表), 列表即说明可以传递多个参数
let obj1 = {
  name:'LBJ'
}
let obj2 = {
  name:'KOBE'
}
function sayName() {
  console.log(this.name);
}
sayName()//undefined

此时的sayName()相当于window.sayName(),this指向window,所以this.name为undefined。所以如果想让this指向obj1或者obj2,就需要借助call、apply或bind

let obj1 = {
  name:'LBJ'
}
let obj2 = {
  name:'KOBE'
}
function sayName(age) {
  console.log(age);
  console.log(this.name);
}
//将sayName放入obj1的环境中指向
sayName.call(obj1, 13)//13, LBJ
sayName.call(obj2, 999)//999 KOBE

call将sayName方法在obj1\obj2中执行,this指向obj1\obj2,改变了this的指向。

-apply(执行环境对象, 参数数组)

修改this指向和call一致,不同的是apply第二个参数接收的是一个数组,可以传递参数带来了一个隐藏的作用

如果需要用Math.max()找出一个数组中最大的数

let arr = [99,23,123,4,2,12]
//let res = Math.max(arr)//直接传入一个数组是行不通的
//console.log(res);//NaN
//此时借助apply接收一个数组调用Math.max()
console.log(Math.max.apply(null,arr));//123

如果第一个参数为null,则node环境下this默认指向global,浏览器环境下this默认指向window。

知道了call和apply修改this指向,就很容易理解经典继承是如何实现的。

-call和apply在继承中

一个简单的例子:

假设 一个obj刚开始为一个空的对象,没有任何的信息。obj现在需要拥有自己的名字 年龄 金钱。第一种方法是使用字面量定义的方式,

obj.name = ’ ’ …但是这明显不是我们希望的。

自定义构造函数也是一个函数,我们可以借助call和apply来让this指向obj,即让Person函数在obj对象内执行。

我们可以说obj通过这种方式继承了Person的属性

function Person(name, age, money) {
  this.name = name;
  this.age = age;
  this.money = money;
}
let obj = {}
console.log(obj);//{}
Person.call(obj,'张三', 14, '1yi')//this指向obj
console.log(obj);//{ name: '张三', age: 14 }

再有自定义构造函数Animal , 拥有着其他所有动物所共有的属性,而其他的动物猫猫狗狗的叫声、喜欢的食物…是不同的。

我们希望Cat、Dog拥有自己特有的属性,也继承Animal共有的属性

//假设无论什么动物都有名字 年龄 高度 
function Animal(name, age, height) {
  this.name = name;
  this.age = age;
  this.height = height
}
//猫的叫声是猫特有的
function Cat(say){
 this.say = say
}
//狗的叫声是狗特有的
function Dog(say) {
  this.say = say
}
let amy = new Cat('喵喵喵')//猫的实例 amy
let tom = new Dog('汪汪汪')//狗的实例 tom

Cat的实例想要继承Animal的属性,就需要让Animal在该实例对象环境中执行。Dog也如此

function Animal(name, age, height) {
  this.name = name;
  this.age = age;
  this.height = height
}
function Cat(say, name, age, height){
  Animal.call(this, name, age, height)
 this.say = say
}
function Dog(say, name, age, height) {
  Animal.call(this, name, age, height)
  this.say = say
}
let amy = new Cat('喵喵喵','AMY', 23, '20cm')//猫的实例 amy
let tom = new Dog('汪汪汪', 'TOM',30, 'TOM')//狗的实例 tom
console.log(amy);//{ name: 'AMY', age: 23, height: '20cm', say: '喵喵喵' }
console.log(tom);//Dog { name: 'TOM', age: 30, height: 'TOM', say: '汪汪汪' }

简单说明过程:new --> this 指向实例 --> 执行自定义构造函 --> Animal此时通过call被调用了,执行环境还是这个实例 --> 该实例获得了Animal的属性**

只是继承Animal 的属性显然是不够的。如两种动物都需要吃东西,只是食物不同

function Animal() {
  this.eat = function() {
    console.log('喜欢的食物是 '+ this.food);
  }
}
function Cat(food){
  Animal.call(this)
  this.food = food
}
function Dog(food) {
  Animal.call(this)
  this.food = food
}
let amy = new Cat('fish')
let tom = new Dog('sausage')
amy.eat()//喜欢的食物是 fish
tom.eat()//喜欢的食物是 sausage
console.log(amy.eat === tom.eat);//false,说明两个函数不一样,内存中存在了两个 效果一样的函数,造成了浪费

之所以要继承,就是希望将共有的属性、方法不存在冗余,而这样实现同样的动作却存在两个函数,是没有必要的。经典继承的缺陷和自定义构造函数创建对象缺陷是一样的。

解决的方法1:将函数提取出来,缺点就是如果存在多个需要继承的方法,就需要在全局中定义多个方法,这会给其他构造函数带来许多的不便。

function Animal() {
  this.eat = eatFood//eat保存的是eatFood的地址
}
function eatFood() {
  console.log('喜欢的食物是 '+ this.food);
}
//
function Cat(food){
  Animal.call(this)
  this.food = food
}
function Dog(food) {
  Animal.call(this)
  this.food = food
}
let amy = new Cat('fish')
let tom = new Dog('sausage')
amy.eat()//喜欢的食物是 fish
tom.eat()//喜欢的食物是 sausage
console.log(amy.eat === tom.eat);//true 他们都指向同一个地址

所以我们需要把Animal的方法,放在Animal的原型上,再让其他对象想办法继承得到。

-原型继承

在自定义构造函数的学习中,我们知道了,当一个实例被new 创建出来时发生了:

  • this指向被new出来的实例
  • 开辟了一块新的内存空间
  • 该实例产生[[prototyoe]]指向它的构造函数的原型prototype,实例可以调用它的构造函数上的属性和方法
继承1

有Animal的’‘类’‘和Cat类,amy是Cat的实例,此时没有任何的继承关系

function Animal(name) {
  this.name = name
}
// 在Animal的原型上 定义了吃东西的方法
Animal.prototype.eatFood = function() {
  console.log('喜欢的食物是 ' + this.food);
}
function Cat(food) {
  this.food = food
}
let amy = new Cat('fish')
iShot2021-09-10 20.26.18

-如何实现继承呢,需要再次提起一句话:当一个实例被new出来时,该实例产生[[prototyoe]]指向它的构造函数的原型prototype,实例可以调用它的构造函数上的属性和方法

所以只需要,将Cat的原型,成为Animal的实例。

 Cat.prototype = new Animal()
//Cat.prototype 成为Animal的实例,就有一个[[prototype]]指向它的构造函数(Animal)的原型。
//此时Cat.prototype 可以调用Animal原型上的属性或方法
iShot2021-09-10 20.34.42
function Animal(name) {
  this.name = name
}
// 在Animal的原型上 定义了吃东西的方法
Animal.prototype.eatFood = function() {
  console.log('喜欢的食物是 ' + this.food);
}
function Cat(food) {
  this.food = food
}
Cat.prototype = new Animal()
let amy = new Cat('fish')
amy.eatFood()//喜欢的食物是 fish

这样子就形成了原型链的继承


-现在有一个原型链
iShot2021-09-10 20.53.51
function Animal(name) {
  this.name = name
}
Animal.prototype.sayName = function() {
  console.log('这是Animal原型上的方法,我想说:' + this.name);
}
Animal.prototype.age = '999岁'
function Cat(food) {
  this.food = food
}
Cat.prototype = new Animal
Cat.prototype.name = 'protoAmy'
Cat.prototype.age = '20岁';
let amy = new Cat('fish')
amy.name = '我自己取的名字叫amy'

console.log(amy.name);//我自己取的名字叫amy
console.log(amy.age);//'20岁'
amy.sayName()//这是Animal原型上的方法,我想说:我自己取的名字叫amy

个人理解:当一个人自己没有一样东西时,需要问自己的爸爸要,如果爸爸没有,则向爷爷要

// amy.name = '我自己取的名字叫amy'
console.log(amy.name);//这是Animal原型上的方法,我想说:protoAmy

验证是否解决了前面经典继承的问题:

function Animal() {
}
Animal.prototype.sayName = function() {
  console.log('这是Animal原型上的方法,我想说:' + this.name);
}
function Cat() {}
function Dog() {}
Cat.prototype = new Animal
Dog.prototype = new Animal
let amy = new Cat
let tom = new Dog()
tom.name = 'tom狗的名字是tom'
amy.name = '猫的名字叫amy'
tom.sayName()//这是Animal原型上的方法,我想说:tom狗的名字是tom
amy.sayName()//这是Animal原型上的方法,我想说:猫的名字叫amy
console.log(tom.sayName === amy.sayName);//true,方法们使用的是同一个方法
-组合继承:利用两种继承的优势

继承属性用经典继承,继承方法用原型链继承

function Animal(name) {
  this.name = name;
  this.friends = ["cat", "rabbit"];
}
Animal.prototype.sayName = function () {
  console.log(this.name);
};
function Dog(name, age) {
  // 继承属性
  Animal.call(this, name);
  this.age = age;
}
// 继承方法
Dog.prototype = new Animal();
Dog.prototype.sayAge = function () {
  console.log(this.age);
};
let dog = new Dog('kevin', 000)
let dog1 = new Dog('jerry', 2333)
//他们的朋友互不影响
dog.friends.push('newAdd')
console.log(dog);//[ 'cat', 'rabbit', 'newAdd' ]
console.log(dog1);//[ 'cat', 'rabbit' ]
//方法公用没有冗余
console.log(dog.sayAge == dog1.sayAge);//true
console.log(dog.sayName == dog1.sayName);//true

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值