深入淺出JavaScript中的"this"關鍵字__Java

來源:互聯網
上載者:User
1. 神秘的this

大多數時間this關鍵字都充滿了神秘的色彩對我和許多JavaScript開發人員。這是一個強大的功能特性,但是它需要努力才能被理解。

從Java、PHP還有其他標準語言的背景,在類方法裡,this是被當做當前對象的一個執行個體,不能多也不能少。大多數情況下,他不能在方法之外使用,這種簡單的策略不會對this的理解造成混亂。

在JavaScript中情況有所不同:this是當前函數的執行環境(execution context of a funciton).JavaScript有4種函數調用方式: 函數調用: alert(‘Hello World’) 方法調用: console.log(‘Hello World’) 構造器調用: new RegExp(‘\d’) 間接調用: alert.call(undefined, ‘Hello World!’)

每種調用方式定義了它們自己的執行環境,所以this與開發人員的期望有略微不同。

此外,strict模式也會影響執行內容。

理解this關鍵字的關鍵是函數調用有一個清晰的看法以及它如何影響上下文。

文本的重點是調用的解釋,函數調用如何影響this,並示範了識別內容相關的常見陷阱。

開始之前,讓我們先來熟悉一些術語: 一個函數的調用是正在執行構成函數體的代碼, 或者簡單的調用。例如parseInt函數調用是parseInt(‘15’) 調用的上下文是函數體內this的值。例如map.set(‘key’, ‘value’)的調用有調用上下文map 一個函數的範圍是函數體內可訪問的變數、對象、函數的集合。 2. 函數調用

函數調用在對一個函數對象求值的運算式後跟一個左括弧(,逗號分隔的參數運算式列表和右括弧)時執行。例如 parseInt(‘18’)

函數調用運算式不能是屬性訪問器(例如console.log(‘hi’)),它是方法調用。再例如[1,5].join(’,’)不是一個函數調用,而是一個方法調用。這種區別是很重要的。

函數調用的一個簡單的例子:

function hello(name) {    return 'Hello ' + name + '!';}// 函數調用var message = hello('World');console.log(message);// => 'Hello World!'

上述hello(‘World’)是一個函數調用: hello運算式求值到一個函數對象,緊跟著一對括弧中帶著參數‘World’

更進階的例子是IIFE(immediately-invoked function expression):

var message = (function(name) {     return 'Hello ' + name + '!';})('World');console.log(message) // => 'Hello World!' 

IIFE也是一個函數調用: 第一對括弧(function(name) {…})是一個被定義為函數的運算式,緊跟著一對括弧中帶著參數‘World’ 2.1. 函數調用中的this

在一次函數調用中, this是一個全域對象

全域對象是在執行環境中被確定的。在瀏覽器中,他是window對象

