JS 兼容、繼承、bind、this

這一篇文章主要是講述一些有關js的小知識點。由於我也不是很精通哪一方面只能把本身知道的一點點寫出來。取名大雜燴也是這意思吧,既然是道菜那麼就來嚐嚐個人手藝吧。後端

第一道菜數組

1.首先,我想說一下事件綁定。
事件綁定咱們都知道有:on + 'type'的事件綁定還有addEventListener的事件綁定。瀏覽器

  • 在之前由於各類緣由致使咱們在不一樣的瀏覽器上面實現同樣的功能出現了兼容的寫法,首先咱們先來看看事件綁定的兼容寫法。app

    function bindEvent(obj, type, handle) {
        if(window.addEventListener){
            obj.addEventListener(type, handle, false);
        }
        if(window.attachEvent){ // IE
            obj.attachEvent('on' + type, handle);
        }
        obj['on' + type] = handle;
    }
  • 事件解除除了on + 'type'其餘的就是怎麼綁定怎麼解除。函數

    function delEvent(obj, type, handle) {
        if(window.removeEventListener){
            obj.removeEventListener(type, handle, false);
        }
        if(window.detachEvent){ // IE
            obj.attachEvent('on' + type, handle);
        }
        obj['on' + type] = null;
    }
  • 順帶提一下IE與其它瀏覽器獲取target和event的兼容方式。this

    function handle(e){
        let event = e || window.event; // IE
        let target = e.target || e.srcElement;
    }

    下一篇我會詳細介紹DOM事件綁定和DOM事件等級。spa

第二道菜prototype

2.這個知識點咱們來講一下繼承。code

  • 說到js的繼承,我是學後端的在C ++ 、Java裏面都跟js的繼承不同,因此一開始很差理解也以爲怪怪的。雖然ES6形式上有點像吧。那咱們先來看看js一開始的繼承。對象

    function Father(){
        this.a = 1;
    }
     Father.prototype.show = function () {
         console.log(this.a);
     }
     function Son(){
         Father.call(this);
     }
     let son = new Son();
     console.log(son.a);
     console.log(son.show());


咱們能夠發現這種是拿不到父級原型上的函數的。

  • 咱們再來看看第二種

    function Father(){
        this.a = 1;
    }
     Father.prototype.show = function () {
         console.log(this.a);
     }
     function Son(){
         Father.call(this);
     }
     Son.prototype = Father.prototype;  //多了這一行
     let son = new Son();
     console.log(son.a);
     console.log(son.show());


咱們發現拿到了原型上的函數,但這樣就是最好的了嗎?咱們一塊兒來看看。當咱們查看Son的時候發現了一個奇怪的事。


咱們能夠看到Son的constructor變成了Father,這是爲何呢?由於constructor是原型上的函數,咱們改變了Son的原型,由於Father的constructor是Father因此Son的constructor就變成了Father。並且這種方法咱們改變Son.prototype時Father.prototype也會改變,那這說明咱們的方法仍是不夠完美。

  • 咱們再來看看第三種

    function Father(){
        this.a = 1;
    }
     Father.prototype.show = function () {
         console.log(this.a);
     }
     function Son(){
         Father.call(this);
     }
     function F(){};    //借用中間層來防止Son改變Father的原型
     F.prototype = Father.prototype;
     Son.prototype = new F();
     Son.prototype.constructor = Son; //改變Son的constructor
     let son = new Son();
     console.log(son.a);
     console.log(son.show());
     console.log(son.constructor);


說到了構造函數那咱們就看看什麼是構造函數。

function Father(){
    this.a = 1;
}
```這個是否是構造函數?若是說是那你就錯了,由於你思惟固定了。咱們都說構造函數開頭首字母大寫但那只是人爲的規定並非語法。還有若是有人問你```this```是誰,你能夠放肆的告訴他,你沒調用我知道是誰啊。```new```只是一個操做符,任何函數都能經過new來執行,並不僅是構造函數,只不過咱們認爲```new```以後的都是構造函數。
+ 那你知道```new```的時候發生了什麼嗎?咱們通常都說四步走
+ 首先建立一個新的對象。
+ 改變這個對象的this
+ 改變這個對象的原型
+ 返回這個對象
咱們試着寫一寫
function myNew(){
     let obj = {};
     let Constructor = arguments[0];
     obj.__proto__ = Constructor.prototype;
     Constructor.apply(obj, arguments);
     return obj;
 }
 let a = myNew(Son);
 console.log(a);
```


