JS基礎之繼承和This

繼 承

原型繼承

  • 讓類B的原型指向類A的實例,那麼以後類B的實例既可以調取類A實例的私有屬性,也可以調取類A實例的公有屬性,那這種繼承方式就是原型繼承
    • 原型繼承:繼承私有和公有
      • 通過改變prototype的指向,使其指向其他實例
       function A(){
           this.getX = function(){console.log('恭喜發財')}
       };
       A.prototype.getY = function(){
           console.log('真好')
       };
       function B(){}
       B.prototype = new A;
       let f = new B;

image.png

中間類繼承

  • arguments類數組,不是一個數組;雖然不是Array的實例,但是我們可以手動把arguments的__proto__指向Array的原型,那這樣arguments就可以使用Array原型上的方法了,這就是中間類繼承
    • 只能繼承公有屬性
      • 通過自己的__proto__指向類的prototype(原型)
        function fn(){
           console.log(arguments instanceof Array)
           arguments.__proto__ = Array.prototype;
           console.log(arguments.push(23))
            console.log(arguments) 
        }
        fn(1,2,3,4,5)

call繼承

  • call繼承:私有屬性
    • 改變類裏的this指向
    • 在類B中,調用了類A,並且通過call改變了類A中的this指向,使其指向B的實例;這樣類B創建的實例就具有類A的私有屬性;這種繼承就是call繼承;
    • call方法在Function的原型上
        /* 
        原型繼承:繼承私有和公有
        中間類繼承:公有
        call繼承:私有屬性
        */
        function A(){
            this.x =10 
        }
        A.prototype.getX = function(){
            console.log('萬事如意')
        }

        function B(){
            // this->當前實例
            /* 
            類B當做構造函數執行時,此時的this是當前實例,
            */
            this.a = 20
            A.call(this)  // 把函數A當做普通函數執行,並且把A的this指向了類B的實例

        }
 //call繼承讓類B繼承了類A的私有屬性,但是不能使用類A的公有屬性
// 類B的所有實例都可以使用類A的私有屬性
        let f = new B;
        console.log(f)
 		f.getX()

寄生組合繼承

  • 繼承公有和私有
    • 創建一個空對象,讓空對象的__proto__指向(傳第一個參)類A的原型,在把這個空對象賦值給類B的原型
    • 使用call繼承繼承了私有屬性,Object.create繼承了公有屬性,這種繼承方式就是寄生組合繼承;
    • 爲了防止修改B的原型時,修改了A的原型,所以使用Object.create的方法
        // Object.create(context): // 創建一個空對象,讓對象的__proto__指向你傳的第一個參數
        let obj = {name:3,getX:function(){console.log(11)}}
        let o = Object.create(obj)
        console.log(o)
  • 實例
        // Object.create(context): // 創建一個空對象,讓對象的__proto__指向你傳的第一個參數
        // let obj = {
        //     name: 3,
        //     getX: function () {
        //         console.log(11)
        //     }
        // }
        // let o = Object.create(obj)
        // console.log(o);
        // console.log(o.__proto__ === obj) // true
        // 創建一個空對象,讓空對象的__proto__指向你傳遞的第一個參數(obj)


        function A(){
            this.a = 10
        }
        A.prototype.getX = function(){
            console.log('恭喜發財')
        }

        function B(){
            /* 
            函數B以構造函數的身份運行
            那類B中的this指向當前實例
            */
            this.x =20;
            A.call(this) // 讓函數A以普通函數身份運行,而且把函數A中的this指向了類B的實例
          // 繼承私有屬性
        }
        B.prototype = Object.create(A.prototype);// 繼承公有屬性;
        // 創建一個空對象,讓空對象的__proto__指向類A的原型,在把這個空對象賦值給類B的原型
        let f = new B;
        f.__proto__.getY = function(){
            console.log(333)
        };
        let m = new A;
        // m.getY()
        console.log(f)
        //call繼承讓類B繼承了類A的私有屬性,然是不能繼承類A的公有屬性
        // console.log(f)
        // f.getX() // 報錯
        // f.a // 可以取到

在這裏插入圖片描述

Class繼承

  // ES6中class創造出來的類不能當做普通函數執行
        class A {
            constructor(q) {
                this.x = q;
            }
            getX() {
                console.log(this.x)
            }
        }
        
        // ES6中的繼承
        class B extends A {
            constructor(name) {
                // 子類繼承父類,可以不寫constructor,但是你要是一旦寫了,
              那在constructor裏第一句話就要寫super()
                // 你要是不寫constructor,那瀏覽器會默認創建一個constructor(...arg){
                //     super(...arg)
                // }
                super(200) // A.call(this, 200) 把父類當做普通函數執行,給方法傳遞參數,
              讓方法中的this是子類的實例

                this.y = 100;
            }
            getX() {
                console.log(this.y)
            }
        }
        
        B.prototype = Object.create(A.prototype); // class定義的類不能改原型重定向
        let f = new B(100);
        console.log(f)
  • B.prototype = Object.create(A.prototype); QQ圖片20191223154557.png

