JavaScript学习笔记

参考:https://github.com/stephentian/33-js-concepts

一、调用堆栈

 

1、Call stack(调用堆栈)

参考:https://developer.mozilla.org/en-US/docs/Glossary/Call_stack

(1)当脚本调用函数时,解释器将其添加到调用堆栈,然后开始执行该函数

(2)该函数调用的任何函数都会进一步添加到调用堆栈中,并在到达其调用的位置运行。

(3)当前函数完成后,解释器将其从堆栈中取出并在最后一个代码清单中从中断处继续执行。

(4)如果堆栈占用的空间超过分配给它的空间,则会导致“堆栈溢出”错误。

(5)我们从一个空的调用堆栈开始,每当我们调用一个函数时,它会自动添加到调用的堆栈中,在执行所有代码后,它会自动从调用堆栈中删除。最终,我们得到了一个空堆栈。

例:

 

2、理解js中执行上下文和执行栈

参考:https://juejin.im/post/5ba32171f265da0ab719a6d7

 (1)执行上下文

执行上下文是评估和执行JavaScript代码的环境的抽象概念。每当JavaScript代码在运行的时候,它都是在执行上下文中运行。

执行上下文的类型:

           全局执行上下文:任何不在函数内部打代码都在全局上下文中,它会执行两件事:创建一个全局的window对象(浏览器情况下),并且设置this的值等于这个全局对象。一个程序中只会有一个全局执行上下文。

           函数执行上下文:每当一个函数被调用时,都会为该函数创建一个新的上下文。每个函数都有自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序执行一系列步骤。

           Eval函数执行上下文:执行在eval函数内部的代码也会有属于它自己的执行上下文。

(2)this绑定

例:

运行结果:

(3)词法环境和变量环境

在ES6中,词法环境和变量环境的一个不同就是前者被用来存储函数声明和变量let和只读常量const绑定,而后者只用来存储(var)变量绑定

let命令:

      1)声明变量,类似于var,但是let命令只在所在的代码块内有效

      2)不存在变量提升

例:

运行结果:

      3)暂时性死区:块级作用域

const命令

      1)声明一个只读常量。一旦声明,常量的值就不能改变

      2)只声明不赋值就会报错

      3)不存在常量提升

      4)暂时性死区

      5)只保证常量名指向的地址不变,并不保证该地址的数据不变  

 

3、JavaScript的执行机制 

参考:https://juejin.im/post/59e85eebf265da430d571f89

(1)Promise:异步编程

参考:https://www.cnblogs.com/lunlunshiwo/p/8852984.html

  1、Promise的原理

      Promise有三种状态:pending、fulfilled、rejected

     1)promise对象初始化状态为pending

     2)当调用resolve(成功),会由pending=>fulfilled

     3)当调用reject(失败),会由pending=>rejected

  2、Promise的几种方法

     1)then:then方法用于注册状态变为fulfiled或者rejected时的回调函数,then方法是异步执行的

     2)catch:catch在链式写法中可以捕获前面then中发送的异常

     3)resolve、reject

       Promise.resolve返回一个fulfilled状态的promise对象

       Promise.reject返回一个rejected对象

    4)all:所有状态中一旦有一个状态为rejected,则all的返回值就是rejected;当所有状态为resolve,则all的返回值才是fulfilled

    5)race:与all不同的是,所有状态中只要有一个状态发生改变,promise就会立刻变成相同的状元并执行对应的回调

(2)JavaScript的执行机制 

例:

       //setTimeout是异步可以延迟执行,setTimeout(fn,0)的意思是只要主线程执行栈内的同步任务全部执行完成,栈为空马上就执行
        setTimeout(function(){
            console.log('定时器开始啦!')
        });

        new Promise(function(resovle){
            console.log('马上执行for循环啦!');
            for(var i=0;i<10000;i++){
                i==99 && resovle();
            }
        }).then(function(){
            console.log('执行then函数啦')
        });

        console.log('代码执行结束');

运行结果:

(3)setInterval(fn,ms):每过ms秒,就会有fn进入Event Queue。一旦setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了。

(4)process.nextTick(callback)类似node.js版的"setTimeout",在事件循环的下一次循环中调用 callback 回调函数

例子1:

        setTimeout(function() {
            console.log('setTimeout');
        })

        new Promise(function(resolve) {
            console.log('promise');
            // resolve();
        }).then(function() {
            console.log('then');
        })

        console.log('console');

运行结果:

 