補充:
咱們都知道ES6那麼他的繼承跟咱們的繼承有什麼區別呢?咱們來看一下ES6的繼承。

class Father {
     constructor(){
         this.a = 1;
     }
     show(){
         console.log(this.a);
     }
 }
 class Son extends Father {
     constructor(){
         super();
     }
 }
 let son = new Son();
 console.log(son.a);
 console.log(son.show());


咱們發現是同樣的。只不過是這種實現方式更加讓人容易理解而且接受尤爲是對於習慣了C++/Java之類語言的人。
其實class這種實現方式只是一個‘語法糖’,當咱們用typeof Son的時候咱們發現他是一個function,其實它就是一個函數只不過更加語義化了,並且裏面定義的方法實際上是定義在了它的原型上面,咱們輸出一下Father看看。

第三道菜

3.咱們再來介紹一下call、bind、apply

  • 它們都是用來改變this的,只不過有一些小的差異。

    • call和apply除了傳參方式的不一樣外,沒有不一樣的地方。
    • bind返回一個函數,call、apply當即執行。

演示我以爲應該不用了吧,由於我不是在寫文檔,那麼咱們說一些什麼呢,就說說怎麼模擬實現吧。我這裏用的是ES6的語法,我只是以爲這樣寫比較簡單但整體思路不變。
下面以這個爲例:

var a = 3;
var obj = {
    a: 1
}
function show(g) {
    console.log(this.a, g);
}
  • call
Function.prototype.callh = function(context){
    let args = [...arguments].slice(1); //首先獲取到傳遞的參數
    context.fn = this; // 獲取當前的調用者,並添加一個方法
    context.fn(...args);    // 傳入參數
    delete context.fn;  //刪除新增的函數
}    
show.callh(obj, 2);

  • apply
Function.prototype.applyh = function(context){
    let args = arguments[1]; // 跟call同樣,只不過apply傳進來的是數組,因此arguments[1]指的是後面的參數
    context.fn = this;
    context.fn(...args);
    delete context.fn; 
    show.applyh(obj, [2]);
}

  • bind(重點)
// 簡易版
Function.prototype.bindh = function(context){
    let args = [...arguments].slice(1);
    let that = this;
    return function (argument) { // bind返回一個函數
        let args2 = [...arguments].slice(0);
        that.call(context, ...args.concat(args2));
    }
}
show.bindh(obj)(5);


但上面bind的方式有一點錯誤。咱們來看看js裏面的bind和咱們的在new以後有什麼區別。
上面是js的後面是模擬的。看到了吧。

由於原型的緣由,咱們來改進一下。

Function.prototype.bindh = function(context){
    let args = [...arguments].slice(1);
    let that = this;
    function bnd(){}
    let fn = function (argument) {
        let args2 = [...arguments].slice(0);
        return that.call(this instanceof fn ? this : context, ...args.concat(args2));
    }
    bnd.prototype = this.prototype;
    fn.prototype = new bnd();
    return fn;
}

這樣就好了。

第四道菜

4.再來說一講this

  • this一直是咱們困惑的問題,有時候能看懂但代碼一多,調用一多,咱們就蒙了,下面我來簡單介紹一下。

    1. 在默認狀況下this指向window(非嚴格模式)
    2. 誰調用指向誰
    3. call.apply
    4. bind
    5. new

我想到的差很少就這幾種吧,你或許會發現實際上是按this綁定的優先級升序排序的。若是你看懂了bind的模擬實現也許會知道爲何bind的優先級會高於call、apply。我以爲弄清楚這些this應該不是多大的問題吧,來一段代碼看看。

var a = 3;
var obj = {
    a: 1,
    fn(){
        console.log(this.a);
    }
    +
}
function show() {
    console.log(this.a);
}
show();  //3
obj.fn(); //1
show.call(obj); // 1
show.apply(obj); // 1
show.bind(obj)(); // 1
show.bind(window).call(obj); //3  bind優先級高,跟綁定順序沒區別

但願這些菜能知足您的胃口,希望也能給您填飽一些肚子。我之後還會繼續努力提升本身的廚藝,但願嚐到這個菜的人都會喜歡。

相關文章
相關標籤/搜索