JavaScript继承

JavaScript继承

 

 

 

 

 

0污染

模块化

实现继承的方式

原型链继承

构造继承

实例继承

拷贝继承(不推荐)

组合继承

寄生组合继承

 

 

 

0污染

什么是污染?

当你使用一个全局变量时,就要考虑污染问题,因为一旦你占用了这个名称,其他人在想使用这个名称就会和你发生冲突,这就是污染。

解决污染的方案:

 使用函数的方式封装

// 将变量封装在函数内
function f() {
    var dt = new Date()
    console.log(+dt); // 1552634315865
}
f()

 

 使用对象的方式封装

// 将变量作为对象的属性封装
var Module = function () {
    this.dt = new Date()
}

console.log(new Module().dt);

 

 在立即函数(闭包)内封装

(function () {
    var name = 'maoamo'
    console.log(name);
})(); // 写立即函数一定不能少了最后的;

// ===================================================================
// 为什么不能少了最后的分号?如果不写呢?
(function () {
    console.log(123);
})

(function(){
    console.log(456);
})
// 上面两个立即函数在js编译的时候会变成下面这个样子:
(function() {
    console.log(123);
})(function(){console.log(456);}) // 也就是说第二个立即函数变成了第一个函数的参数

// 在看一个例子:==============================================
<button id="btn">点击我</button>
<script>
    var btn = document.getElementById('btn');
    btn.onclick = function() {
        console.log(123);
    }
    (function () {
        console.log(456);
    })()
</script>
// 上面的代码直接打印出123然后报错:Uncaught TypeError: (intermediate value)(...) is not a function
// 原因就是因为上面的代码最后少了;程序变成了类似下面这样:
var btn = document.getElementById('btn');
btn.onclick = function() {
    console.log(123);
}(function () {console.log(456);})  // 立即函数变成了上一个函数的参数,导致上一个函数变成了立即函数
()

// 你看上面这个函数
function(){}() // 这个立即函数单独写是不可以的,但是经过赋值操作后这个表达式变得可运行了
// 原因就是赋值操作,先将函数表达式赋给变量,然后在执行变量

 

 

 立即函数(闭包),将属性封装在全局变量中

使用闭包传参(全局变量),使用全局变量封装属性,会提升效率,因为少了变量提升。

(function (w) {
    var name = '毛毛';
    var getName = function () {
        return name;
    };
    var setName = function (name_) {
        name = name_
    };
    // 一般使用json,将所有内部东西封装起来,赋值给全局变量
    w.json = {
        name:name,
        getName:getName,
        setName:setName,
    };
})(window); // 写立即函数一定不能少了最后的;

       对比从外部直接使用全局变量封装:考虑一丁点效率问题,会出现变量提升。

 

(function () {
    var name = '毛毛';
    var getName = function () {
        return name;
    };
    var setName = function (name_) {
        name = name_
    };
    // 一般使用json,将所有内部东西封装起来,赋值给全局变量
    window.json = {
        name:name,
        getName:getName,
        setName:setName,
    };
})(); // 写立即函数一定不能少了最后的;

 

 

 

 

 立即函数(闭包),使用return,将封装的属性返回

var f = (function () {
    var name = '毛毛';
    var getName = function () {
        return name;
    };
    var setName = function (name_) {
        name = name_
    };
    return {
        name:name,
        sex:sex,
        getName:getName,
        setName:setName,
    };
})(); // 写立即函数一定不能少了最后的;
console.log(f);

 

模块化

演变:全局函数的方式->封装对象的方式->私有空间的划分->模块的扩展和维护

//1.全局函数的形式

<input type="text" id="x">
<select name="" id="fa">
    <option value="+">+</option>
    <option value="-">-</option>
</select>
<input type="text" id="y">
<button id="equal">=</button>
<input type="text" id="result">
<body>
<script>
    var equal = document.querySelector('#equal');

    var add = function (x, y) {
        return x + y;
    }

    var subtract = function (x, y) {
        return x - y;
    }

    equal.onclick = function () {
        var x = parseInt(document.querySelector('#x').value);
        var y = parseInt(document.querySelector('#y').value);
        var result = document.querySelector('#result');
        var option = document.querySelector('#fa option:checked');
        switch (option.value) {
            case '+':
                result.value = add(x, y)
                break;
            case '-':
                result.value = subtract(x, y)
                break;
        }
    }
</script>

 

缺点:污染太多全局变量,对外暴露了所有变量。

// 2.封装对象的方式
<script>
    var calc = function () {

    }

    calc.prototype = {
        add: function (x, y) {
            return x + y;
        },
        subtract: function (x, y) {
            return x - y;
        }
    }
    /*==============上面是封装===============*/
    
    /*业务...*/
</script>

 

缺点:对外暴露了所有内部属性/方法。

//3.私有空间划分
<script>
    var cc =  (function () {
        var add = function (x, y) {
            return x + y;
        }
        var subtract = function (x, y) {
            return x - y;
        }
        var ceilAdd = function (x, y) {
            return ceil(x) + ceil(y)
        }
        var ceil = function (x) { // ceil仅供内部使用,是私有的
            return Math.ceil(x);
        }
        return {
            add: add,
            subtract: subtract,
            ceilAdd: ceilAdd
        }
    })();
    /*==============上面是封装===============*/

    /*业务...*/
</script>

缺点:不利于扩展和维护

