js的继承,我是搬运工

主要是搬运的这个大佬的  js 继承

这个搬运的 原型、构造函数、实例

他说的第一个是原型链继承:

先谈了原型、构造函数和实例以及三者之间的关系

1、原型 (prototype):一个简单的对象,用于实现对象的属性继承,可以简单的理解为对象的爹,每个 JavaScript 对象都包含一个 _proto_ 属性,指向他爹(该对象的原型),可用 [obj]._proto_ 进行访问

2、构造函数:可以通过 new 来新建一个对象的函数

3、实例:通过 new + 构造函数创建出来的对象,就是实例。

实例通过 _proto_ 指向原型,通过 constructor 指向构建函数。

以Object为例:

// 构造函数
Object();

// 实例
const instace = new Object();

// 原型,可以通过 [实例]._proto_ 或者 [构造函数].prototype
const prototype = Object.prototype;

补一个原型链:

原型链:查找一个对象的属性,先查找本身对象是否存在该属性,没有则会沿着 _proto_ 属性(即原型)一直向上查找,直到查找的这个对象为 null,结束查找。

一、原型链继承:

// 构造一个函数实现通用属性和方法,new 实例方法继承构造函数的属性和方法
function SuperFun (name,age) {
    // 公共的属性
    this.name = name
    this.age = age
    this.has = ['头','手','脚']
    this.say = function () {
        console.log(123465)
    }
}
// 公共方法,构建在原型上
SuperFun.prototype.getSuperValue = function () {
    console.log(this.has,'诶,我就是有');
    console.log(this.age+'岁'+this.name+'就是玩');
}

// 原型链继承,是通过 prototype 原型属性的方式继承父类
function SubFun (name,age) {
    // 公共的属性
    this.name = name
    this.age = age
}
// 把父的实例对象赋值给子的原型
SubFun.prototype = new SuperFun();    // 原型链继承
// 实例对象1
let zhangsan = new SubFun('zhangsan',18);
zhangsan.getSuperValue();    // 访问父的方法
zhangsan.has.push('小细腿');    
console.log(zhangsan.has);     // ["头", "手", "脚", "小细腿"]
// 实例对象2
let fwkt = new SubFun('法外狂徒',18);
fwkt.getSuperValue();    // 访问父的方法
fwkt.has.push('大长腿'); 
console.log(fwkt.has);    // ["头", "手", "脚", "小细腿", "大长腿"]

缺点:多个实例对引用类型的操作会被篡改。

遇见问题:

1、写公共方法时,在想为什么不能把方法写在构建函数里面,试了下

// 有这样的
function SuperFun (name,age) {
    // 公共的属性
    this.name = name
    this.age = age
    this.has = ['头','手','脚']
    function say () {
        console.log(123465)
    }
}
// 报错,Uncaught TypeError: zhangsan.say is not a function at <anonymous>:1:10

// 有这样的
function SuperFun (name,age) {
    // 公共的属性
    this.name = name
    this.age = age
    this.has = ['头','手','脚']
    return function say () {
        console.log(123465)
    }
}
// new SuperFun 就是返回的那个函数

// 最后终于找到了,感觉是个很小白的问题,但是,凡是总有但是,诶,我不会
function SuperFun (name,age) {
    // 公共的属性
    this.name = name
    this.age = age
    this.has = ['头','手','脚']
    this.say = function () {
        console.log(123465)
    }
}
// 这样就可以访问 say 了

在构建函数中也可以写公共方法,为什么写在原型上呢?

方法写在构建函数内和写在原型上的区别

写在构建函数内,每次 new 一个实例都会开辟一个 say 的空间,如果写在原型上只需要让实例去按着 _proto_(原型)去找就行了,节省内存。

2、SubFun.prototype = new SuperFun();    // 原型链继承,把父 new 的实例给了子的原型,这就是原型链,fwkt = new SubFun(),那么 fwkt 的原型就是自身的父亲,没毛病啊

二、构造函数继承

借用父类的构造实例来增强子类实例,就是复制一份父类构造函数中属性和方法

function SuperFun (name,age) {
    // 公共的属性
    this.name = name
    this.age = age
    this.has = ['头','手','脚']
    this.say = function () {
        console.log(123465)
    }
}
// 公共方法,构建在原型上
SuperFun.prototype.getSuperValue = function () {
    console.log(this.has,'诶,我就是有');
    console.log(this.age+'岁'+this.name+'就是玩');
}

function SubFun (name,age) {
    // 通过拷贝父类
    SuperFun.call(this,name,age);
    // 所有子类都有父类实例函数的副本
    // call 拷贝怎么进行的?
}

// 实例对象1
let zhangsan = new SubFun('张三',18);
zhangsan.getSuperValue();    // VM7072:25 Uncaught TypeError: zhangsan.getSuperValue is not a function at <anonymous>:25:10
zhangsan.has.push('大长腿');    
console.log(zhangsan.has);    // ["头", "手", "脚", "大长腿"]