例子2:

        console.log('1');

        setTimeout(function() {
            console.log('2');
            
            new Promise(function(resolve) {
                console.log('4');
                resolve();
            }).then(function() {
                console.log('5')
            })
        })
        
        new Promise(function(resolve) {
            console.log('7');
            resolve();
        }).then(function() {
            console.log('8')
        })

        setTimeout(function() {
            console.log('9');
            
            new Promise(function(resolve) {
                console.log('11');
                resolve();
            }).then(function() {
                console.log('12')
            })
        })

运行结果:

 

 例子3:

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})

输出结果:2 4 3 5

 

二、原始数据

 

1、原始数据

参考:https://developer.mozilla.org/zh-CN/docs/Glossary/Primitive

(1)基本类型

string, number, boolean, null, undefined, symbol(新增)

除null和undefined之外,所有基本类型值都有包裹这个基本类型的等价对象。这个包裹对象的valueOf()方法返回基本类型值

String:字符串基本类型

Number:数值基本类型

Boolean:布尔基本类型

Symbol:字面量基本类型

 

2、JavaScript标准

参考:https://wangdoc.com/javascript/types/number.html

(1)整数和浮点数

JavaScript内部,所有数字都是以64位浮点数形式存储,即使整数也是如此。所以1与1.0是相同的,是同一个数。

例:

        console.log(1===1.0);
        console.log(0.1+0.2);
        console.log(0.3/0.1);
        console.log((0.3-0.2)===(0.2-0.1));
        console.log((0.3-0.2)==(0.2-0.1));

运行结果:

 

(2)数值精度

根据国际标准IEEE754,JavaScript浮点数的64个二进制位:

第1位:符号位,0表示整数,1表示负数

第2位到第12位(共11位,2^11-1,大小范围就是0到2047):指数部分====》决定了数值大小

第3位到第64位(共52位):小数部分(即有效数字)===》决定了数值的精度

IEEE754规定,如果指数部分的值在0到2047之间(不含两个端点),那么有效数字的第一位默认总是1,不保存在64位浮点数之中。也就是说,有效数字这时总是1.xx...xx的形式,其中xx...xx的部分保存在64位浮点数之中,最长可能位52位。因此,JavaScript提供的有效数字最长位53个二进制位。这意味着,绝对值<2^53次方的整数,即-2^53到2^53,都可以精确表示。

例:

        console.log(Math.pow(2, 53));
        console.log(Math.pow(2, 53)+1);
        console.log(Math.pow(2, 53)+3);
        console.log(Math.pow(2, 53)+4);
        console.log(Math.pow(2, 53)+5);

运行结果:

 

由此可知,JavaScript对15位的十进制都可以精确处理。

(3)数值范围

64位浮点数的指数部分的值最大为2047,分出一半表示负数

例:

        console.log(Math.pow(2, 1024));//如果一个数>=2^1024,就会发生“正向溢出”,返回Infinity
        console.log(Math.pow(2, -1023));
        console.log(Math.pow(2, -1024));
        console.log(Math.pow(2, -1075));//如果一个数<2^-1075(指数部分最小值-1023,再加上小数部分52位,就会发生“负向溢出”,会直接返回0)
        console.log(Math.pow(2, -1076));
        console.log(Math.pow(2, -3076));

运行结果:

 

例:6.8e+1:表示6.8x10^1

 JavaScript提供Number对象的MAX_VALUE和MIN_VALUE属性,返回可以表示具体的最大值和最小值

       console.log(Number.MAX_VALUE);
       console.log(Number.MIN_VALUE);

运行结果如下:

 

三、值类型和引用类型

 

1、引用类型

参考:http://www.w3school.com.cn/js/pro_js_referencetypes.asp

(1)Object对象

属性1:constructor:对创建对象的函数的引用(指针)。对于Object对象,该指针指向原始的Object()函数。

属性2:Prototype:对该对象的对象原型的引用。对于所有的对象,它默认返回Object对象的一个实例。

方法1:hasOwnProperty(property):判断对象是否有某个特定的属性。必须用字符串指定该属性。

方法2:IsPrototypeOf(object):判断该对象是否为另一个对象的原型。

方法3:PropertyIsEnumerable:判断给定的属性是否可以用for...in语句进行枚举。

ToString:返回对象的原始字符串表示。对于Object对象,ECMA-262没有定义这个值,所以不同的ECMAScript实现具有不同的值。

方法4:ValueOf():返回最适合该对象的原始值。对于许多对象,该方法返回的值都与ToString()的返回值相同。