This

this詳解

  • 跟函數執行有關係
  • 他是js中的關鍵字,有特殊的特殊意義
  • 他就是函數的執行體,誰執行函數this就是誰
  • 不能給this直接賦值
  • this傳的是指針,空間地址
  • this是個關鍵字;在特殊的情景下,this有特殊的意義;this不能用等號對其直接修改

this的幾種情況

1.在全局作用於下,this就是window
2.在函數執行時,看執行函數前有沒有".",如果有點,那點前面是誰,this就是誰,如果沒有點,那this就是window
3.自執行函數裏的this是window
4.給元素事件行爲綁定方法,方法裏的this指向被綁定的元素本身
5.回調函數裏的this一般指向window
6.實例的私有屬性或者公有屬性裏的this一般指向當前實例
7.構造函數裏的this是當前實例
8.箭頭函數沒有this,要是在箭頭函數裏使用this,就看他上一級作用域的this,不能用call更改,不能被new
9.call、apply、bind可以改變this的指向

        //2給元素的事件綁定的函數中的this,指向了當前被點擊的那個元素
        box.onclick = function () {
          // this : 對象
        console.dir(this === box); //空間地址相同
        box.style.color="red";
        this.style.color = "red";
        this=100;//this 不能放在等號左邊
        }
        box1.onclick = function () {
            //this : 對象
            console.dir(this);
        }

		//5 回調函數中的this 一般指向window
        setTimeout(function () {
            console.log(this);

        }, 1000)

        var ary = [1, 2, 3, 4];
        ary.map(function () {
            console.log(this);

        }) 
		//回調函數的特殊
		//回調函數但是this更改了
        function A(){
            console.log(this);
        }
        function B(a){
            // a();
            var obj ={a:a};
            obj.a();
        }
        B(A);


/*     //  var let = 3;
            //  console.log(let)

                // console.log(this)
                // console.log(this === window)
                // window.a = 12;
                // console.log(this.a) */

        /*   // function fn(){
          //     console.log(this)
          // }
          // fn()


          // var age = 15;
          // var obj = {
          //     age: 13,
          //     name: function(){
          //         console.log(this.age)
          //     }
          // }
          // obj.name() // this是obj
          // var f = obj.name;
          // f()  // this是window*/

        // (function(){
        //     console.log(this) // window
        // })()

        // box.onclick = function(){
        //     console.log(this) 
        // }
        // var ary = [1, 2];
        // ary.map((a,b)=>{
        //     console.log(this) // window
        // })
        // ary.sort((a,b)=>{
        //     console.log(this); // window
        //     return a-b
        // })

        // function fn(a){
        //     a()
        // }
        // fn(function(){
        //     console.log(this)
        // })

        // setTimeout(()=>{
        //     console.log(this) // window
        // }, 2000)


        /*         var num = 100;
                var obj = {
                    num: 2,
                    fn: function () {
                        var num = 1;
                        console.log(this) //window
                        (function (num) { // 100
                        // this-> window
                            console.log(this.num + num);//200
                        })(this.num)
                        // this->window
                    }
                }
                // obj.fn(); 
                var f = obj.fn;
                f() */


        var num = 1; // 1 2
        var obj = {
            num: 0,
            fn: function () {
                num = 1;
                // this=>obj
                (function (num) {  // 0 1
                // this=>window
                    ++this.num;
                    num++;
                    console.log(num)
                })(this.num) 
            }
        }
        obj.fn(); 
        console.log(window.num, obj.num);

改變this指向的方法

每一個函數都是Function的實例,所以每一個函數都可以調取Function原型上的方法,call,apply,bind,他們三個都可以改變函數裏的this指向

call

  • call繼承:私有屬性
  • fn通過__proto__屬性找到當前所屬類的原型(Function的原型)上的call方法
  • 所有函數可以獲取到call
  • 讓call方法執行,並且給call傳遞實參
  • 在call方法執行的同時,也讓fn執行,並且把fn的this指向了第一個參數
  • 注意事項
    • 在嚴格模式下,如果call不傳參或者傳undefined,那fn的this就是undefined,如果傳null,那fn的this就是null
    • 在非嚴格模式下,如果call不傳參或者傳undefined或者傳null,那fn的this都是window
    • call的第一個參數是fn的this指向,從第二個開始,就是fn的正常參數了
  • fn通過__proto__先找到Function原型中的call方法,讓call方法執行,call運行時,改變了call的this的this指向,fn中的this指向call的第一個參數,並且讓call中this執行;
        function fn1(){
            console.log(100);
            console.log(this);
        }
        function fn2(){
            console.log(200);
        }
        //fn.call(1)
        fn1.call.call.call.call(fn2);
        // 1. fn1.call.call.call -->this--> fn2;
        // 2. fn1.call.call.call()

        // 1.fn2--> this 沒有變
        // 2.fn2();

        // 1.先執行後面的call方法(這個call方法中的this是fn1.call);