// 实例对象2
let fwkt = new SubFun('法外狂徒',18);
fwkt.has.push('黑长直');    
console.log(fwkt.has);    // ["头", "手", "脚", "黑长直"]

缺点:只能继承父类的实例属性和方法,无法继承原型上的属性和方法。无法实现复用,所有子类都有父类实例函数的副本,影响性能

遇见问题:

1、// 通过拷贝父类 SuperFun.call(this,name,age);  // 所有子类都有父类实例函数的副本
    call 拷贝怎么进行的?

call 的实现

// 刚刚的刷新了一下没了,那么我再重新来,完全不慌
// 再举个栗子
let superObj = {
    age: 18,
}
function sub() {
    console.log(this.age);
}
sub.call(superObj);    // 18

// 再改造一下
let superObj1 = {
    age: 18,
    sub: function () {
        console.log(this.age);
    }
}
superObj1.sub();    // 18
// 我悟了

// 然后正式开始 call 函数2.0
Function.prototype.call2 = function (context) {
    // 这里的 this 是指向实例本身的,即 superObj2
    context.fn = this; 
    context.fn();    // 为啥非要赋值后再执行,为啥不能 this(),我试试确实不行
    delete context.fn;
}

// 测试1.0
let superObj2 = {
    age: 18,
}
let superObj3 = {
    sub: function () {
        console.log(this.age);
    }
}

superObj3.sub.call2(superObj2);  18    // 这个是我声明 superObj3 的时候没反应过来,写错了,不想改了
// 测试2.0
let superObj2 = {
    age: 18,
}
function sub () {
    console.log(this.age);
}
sub.call2(superObj2);    // 18

/*
 第一步,Function 这个原型上挂载一个新的 call2 的方法;
 第二步,把通过 this 把实例拿出来,赋值给传进来的父级,这个时候父级上面就会多个叫 fn 的方法
 第三步,父级的 fn 方法不想让别人看到,所以阅后即焚,就把它删除了
 sub.call2(superObj2) 
     ==>
 superObj2 = {
    age: 18,
    fn: function (name) {
        console.log(this.age)
    }
 } 
 superObj2.fn();
 delete superObj2.fn;
 大概就是这样了
*/

// 开始 call 函数3.0,解决不定参
/*
 arguments 对象是所有(非箭头)函数中都可用的局部变量,此对象包含传递给函数的每个参数【说是对象,调用好像数组一样,arguments[0]】
*/
Function.prototype.call2 = function (context) {
    context.fn = this;
    let args = [...arguments].slice(1);
    context.fn(...args);
    delete context.fn;
}

// 开始 call 函数4.0,当 context 为 null 时
Function.prototype.call2 = function (context1) {
    const context = context1 || window;    // 如果为 null,就指向 window
    context.fn = this;
    let args = [...arguments].slice(1);
    let result = context.fn(...args);
    delete context.fn;
    return result;    // 返回出去,call2 就变成了 context.fn
}

// 测试
obj1 = {
    value: 1,
}

function aa1(name,age) { 
    console.log( { value: this.value,name,age }) 
}

aa1.call2(obj1,'dengsn',12);    // {value: 1, name: "dengsn", age: 12}

/*
    // 处理 context 传来的要指向的对象,或者是null
    const context = context1 || window;    // 如果为 null,就指向 window

    // 处理 args
    let args = [...arguments].slice(1);    // 除去第一个 context1 参数外的所有参数

    superObj2 = {
        age: 18,
        fn: function (...args) {    // 展开传参
            console.log(this.age)
            ...
        }
     } 
     superObj2.fn(...args);
     delete superObj2.fn;
     return superObj2.fn(...args);    // 我不知道为什么要 return 

 return 之后 call2() === superObj2.fn(...args),
那 aa1.call2(obj1,'des',12) === aa1.superObj2.fn(...args,obj1,'des',12);
不懂了,不懂了
*/
// 结束了,距离菜鸡又近了一步



三、组合继承

function SuperFun (name,age) {
    // 公共的属性
    this.name = name
    this.age = age
    this.has = ['头','手','脚']
    this.say = function () {
        console.log(123465)
    }
}
// 公共方法,构建在原型上
SuperFun.prototype.getSuperValue = function () {
    console.log(this.has,'诶,我就是有');
    console.log(this.age+'岁'+this.name+'就是玩');
}
// 原型链继承,是通过 prototype 原型属性的方式继承父类
function SubFun (name,age,address) {
    // 通过拷贝父类属性
    SuperFun.call(this,name,age);
    this.address = address;
}