(2)Number对象

方法1:toFixed():返回的是具有指定位数小数的数字的字符串表示。

方法2:toExponential():与格式化数字相关的方法,返回的是用科学计数法表示的数字的字符串形式。

方法3:toPrecision(num):根据最有意义的形式来返回数字的预定形式或指数形式。参数用来表示数字的数字总数(不包括指数)

例1:

        var a=new Number(68);
        alert(a.toPrecision(1));   //用一位数字表示68

 

输出结果为7e+1

 

例2:

        var a=new Number(68);
        alert(a.toPrecision(2));

输出结果为68

 

(3)String对象

方法1:charAt():返回的是包含指定位置处的字符的字符串

方法2:charCodeAt():返回的是字符代码

例:

        var a=new String("hello");
        alert(a.charCodeAt(1));

输出结果为101

方法3:concat():把一个或多个字符串连接到String对象的原始值上。该方法返回的是String原始值,原对象不变。

方法4:indexOf():从字符串的开头(位置0)开始检索,返回指定的字串在另一个字符串中的位置,如果找不到子串,则返回-1。

方法5:lastIndexOf():从字符串的结尾开始检索,返回指定的字串在另一个字符串中的位置,如果找不到子串,则返回-1。

方法6:localeCompare(string):对字符串进行排序。返回的值有三种,1:返回负数(String对象按照字母顺序排在参数中的字符串之前);2:返回0(String对象等于参数中的字符串);3:返回正数(String对象按照字母顺序排在参数中的字符串之后)。

方法7:slice(index,end):index必选,end可选,返回值为index---end-1。

方法8:substring():与方法7类似,但是当参数为负数时,slice()方法会用字符串的长度加上参数,substring()方法则将其作为0处理。

例:

        var a = new String("hello world!");
        console.log(a.slice(-3));  //ld!
        console.log(a.substring(-3));  //hello world! 
        console.log(a.slice(3,-4));  //1o wo
        console.log(a.substring(3,-4));  //hel

方法9:toLowerCase()、toLocalelLowerCase()、toUpperCase()和toLocaleUpperCase():字符串大小写转换

toLocalelLowerCase()和toLocaleUpperCase()是基于特定区域实现的。

 

2、js中的值类型和引用类型的区别

参考:https://www.cnblogs.com/leiting/p/8081413.htmlhttp://www.cnblogs.com/onepixel/p/5126046.html

(1)值类型:1、占用空间固定,保存在栈中(当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块占内存里,随着方法的执行结束,这个方法的内存栈也将自然销毁了。)

                        2、保存与复制是值的本身

                        3、使用typeof检测数据的类型(注:无法检测null类型)

                        4、基本数据类型是值类型

(2)引用类型:1、占用空间不固定,保存在堆中(当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用,这个运行时数据区就是堆内存。)

                            2、保存与复制的是指向对象的一个指针

                            3、使用instanceof检测数据类型,返回true或false(注:无法正确区分[])

                            4、使用new()方法构造出的对象是引用型

例:

        //值类型
        var a=100;
        var b=a;
        a=300;
        console.log(b);  //100

        console.log(typeof undefined);  //undefined
        console.log(typeof 'chen');  //string
        console.log(typeof 15);  //number
        console.log(typeof true);  //boolean
        console.log(typeof []);  //object
        console.log(typeof function(){});  //function
        console.log(typeof {});  //object

        console.log(typeof null);  //object

        
        //引用类型
        var x={age:22};
        var y=x;
        y.age=18;
        console.log(x.age);  //18

        console.log([] instanceof Array);  //true
        console.log([] instanceof Object);  //true
        console.log("chen" instanceof String);  //false
        console.log(new String("chen") instanceof String);   //true
        console.log(12 instanceof Number);  //false
        console.log(new Number(15) instanceof Number);  //true
        console.log(true instanceof Boolean);  //false
        console.log(new Boolean(false) instanceof Boolean);  //true

 

(3)类型检测:constructor:检测原理:用对象的构造函数属性来判断对象类型,返回boolean值

当一个函数被定义时,js引擎会为该函数添加prototype属性,然后再在prototype属性上添加一个constructor属性,并指向该函数的应用。