// 4.模块的扩展和维护
<script>
    (function (calc) {
        calc.add = function (x, y) {
            return x + y;
        }
        calc.subtract = function (x, y) {
            return x - y;
        }
        calc.ceilAdd = function (x, y) {
            return ceil(x) + ceil(y)
        }
        var ceil = function (x) { // 内部方法,并未封装在对象中
            return Math.ceil(x);
        }
        window.calc = calc; // 必须在重新赋给全局({}情况)
    })(window.calc || {});
    /*==============上面是封装===============*/

    // 外界可直接扩展[只支持扩展,外界不能更改内部方法]
    window.calc.remander = function (x, y) {
        return x % y;
    }
</script>

遵循开闭原则,支持扩展和维护。

 

在什么场景下使用模块化编程:

1、业务复杂 2、重用逻辑非常多 3、扩展性要求较高

 

 

 

 

实现继承的方式

// 定义一个动物类
        function Animal(name) {
            // 属性
            this.name = name || 'Animal';
            // 实例方法
            this.sleep = function () {
                console.log(this.name + '正在睡觉')
            }
        }
        // 原型方法
        Animal.prototype.eat = function (food) {
            console.log(this.name + '正在吃:' + food);
        }

 

原型链继承

// 1.原型链继承[核心:将父类的实例作为子类的原型]
        function Cat() {

        }
        Cat.prototype = new Animal();
        Cat.prototype.name = 'cat';

        // Test Code
        var cat = new Cat();
        console.log(cat.name);
        cat.eat('fish')
        cat.sleep()
        console.log(cat instanceof Animal); // true
        console.log(cat instanceof Cat); // true

 

特点:

1、非常纯粹的继承关系,实例是子类的实例,也是父类的实例。

2、父类新增原型方法/原型属性,子类都能访问到。

3、简单易于实现。

 

缺点:

1、要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放在构造中。

2、无法实现多继承

3、来自原型对象的引用属性是所有实例共享的

4、创建子类实例时,无法向父类构造函数传参

 

 

构造继承

// 核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

function Cat(name) {
            Animal.call(this);
            this.name = name || 'Tom';
        }

        // Test Code
        var cat = new Cat();
        console.log(cat.name);
        // cat.eat('fish') 报错
        cat.sleep()
        console.log(cat instanceof Animal); // false
        console.log(cat instanceof Cat); // true

 

特点:

1、解决了原型链继承中,子类实例共享父类引用属性(指定是Animal.prototype.eat)的问题。

2、创建子类实例时,可以向父类传递参数。

3、可以实现多继承(call多个父类对象)。

 

缺点:

1、实例并不是父类的实例,只是子类的实例。

2、只能继承父类的实例属性和方法,不能继承原型属性/方法

3、无法实现函数复用,每个子类都有父类实例函数的副本,影像性能。

 

 

 

实例继承

        function Cat(name) {
            var instance = new Animal();
            instance.name = name || 'Tom';
            return instance;
        }

        // Test Code
        var cat = new Cat();
        console.log(cat.name);
        cat.eat('fish') 
        cat.sleep()
        console.log(cat instanceof Animal); // true
        console.log(cat instanceof Cat); // flase

 

特点:

1、不限制调用方式,不管是new子类()还是子类(),返回的对象具有相同的效果。

 

缺点:

1、实例是父类的实例,不是子类的实例。

2、不支持多继承。

 

 

拷贝继承(不推荐)

        function Cat(name) {
            var instance = new Animal();
            for (var p in instance) {
                Cat.prototype[p] = instance[p]
            }
            Cat.prototype.name = name || 'Tom';
        }

        // Test Code
        var cat = new Cat();
        console.log(cat.name);
        cat.eat('fish')
        cat.sleep()
        console.log(cat instanceof Animal); // flase
        console.log(cat instanceof Cat); // true

 

特点:

1、支持多继承

 

缺点:

1、效率较低,内存占用高(因为要拷贝父类的属性)

2、无法读取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)

 

 

组合继承

    // 通过调用父类构造,继承父类属性并保留传参的优点,然后通过将
        // 父类实例作为子类原型,实现函数复用
        function Cat(name) {
            Animal.call(this);
            this.name = name || 'Tom';
        }
        Cat.prototype = new Animal();

        // Test Code
        var cat = new Cat();
        console.log(cat.name);
        cat.eat('fish')
        cat.sleep()
        console.log(cat instanceof Animal); // true
        console.log(cat instanceof Cat); // true

 

特点:

1、弥补了构造继承的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法。

2、即是子类的实例,也是父类的实例。

3、不存在引用属性共享问题。

4、可传参。

5、函数可复用

 

缺点:

1、调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

 

 

寄生组合继承

// 通过寄生方式,砍掉父类的实例属性,这样在调用两次父类的构造的时候,
        // 就不会初始化两次实例方法/属性,避免组合继承的缺点
        function Cat(name) {
            Animal.call(this);
            this.name = name || 'Tom';
        }
        (function () {
            // 创建一个没有实例方法的类
            var Super = function () { };
            Super.prototype = Animal.prototype;
            // 将实例作为子类的原型
            Cat.prototype = new Super();
        })();

        // Test Code
        var cat = new Cat();
        console.log(cat.name);
        cat.eat('fish')
        cat.sleep()
        console.log(cat instanceof Animal); // true
        console.log(cat instanceof Cat); // true

 

特点:

1、最佳实现继承方式

 

缺点:

1、比较麻烦

 

posted @ 2020-08-10 10:21  刘呆哗  阅读(140)  评论(0编辑  收藏  举报