是改變fn1.call中的this指向fn2;並且讓fn1.call運行;
        // 2. 當fn1.call運行時,改變fn.call中的this的this指向沒有發生改變,
繼續讓fn1.call中this執行,也就是讓fn2運行;
-------------------------------------------------------
    function B(){

    } 
    console.log(B.name);// 對象

    function A(){

    }
    A.call()
    function B(){

    }
    A.call.call.call(B);// B執行,並且B中的this一定指向window
    最後一個call執行時, 把A.call.call中的this改成了函數B;並且讓A.call.call執行;A.call.call執行時,把函數Bthis改成window,並且讓B執行;
      //  "use strict"   
      function fn(n,m){
          console.log(this)
      }
      let obj = {
          name:3
      }
    //   fn(12,13)
    // 實現把函數裏的this改爲obj
    //   obj.fn = fn;
    //   obj.fn();
    //   delete obj.fn;
      fn.call(undefined)

call方法封裝

        function myCall(context, ...arg) { // 收縮運算符
          // arg接收的是傳遞的從第二個開始的實參
            // this->fn    context->obj
            let res = null; // 初始化一個實例的返回值
            context = context || window 
          // 處理傳參的特殊情況,如果傳的是空、null和undefined,context的值就是window
            context.$fn = this // 把當前實例放到對象裏
            res = context.$fn(...arg); // 讓this執行(讓當前實例執行)
            delete context.$fn // 在對象裏刪除那個實例
            return res;// 把this執行之後的返回值return 出去
        }
        Function.prototype.myCall = myCall;
        function fn() {
            console.log(this)
            return 1
        }
        let obj = {
            name: 3
        }
        //    console.log(fn.call(1)) 
        console.log(fn.myCall(obj, 12, 23))
        console.log(obj)

連call面試題

  • 如果有兩個及以上call,那最後就是執行傳入的參數
<script>
        function fn1() {console.log(1)}
        function fn2() {console.log(2)}
        // fn1.call(fn2); //1
        // fn1.call.call(fn2); //2 不管前邊有多少call,他執行的是最後一個call方法
        // Function.prototype.call(fn1); // 不輸出
        // Function.prototype.call.call(fn1); // 1
        Function.prototype.call.call.call.call(fn1);
        function myCall(context){
            // 如果有兩個及以上call,那最後就是執行傳入的參數,傳入的參數的this指向window
        
            
            /* 
            fn1.call(fn2);
            context->fn2  this->fn1
            context.$fn = this  ->fn2.$fn = fn1
            context.$fn()  ->fn1()
            */

            /* 
            fn1.call.call(fn2)
            context ->fn2  this-> fn1.call ->call
            context.$fn = this ->fn2.$fn = call
            context.$fn()  ->fn2.$fn()  ->call()
            第二次執行
            context->window  this->fn2
            context.$fn = this ->window.$fn = fn2;
            context.$fn()  ->window.$fn()  ->fn2()
            */

            /* 
            Function.prototype.call(fn1)
            context ->fn1   this->Function.prototype【原型】
            context.$fn = this ->fn1.$fn = 【原型】
            context.$fn() ->fn1.$fn() ->【原型】()
            */

           /* 
           Function.prototype.call.call(fn1)
           context ->fn1  this -> Function.prototype.call ->call
           context.$fn = this  ->fn1.$fn = call
           context.$fn() ->fn1.$fn()  ->call()
           第二次執行
           context->windwo  this->fn1
           context.$fn = this ->window.$fn = fn1;
           context.$fn()  ->window.$fn  ->fn1()
           */
            context.$fn = this;
            context.$fn()
        }
    </script>

apply

  • 改變this指向:他和call方法一樣,只不過傳參不同,第二個參數必須是數組或者類數組
  • 傳入數組,但是Fn實際接收的仍然是一個一個接收;
        function fn(a,b){
            console.log(a,b);
            console.log(this);   
        }
        fn.apply(null,[100,200])
----------------------------------------
				function fn(n,m){
            console.log(this, n, m)
        }
        fn.apply(1, [20,30])

bind

  • 預處理this
  • 這個方法也是改變this指向的,但他會提前改變實例函數的this指向,並不會讓實例函數執行,他的返回值是改變this之後的新函數
  • 在bind函數中將fn進行了包裝和處理,改變了fn裏面的this指向,並且返回一個改變this之後的新函數
  • bind在IE8以下不兼容,bind正常傳參
        function fn(a,b){
            console.log(a,b);
            console.log(this);   
        }
        var f = fn.bind([1,2]);
        f();
        var  f =fn.bind([1,2]);
        f(100,200);
        fn();	
--------------------------------------------------------
			<div id="box">1111</div>
            // let box = document.getElementById('box');
            let fn = function(){
                console.log(this)
            }
            let obj = {}
            // box.onclick  = fn.bind(obj)
          	fn = fn.bind(obj)//不改變原有函數,需要重新賦值
          	fn()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章