例:

        function fn(){};
        fn.prototype=new Array();
        var f=new fn();
        console.log(f.constructor===fn);   //false
        console.log(f.constructor===Array);  //true

        function tx(){}
        var t=new tx();
        tx.prototype;
        console.log(t.constructor===tx);  //true

        console.log(''.constructor==String);  //true
        console.log(new Number(24).constructor==Number);  //true
        console.log(new Boolean(false).constructor==Boolean);  //true
        console.log(true.constructor==Boolean);  //true
        console.log(new Date().constructor==Date);  //true
        console.log(new Error().constructor==Error);  //true
        console.log([].constructor==Array);  //true
        console.log(document.constructor==HTMLDocument);  //true

 

(4)类型检测:Object.prototype.toString.call():返回一个内部属性,其格式为[Object,对象类型]

例:

        console.log(Object.prototype.toString.call(''));  //[object String]
        console.log(Object.prototype.toString.call(123));  //[object Number]
        console.log(Object.prototype.toString.call(true));  //[object Boolean]
        console.log(Object.prototype.toString.call(Symbol()));  //[object Symbol]
        console.log(Object.prototype.toString.call(undefined));  //[object Undefined]
        console.log(Object.prototype.toString.call(null));  //[object Null]
        console.log(Object.prototype.toString.call(new Function()));  //[object Function]
        console.log(Object.prototype.toString.call(new Date()));  //[object Date]
        console.log(Object.prototype.toString.call([])); //[object Array]
        console.log(Object.prototype.toString.call(new RegExp()));  //[object RegExp]
        console.log(Object.prototype.toString.call(new Error()));  //[object Error]
        console.log(Object.prototype.toString.call(document));  //[object HTMLDocument]
        console.log(Object.prototype.toString.call(window));  //[object Window]

 

四、闭包

 

1、闭包

参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures

闭包:由函数以及创建该函数的词法环境的组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量。

例:

        function makeAdder(x){
            return function(y){
                return x+y;
            };
        }

        var add1=new makeAdder(5);
        console.log(add1);  //f(y){return x+y;}
        var add2=new makeAdder(10);

        console.log(add1(2));   //7
        console.log(add2(2));   //12

解析:add1和add2都是闭包,它们享有相同的函数定义,但是保存了不同的词法环境。在add1环境中,x为5,在add2环境中,x则为10。

(1)用闭包模拟私有方法

私有方法不仅仅有利于限制对代码的访问,还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。

例:模块模式:用闭包来定义公共函数,并令其可以访问私有函数和变量。

            var counter=(function(){
            var privateCounter=0;
            function changeBy(val){
                privateCounter+=val;
            }
            return {
                increment:function(){
                    changeBy(1);
                },
                decrement:function(){
                    changeBy(-1);
                },
                value:function(){
                    return privateCounter;
                }
            }
        })();

        console.log(counter.value());    //0
        counter.increment();
        counter.increment();
        console.log(counter.value());    //2
        counter.decrement();
        console.log(counter.value());    //1

一个词法环境,为三个函数所共享:counter.increment,counter.decrement,counter.value。该共享环境创建于一个立即执行的匿名函数体内。这个环境包含两个私有项,名为privateCounter的变量和名为changeBy的函数,这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。

(2)循环中创建闭包

例:需求:当鼠标在不同的文本输入框中聚焦时提示文字进行相应的改变

    <p id="point">Helpful notes will appear here</p>
    <p>E-mail:<input type="text" id="email" name="email"></p>
    <p>Name:<input type="text" id="name" name="name"></p>
    <p>Age:<input type="text" id="age" name="age"></p>

 

方案1:使用更多的闭包

        function showPoint(point){
            document.getElementById('point').innerHTML=point;
        }
        function pointCallback(point){
             return function(){
                 showPoint(point);
             };
         }
        function setupPoint(){
            var pointText=[
                {'id':'email','point':'Your e-mail address'},
                {'id':'name','point':'Your full name'},
                {'id':'age','point':'Your age(you must be over 18)'}
            ];
            for(var i=0;i<pointText.length;i++){
                 var item=pointText[i];
                 document.getElementById(item.id).onfocus=pointCallback(item.point);
                
            }
        }
        setupPoint();

 

方案2:使用匿名闭包

        function showPoint(point){
            document.getElementById('point').innerHTML=point;
        }
        
        function setupPoint(){
            var pointText=[
                {'id':'email','point':'Your e-mail address'},
                {'id':'name','point':'Your full name'},
                {'id':'age','point':'Your age(you must be over 18)'}
            ];
            for(var i=0;i<pointText.length;i++){
                
                (function(){
                    var item=pointText[i];
                    document.getElementById(item.id).onfocus=function(){
                        showPoint(item.point);
                    };
                })();  //马上把当前循环项的item与事件回调相关联起来
            }
        }
        setupPoint();