SubFun.prototype = new SuperFun()  //原型链继承
// console.log(SubFun.prototype.constructor); //指向SuperFun
// 重写SubFun.prototype的constructor属性,指向自己的构造函数SubFun
SubFun.prototype.constructor = SubFun;
// 实例对象1
let zhangsan = new SubFun('zhangsan',18,'浙江宁波');
zhangsan.getSuperValue();    
zhangsan.has.push('小细腿');    
console.log(zhangsan.has);     
// 实例对象2
let fwkt = new SubFun('法外狂徒',18,'浙江宁波');
fwkt.getSuperValue();    
fwkt.has.push('大长腿'); 
console.log(fwkt.has);    

缺点:在使用子类创建实例对象时,其原型上会存在两份相同的属性/方法

四、寄生组合式继承

寄生组合式继承:结合构造函数传递参数和寄生模式实现继承

// 寄生式
function inheritPrototype (subType,superType) {
    // 创建一个对象,缓存父类原型
    let _prototype = Object.create(superType.prototype);
    // 将父类的原型赋值给子类的原型
    subType.prototype = _prototype;
    // 增强对象
    _prototype.constructor = subType;    // 防止因重写原型而失去默认的 constructor 属性
}

// 父类初始化实例属性和原型属性
function SuperFun (name,age) {
    // 公共属性
    this.name = name
    this.age = age
    this.has = ['头','手','脚']
}
// 公共方法写在原型上
SuperFun.prototype.getSuperValue = function () {
    console.log(this.has,'诶,我就是有');
    console.log(this.age+'岁'+this.name+'就是玩');
}
// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubFun (name,age,sex) {
    // 拷贝父类
    SuperFun.call(this,name,age);
    this.sex = sex;
}
// 将父类原型指向子类
inheritPrototype(SubFun,SuperFun);
// 新增子类原型属性
SubFun.prototype.saySex = function () {
    console.log(this.sex);
}
let instance1 = new SubFun('ss',12,'女')
instance1.has.push('aa')
let instance2 = new SubFun('gg',11,'男')
instance2.has.push('bb')
console.log(instance1);
console.log(instance2);

这个是目前最成熟的继承方式,我觉得就是,构造函数式继承,然后再加个把父类原型指向子类原型。。我太困了啊,脑子不转了。

五、ES6类继承 extends

extends 关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。

constructor 表示构造函数,一个类只能有一个构造函数,有多个会报 SyntaxError 错误

如果没有显示指定构造方法,则会添加默认的 constructor 方法

子类继承父类用 super

class Rectangle {
    constructor(w,h){
        this.height = h
        this.width = w
    }
    get area(){
        return this.width*this.height
    }
}
const reactangle = new Rectangle(10,20)
console.log(reactangle.area); //200
console.log(reactangle);
// ------------------------继承--------------------------------
class Square extends Rectangle {
    constructor(length){
        super(length,length)
       // 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
        this.name='square'
    }
    get area(){
        return this.width*this.height
    }
}
const square = new Square(10)
console.log(square.area); //100
console.log(square);

上面是我复制的第一个链接的,我不想写了困了。

extends 的核心代码如下,与寄生组合式继承一样

function _inherits(subType, superType) {
  
    // 创建对象,创建父类原型的一个副本
    // 增强对象,弥补因重写原型而失去的默认的constructor 属性
    // 指定对象,将新创建的对象赋值给子类的原型
    subType.prototype = Object.create(superType && superType.prototype, {
        constructor: {
            value: subType,
            enumerable: false,
            writable: true,
            configurable: true
        }
    });
    
    if (superType) {
        Object.setPrototypeOf 
            ? Object.setPrototypeOf(subType, superType) 
            : subType.__proto__ = superType;
    }
}

/*
     解析一下
     Object.create 有两个参数,第一个就是对象,第二个就是与 Object.defineProperty 一样都是编辑是否可读,是否可写之类的。
    superType && superType.prototype 意思是,如果 superType 有,就用 superType.prototype,是 && 运算
    constructor 指定了 superType.prototype 有一个属性叫 constructor,然后对象里面的就是描述符
    下面那个应该就是让父类原型指向子类原型
   

*/

函数声明和类声明的区别?

函数声明会有变量提升,类声明没有,如果没有声明类就访问会报错。

console.log(obj1);    // VM22125:1 Uncaught ReferenceError: obj1 is not defined
let obj1 = {
    age: 18
}

console.log(obj2);    // f obj2() {age:18}
function obj2() {
    age: 18
}

this 指向问题

this 指向

1、普通函数指向 window

2、定时器方法指向 window

3、构造函数指向 实例对象

4、对象方法中的指向 实例对象

5、原型对象方法中的指向 实例对象

6、匿名函数都是指向 window

function fn1() {
    console.log(this,'诶,我是普通函数的this');
}

setTimeout(function() {
    console.log(this,'诶,我是定时器的this');
},1000)

function Person() {
    console.log(this,'诶,我是构造方法的this');
    this.say = function() {
        console.log(this,'诶,我是对象方法');
    }
}
Person.prototype.sayHi = function () {
    console.log(this,'诶,我是原型对象方法的this');
}

const fwkt = new Person();
fwkt.say();
fwkt.sayHi();

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值