程序員花0.5小時總結:JavaScript 中一顆有毒的語法糖

在 JavaScript 中 this 其實是一顆語法糖,但是這糖有毒。this 致命的地方在於它的指向往往不能直觀確定。希望下面可以一步步去掉有毒的糖衣。

這篇文章共享之前我仍是要引薦下我自個的前端群:657137906,不論你是小白仍是大牛,小編我都挺期待,不定期共享乾貨,包含我自個整理的一份2017最新的前端材料和零根底入門教程,期待初學和進階中的小夥伴。

1 用 f.call(thisVal, ...args) 指定 this

調用函數的方式有三種,用 Function.prototype.call 調用可以指定 this:

定義 function f(...args){/*...*/}

調用 f.call(thisVal, ...args);

例一

程序員花0.5小時總結:JavaScript 中一顆有毒的語法糖

例二

程序員花0.5小時總結:JavaScript 中一顆有毒的語法糖

2 使用語法糖,this 自動指定

先接受函數 f 的正確調用方式是 f.call(thisVal, ...args);, 然後就可以把 f(...args); 理解成語法糖。

但是不用 f.call(thisVal, ...args), this 怎樣動態指定?

一、函數(function)

程序員花0.5小時總結:JavaScript 中一顆有毒的語法糖

一、方法(method)

程序員花0.5小時總結:JavaScript 中一顆有毒的語法糖

通過上面的例子,分別演示了函數 f(..args) 和方法 obj1.obj2....objn.m(..args)怎樣自動指定 this嚴格區分函數(function)和方法(method)這兩個概念有利於清晰思考,因為它們在綁定 this 時發生的行為完全不一樣。同時函數和方法可以相互賦值(轉換),在賦值前後,唯一發生變化的是綁定 this 的行為(當然這種變化在調用時才會體現)。下面先看函數轉方法,再看方法轉函數。

3 函數轉方法

函數聲明(function f(){})和函數表達式(var f = function(){};)有一些微妙的區別,但是兩種方式在調用時綁定this行為完全一樣,下面在嚴格模式下以函數表達式為例:

程序員花0.5小時總結:JavaScript 中一顆有毒的語法糖

將函數轉成方法通常不太容易出錯,因為起碼在方法中 this能夠有效地指向一個對象。函數轉成方法是一個模糊的說法,實際上可以這樣理解:

JavaScript 不能定義一個函數,也不能定義一個方法,是函數還是方法,要等到它執行才能確定;當把它當成函數執行,它就是函數,當把它當成方法執行,它就是方法。所以只能說執行一個函數和執行一個方法。\

\

這樣理解可能有些極端,但是它可能有助於避免一些常見的錯誤。因為關係到 this 怎樣綁定,重要的是在哪裡調用(比如在 obj1, obj2... 上調用)以及怎樣調用(比如以 f(), f.call()... 的方式),而不是在哪裡定義。

但是,為了表達的方便,這裡仍然會使用定義函數和定義方法這兩種說法。

4 方法轉函數

將方法轉成函數比較容易出錯,比如:

程序員花0.5小時總結:JavaScript 中一顆有毒的語法糖

當一個對象的方法使用了 this時,如果這個方法最後不是由這個對象調用(比如由其他框架調用),這個方法就可能會出錯。但是有一種技術可以將一個方法(或函數)綁定(bind)在一個對象上,從而無論怎樣調用,它都能夠正常執行。

5 把方法綁定(bind)在對象上先看這個obj.getName的例子:

程序員花0.5小時總結:JavaScript 中一顆有毒的語法糖

上面的例子之所以可以成功是因為 obj.getName 根本沒有用到 this, 所以 this 指向什麼對 obj.getName 都沒有影響。

這裡有一種技術把使用 this 的方法轉成不使用 this 的方法,就是創建兩個閉包(即函數),第一個閉包將方法(method)和對象(obj)捕獲下來並返回第二個閉包,而第二個閉包用於調用並返回 obj.method.call(obj);. 下面一步步實現這種技術:

第一步 最簡單的情況下:

function method(){ obj.method.call(obj);}method(); // correct, :))

存在的缺陷:

  1. 只適合沒有參數和返回的 obj.method

  2. 存在兩個安全隱患:

    1 後續改變 obj.method,比如 obj.method = null;

    2 後續改變 obj,比如 obj = null

第二步 在方法有參數有返回的情況下:

function method(a, b){ return obj.method.call(obj, a, b);}method(a, b); // correct, :))

存在的缺陷:

  1. 只適合兩個參數的 obj.method

  2. 存在兩個安全隱患,同上。

第三步 一個傳遞參數更好的辦法:

function method(){ return obj.method.apply(obj, arguments);}method(a, b); // correct, :))

仍存在兩個安全隱患。

第四步 更加安全的方式:

var method = (function(){ return function(){ return obj.method.apply(obj, arguments); };})(obj.method, obj);method(a, b); // correct, :))

第五步 抽象出一個函數,用於將方法綁定到對象上:

function bind(method, obj){ return function(){ return method.apply(obj, arguments); };}var obj = { name: 'ngolin', getName: function(){ return this.name; }};var method = bind(obj.getName, obj);method(); // 'ngolin'

6 Function.prototype.bind

這種方法很常見,後來 ECMAScript 5 就增加了 Function.prototype.bind, 比如:

var binded = function(){ return this.name;}.bind({name: 'ngolin'});binded(); // 'ngolin'

具體來說,Function.prototype.bind

這樣工作:

更多使用 Function.prototype.bind 的例子:

var f = obj.method.bind(obj);button.onClick = obj.method.bind(obj);document.addEventListener('click', obj.method.bind(obj));

7 常見問題及容易出錯的地方

在定義對象時有沒有 this?

程序員花0.5小時總結:JavaScript 中一顆有毒的語法糖

二 在方法內的 this 都是同一對象嗎?

程序員花0.5小時總結:JavaScript 中一顆有毒的語法糖

最後在說幾句:

  1. 厲害程序員相對於普通程序員的優勢在於:

  2. 寫出的代碼更容易排錯,不是高手的代碼就不會錯,而是高手的代碼出了錯容易找。高手的代碼可讀性一定很好,模塊清晰,命名規範,格式工整,關鍵的地方有註釋,出了異常有log,自然容易排錯,即使交給別人去debug也是比較容易的。

  3. 今天這個圖片彈窗特效到這裡寫完了,學習web前端的可以加我的群,每天分享對應的學習資料:657137906,歡迎初學和進階中的小夥伴。多寫多練。

如果想看到更加系統的文章和學習方法經驗可以關注我的微信公眾號:‘前端根據地’關注後回覆‘領取資料’可以領取一套完整的學習視頻

相關推薦

推薦中...