方案3:使用let而不用var

        function showPoint(point){
            document.getElementById('point').innerHTML=point;
        }
   
        function setupPoint(){
            var pointText=[
                {'id':'email','point':'Your e-mail address'},
                {'id':'name','point':'Your full name'},
                {'id':'age','point':'Your age(you must be over 18)'}
            ];

            for(var i=0;i<pointText.length;i++){
                let item=pointText[i];
                document.getElementById(item.id).onfocus=function(){
                    showPoint(item.point);
                }
            }
        }
        setupPoint();

每个闭包都绑定了块级作用域的变量,这意味着不再需要额外的闭包。

闭包优缺点:优点:主要是为了设计私有的方法和变量,可以避免全局变量的污染。

                      缺点:会常驻内存,容易造成内存泄漏。

 

五、js高阶函数

 

1、js高阶函数

js的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

参考:https://juejin.im/post/5835808067f3560065ed4ab2

        var arr=[1,2,3,4,5];
        //forEach,让数组中每一项做一件事        
        arr.forEach(function(item,index){
            console.log(item);  //1 2 3 4 5
        })

        //map,让数组通过某种计算产生一个新数组
        var newArr=arr.map(function(item,index){
            return item*2;
        });
        console.log(newArr);  //[2,4,6,8,10]

        //filter,筛选出数组中符合条件的项,组成新数组
        var newArr1=arr.filter(function(item,index){
            return item>3;
        });
        console.log(newArr1);  //[4,5]

        //reduce,让数组中的前项和后项做某种计算,并累积最终值
        var result1=arr.reduce(function(prev,next){
            return prev+next;
        });
        console.log(result1);    //15(1+2+3+4+5)

        //some,检测数组中是否有某些项符合条件
        var result2=arr.some(function(item,index){
            return item>1;
        });
        console.log(result2);   //true

 

六、面向对象编程

 

1、js面向对象编程之:封装、继承、多态

参考:https://juejin.im/post/59396c96fe88c2006afc2707

(1)封装

封装:将属性和方法组成一个类的过程。在es5中,没有class的概念,但是可以模拟class的概念,类就是保存了一个函数的变量。

    1、通过构造函数添加:

       (1)首字母大写(大驼峰命名);(2)内部使用this;(3)使用new生成实例


        function Cat(name,color){
            this.name=name;  //this总是指向当前对象,所以通过this添加的属性和方法只是在当前对象上添加,是该对象自身拥有的
            this.color=color;//(接上)所以我们实例化一个新对象的时候,this指向的属性和方法都会得到想应的创建
            this.eat=function(){
                console.log('吃老鼠');
            }
        }
        var cat1=new Cat('tom','red');
        cat1.eat();
        console.log(cat1.name+' is '+cat1.color);

  2、通过原型prototype

    js规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。

    通过原型继承的方法并不是自身的,我们要在原型链上一层一层的查找,这样查找的好处是只在内存中创建一次,实例化的对象都是指向这个prototype对象

   弊端:实例化的对象的原型都是指向同一内存地址,改动其中的一个对象的属性可能会影响到其他对象

        function Dog(name,color){
            this.name=name;
            this.color=color;
        }
        Dog.prototype.type="犬科动物";
        Dog.prototype.behaviour=function(){
            console.log("对人类很忠诚");
        }
        var dog1=new Dog("花花","白色");
        var dog2=new Dog("欢欢","黄色");
        console.log(dog1.type);  //犬科动物
        dog2.behaviour();   //对人类很忠诚

  3、在类的外部通过语法添加

 

(2)js中的private、public、protected

private:在函数内部直接定义的属性和方法都是私有的

public:通过new关键词实例化时,this定义的属性和变量都会被复制一遍,所以通过this定义的属性和方法就是共有的。

             通过prototype创建的属性在类的实例化之后类的实例化对象也是可以访问到的,所以也是共有的。

protected:在函数内部定义的成员外部不可使用但可以继承给子类 

 

(3)继承(基于封装的特性来实现)

继承就是子类可以使用父类的所有功能,并且对这些功能进行扩展。继承的过程,就是从一般到特殊的过程。

   1、类式继承

类式继承就是使用的原型的方式,讲方法添加在父类的原型上,然后子类的原型时父类的一个实例化对象