window;function myFunc() {    console.log(window == this);// true;}

在一次函數調用,執行內容就是全域對象。

讓我們檢查下面函數的上下文:

function sum(a, b) {    console.log(this == window);// true    this.myNumber = 20; // 添加一個叫'myNumber'的屬性到全域對象中    return a + b;}// sum() 作為函數被調用// this 在sum() 中是一個全域對象(window)sum(15, 16);        // => 31window.myNumber; // => 20

當sum(15, 16)被調用時, JavaScript自動化佈建this為全域對象,在瀏覽器中它是window

this在函數之外被使用時,this也為全域對象:

console.log(this === window); // => true  this.myString = 'Hello World!';  console.log(window.myString); // => 'Hello World!'  
<!-- In an html file -->  <script type="text/javascript">     console.log(this === window); // => true</script>  
2.2. strict 模式(strict mode)下,函數調用中的this

在strict 模式(strict mode)下,一次函數調用中, this是一個undefined

在ECMAScript 5.1中引入了strict 模式(strict mode),它是JavaScript的一個有限變體。它提供更好的安全性和更強的錯誤檢查。

要啟用strict 模式(strict mode),請將指令“use strict”放在函數體的頂部

一旦啟用,strict 模式(strict mode)影響執行內容,使其在常規函數調用中未定義。執行內容不再是全域對象,與上面的情況2.1相反。

function myFunc() {    'use strict';    console.log(this == undefined);// =>true}myFunc();

函數strict 模式下被執行的例子:

function multiply(a, b) {      'use strict'; // 啟用strict 模式    console.log(this === undefined); // => true    return a * b;}// this的值是undefinedmultiply(2, 5); // => 10  

strict 模式開啟後不僅僅作用在當前執行環境,也作用在內建函式執行環境。也就是說內建函式也在strict 模式下被執行。

function execute() {     'use strict'; // activate the strict mode       function concat(str1, str2) {     // the strict mode is enabled too     console.log(this === undefined); // => true     return str1 + str2;   }   // concat() is invoked as a function in strict mode   // this in concat() is undefined   concat('Hello', ' World!'); // => "Hello World!"}execute();  

單個JavaScript檔案可能包含非strict 模式與strict 模式並存。因此在同一個調用類型的單個指令碼中可能有不同的上下文行為:

function nonStrictSum(a, b) {    // non-strict mode  console.log(this === window); // => true  return a + b;}function strictSum(a, b) {    'use strict';  // strict 模式啟動  console.log(this === undefined); // => true  return a + b;}// nonStrictSum函數內this是windownonStrictSum(5, 6); // => 11  // strictSum函數內this是undefinedstrictSum(8, 12); // => 20  
2.3. 陷阱:內建函式中的this 錯誤的認知: 函數調用的一個常見的陷阱是認為 this在內建函式與外部函數相同 正確的認知: 正確地,內建函式的上下文僅依賴於調用,而不依賴於外部函數的上下文。
var numbers = {     numberA: 5,   numberB: 10,   sum: function() {     console.log(this === numbers); // => true     function calculate() {       // this是window或者是undefined,如果是在strict 模式下的話。       console.log(this === numbers); // => false       return this.numberA + this.numberB;     }     return calculate();   }};numbers.sum(); // => 普通模式下NaN,在strict 模式下會報錯

為瞭解決這個問題, calculate這個函數應該被sum方法相同的執行內容執行,可以用下面這種方法來指定執行內容:

var numbers = {     numberA: 5,   numberB: 10,   sum: function() {     console.log(this === numbers); // => true     function calculate() {       console.log(this === numbers); // => true       return this.numberA + this.numberB;     }     // 使用.call() 方法來修改執行內容     return calculate.call(this);   }};numbers.sum(); // => 15
3. 方法調用

方法是儲存在對象的屬性中的函數。例如:

var myObject = {    // helloFunction 它就是一個方法    helloFunction: function() {        return 'Hello World!';    }}var message = myObject.helloFUnction();

helloFunction就是myObject的一個方法。擷取方法,用屬性訪問器: myObject.helloFunction.

方法調用在屬性訪問器的形式中的運算式執行,該運算式計算為函數對象後面跟著一個開啟的括弧(,逗號分隔的參數運算式列表和右括弧)。

回憶前面的例子,myObject.helloFunction()是對象myObject上的helloFunction的方法調用。方法調用也是:[1,2] .join(’,’)或/\s/.test(‘beautiful world’)。

函數調用方法調用區分開很重要,因為它們是不同的類型。主要的區別是方法調用需要一個屬性訪問器來調用函數(obj.myFunc()),而函數調用不需要(myFunc())。

下面的的調用列表展現了怎樣區分它們的不同:

['Hello', 'World'].join(', '); // 方法調用({ ten: function() { return 10; } }).ten(); // 方法調用var obj = {};  obj.myFunction = function() {    return new Date().toString();};obj.myFunction(); // 方法調用var otherFunction = obj.myFunction;  otherFunction();     // 函數調用parseFloat('16.60'); // 函數調用isNaN(0);            // 函數調用

理解函數調用方法調用的差異能協助我們標識正確的上下文。 3.1. 方法調用中的this

this是在方法調用中擁有方法的對象

當在一個對象中調用一個方法時,this變成了對象它本身。

var myObject = {    myMethod: function() {        this;    }}myObject.myMethod();

讓我們建立一個帶一個增加數位方法的對象:

var calc = {    num: 0,    increment: function() {        console.log(this === calc); // true        this.num += 1;        return this.num;    }};// 方法調用calc.increment(); // => 1calc.increment(); // => 2

調用calc.increment()使increment函數的上下文為calc對象。所以使用this.num來增加number屬性是很好的。

讓我們看看其他情況。一個JavaScript對象從它的原型繼承一個方法。當這個被繼承的方法在這對象中被調用時,調用的上下文仍然是它自己本身。

var myDog = Object.create({    sayName: function() {     console.log(this === myDog); // => true     return this.name;  }});myDog.name = 'Milo';  // method invocation. this is myDogmyDog.sayName(); // => 'Milo'
3.2. 陷阱:分離方法與其對象 錯誤的認知: 從一個對象的方法可以提取到一個單獨的變數var alone = myObj.myMethod。當單獨調用該方法時,從原始對象alone()分離,您可能認為這是定義方法的對象。 正確的認知: 如果沒有對象調用該方法,則會發生函數調用:這是全域對象視窗或在strict 模式下未定義(參見2.1和2.2)

以下樣本建立Animal建構函式並建立它的執行個體 - myCat。然後setTimout()1秒後記錄myCat對象資訊:

function Animal(type, legs) {  this.type = type;  this.legs = legs;  this.logInfo = function() {    console.log(this === myCat); // => false    console.log('The ' + this.type + ' has ' + this.legs + ' legs');  }}var myCat = new Animal('Cat', 4);  // 顯示 "The undefined has undefined legs" 或者拋出異常(在strict 模式下)setTimeout(myCat.logInfo, 1000); // 顯示 "The Cat has 4 legs"setTimeout(myCat.logInfo.bind(myCat), 1000);  
4. 構造器調用

當new關鍵字後面是一個運算式,其值為一個函數對象,一個開啟的括弧(,逗號分隔的參數運算式列表和一個右括弧)時,將執行構造方法調用。例如:new RegExp(’\ d’)。

function Country(name, traveled) {     this.name = name ? name : 'United Kingdom';   this.traveled = Boolean(traveled);}Country.prototype.travel = function() {    this.traveled = true;};// 構造器調用var france = new Country('France', false);  // 構造器調用var unitedKingdom = new Country;france.travel(); // 法國旅行

new Country(‘France’, false)是一個Country函數的構造器調用。這個執行結果是一個新的對象,它的name屬性是‘France’

如果構造器沒有參數,括弧可以被省略: new Country 4.1. 構造器調用中的this

this是在構造器調用中新建立的對象

構造器調用的上下文是剛建立了的對象。它用來初始化對象的資料, 從構造器函數參數, 設定初始的屬性值還有一些處理方法等。

functioin Constructor() {    this;// 自身對象}var object = new Constructor();// object跟上面的this是同一個對象。

讓我們來看看下面的例子的上下文:

function Foo() {    console.log(this instanceof Foo); // => true    this.property = 'Default Value';}// 構造器調用var fooInstance = new Foo();fooInstance.property; // => 'Default Value'

new Foo()被當做一個構造器調用, 它的上下文是fooInstance。在Foo內部,對象被初始化:this.property被賦予一個預設值。

new Foo()被執行的時候, JavaScript建立一個Null 物件並且讓它成為構造方法的上下文。之後你可以使用this關鍵字添加屬性到對象: this.property = ‘Default Value’ 4.2. 陷阱:忘記了new

一些JavaScript函數建立執行個體不僅用構造器,而且用函數調用。例如RegExp:

var reg1 = new RegExp('\\w+');var reg2 = RegExp('\\w+');reg1 instanceof RegExp;         // => truereg2 instanceof RegExp;         // => truereg1.source === reg2.source;    // => true
function Vehicle(type, wheelsCount) {    if(!(this instanceof Vehicle) {        throw Error('Error: 錯誤調用');    }    this.type = type;    this.wheelsCount = wheelsCount;    return this;}var car = new Vehicle('Car', 4);car.type;car.wheelsCount;car instanceof Vehicle;// 函數調用。會產生一個錯誤var brokenCar = Vehicle('Broken Car', 3);

new Vehicle(‘Car’, 4)運行正常: 一個新的對象被建立並且被初始化, 因為new關鍵字存在於構造器調用中 5. 間接調用

間接調用: 當一個函數使用myFun.call()或者myFun.apply被調用時,將執行間接調用。

JavaScript中的函數是第一類對象,這意味著一個函數是一個對象。此對象的類型是Function

Function對象的兩個方法:

call(thisArg[, arg1[, arg2[, …]]])
apply(thisArg, [arg1, arg2, …])

例如:

myFun.call(thisValue, 'val1', 'val2');myFunc.apply(thisValue, ['val1', 'val2']);
5.1. 間接調用中的this

間接調用this.call()或者.apply()的第一個參數

下面的例子顯示了間接調用的上下文:

var rabbit = { name: '小白兔' };  function concatName(string) {    console.log(this === rabbit); // => true  return string + this.name;}// 間接調用concatName.call(rabbit, '你好 ');  // => '你好 小白兔'  concatName.apply(rabbit, ['再見 ']); // => '再見 小白兔'  

間接調用在某些時候非常有用,比如在strict 模式下,我們的上下文是window或者undefined的時候,使用間接調用,函數體內的this就能夠是我們想要的對象。 6. 綁定函數

綁定函數是一個攜帶著對象的函數。通常,它都是通過原始函數使用.bind()函數來建立的。通過此方法來改變函數的執行環境

function multiply(number) {    'use strict';  return this * number;}// create a bound function with contextvar double = multiply.bind(2);  // invoke the bound functiondouble(3);  // => 6  double(10); // => 20  
6.1. 綁定函數中的this

綁定單數中的this.bind()函數的第一個參數。

var numbers = {    array: [3, 5, 10],  getNumbers: function() {    return this.array;      }};// 建立一個綁定函數var boundGetNumbers = numbers.getNumbers.bind(numbers);  boundGetNumbers(); // => [3, 5, 10]  // 提取方法var simpleGetNumbers = numbers.getNumbers;  simpleGetNumbers(); // => undefined 或者 拋出異常在strict 模式下
6.2. 緊密上下文綁定

.bind()建立一個永久上下文連結並始終保持它。

當使用.call()或.apply()與不同的上下文時,綁定的函數不能更改其連結的上下文,或者甚至反彈沒有任何效果。只有綁定函數的建構函式調用可以改變,但是這不是推薦的方法(對於建構函式調用,使用正常,不綁定的函數)。

以下樣本建立一個bound函數,然後嘗試更改其已預定義的上下文:

function getThis() {    'use strict';  return this;}var one = getThis.bind(1);  // 綁定函數調用one(); // => 1  // 用 .apply() .call() 調用綁定函數one.call(2);  // => 1  one.apply(2); // => 1  // 再次綁定one.bind(2)(); // => 1  // 將綁定函數通過構造器來調用new one(); // => Object 

只有new()改變綁定的函數的上下文,其他類型的調用不會改變函數的執行內容。 7. 箭頭函數

箭頭函數旨在以較短的形式聲明函數,並以詞法綁定上下文。

它可以使用以下方式:

var hello = (name) => {    return 'Hello ' + name;};hello('World'); // => 'Hello World'  // 只保留偶數[1, 2, 5, 6].filter(item => item % 2 === 0); // => [2, 6]

箭頭函數帶來一個更輕的文法,可以不包括詳細關鍵字function。當函數只有1個語句,你甚至可以省略返回。

一個箭頭函數是匿名的,這意味著name屬性是一個Null 字元串”。這樣,它沒有詞法函數名(這對於遞迴,分離事件處理常式很有用)。

它也不支援arguments對象,跟普通函數不一樣。然後他可以支援剩餘參數在ES2015之後:

var sumArguments = (...args) => {     console.log(typeof arguments); // => 'undefined'   return args.reduce((result, item) => result + item);};sumArgume
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.