例:

         //声明父类
        var SuperClass=function(){
            var id=1;
            this.name=['javascript'];
            this.superValue=function(){
                console.log('superValue is true');
                console.log(id);
            }
        };
        //为父类添加共有方法
        SuperClass.prototype.getSuperValue=function(){
            return this.superValue();
        };
        //声明子类
        var SubClass=function(){
            this.subValue=function(){
                console.log('this is subValue');
            }
        };
        //继承父类
        SubClass.prototype=new SuperClass();
        //为子类添加共有方法
        SubClass.prototype.getSubValue=function(){
            return this.subValue();
        };

        //声明子类对象实例
        var sub=new SubClass();
        var sub2=new SubClass();

        sub.superValue();   //superValue is true   1
        sub.getSubValue();  //this is subValue

        console.log(sub.id);   //undefined
        console.log(sub.name);   //['javascript']

        sub.name.push('java');
        console.log(sub2.name);  //['javascript','java']

总结:使用类继承的方法,如果父类的构造函数中有引用类型,就会在子类中被所有实例共用,因此一个子类的实例如果更改了这个引用类型,就会影响到其它子类的实例。

  2、构造函数继承

构造函数继承的核心思想就是SuperClass.call(this,id),直接改变this的指向,使通过this创建的属性和方法在子类中复制一份,因为时单独复制的,所以各个实例的子类互不影响,但是会造成内存浪费的问题。

        //声明父类
        function SuperClass1(id){
            var name='javascript';
            this.books=['javascript','java','typescript'];
            this.id=id;
        }
        //声明父类原型方法
        SuperClass1.prototype.showBooks=function(){
            console.log(this.books);
        }
        //声明子类
        function SubClass1(id){
            SuperClass1.call(this,id);
        }
        //创建子类实例
        var subClass1=new SubClass1(10);
        var subClass2=new SubClass1(15);

        //创建父类实例
        var superClass1=new SuperClass1(2);

        console.log(subClass1.books);   //['javascript','java','typescript']
        console.log(subClass2.id);   //15
        console.log(subClass1.name);   //undefined
        superClass1.showBooks();   //['javascript','java','typescript']
        subClass2.showBooks();   //报错,因为showBooks方法里的this指的是SuperClass1

   3、组合式继承

组合式继承就是汲取类式继承和构造函数继承两者的优点,既可以使子类能够访问父类原型的方法,又使得每个实例化的子类互不影响。

缺点:父类的构造函数会被创建两次(call()的时候一遍,new的时候又一遍)

        var SuperClass2=function(name){
            this.name=name;
            this.books=['javascript','java','typescript'];
        };
        //声明父类原型上的方法
        SuperClass2.prototype.showBooks=function(){
            console.log(this.books);
        };
        //声明子类
        var SubClass2=function(name){
            SuperClass2.call(this,name);
        };
        //子类继承父类(链式继承)
        SubClass2.prototype=new SuperClass2();
        //实例化子类
        var subClass3=new SubClass2('vue');
        var subClass4=new SubClass2('angular');
        subClass4.showBooks();  //['javascript','java','typescript']
        subClass3.books.push('react'); //['javascript','java','typescript']   (push不改变原数组)
        console.log(subClass3.books);  //['javascript','java','typescript','react']
        console.log(subClass4.books);  //['javascript','java','typescript']

    4、寄生组合继承

寄生式继承就是对原型继承的二次封装,使得子类的原型等于父类的原型。并且在第二次封装的过程中对继承的对象进行了扩展。

        //原型式继承
        function inheritObject(o){
            //声明一个过渡函数对象
            function F(){}
            //过渡对象的原型继承父对象
            F.prototype=o;
            //返回一个过渡对象的实例,该实例的原型继承了父对象
            return new F();
        }
        //寄生式继承
        function inheritPrototype(sub3,super3){
            //复制一份父类的原型保存在变量中,使得p的原型等于父类的原型
            var p=inheritObject(sub3,super3);
            //修正因为重写子类原型导致子类constructor属性被修改
            p.constructor=sub3;
            //设置子类的原型
            sub3.prototype=p;
        }
        //定义父类
        var SuperClass3=function(name){
            this.name=name;
            this.books=['javascript','java','typescript'];
        };
        //定义父类原型方法
        SuperClass3.prototype.getBooks=function(){
            console.log(this.books);
        };
        //定义子类
        var SubClass3=function(name){
            SuperClass3.call(this,name);
        };
        inheritPrototype(SubClass3,SuperClass3);
        var subClass5=new SubClass3('angularjs');
        // subClass5.getBooks();
        console.log(subClass5.books);  //['javascript','java','typescript']

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值