本文为译文,文章有点长,可是仔细通篇阅读下来,关于this
的识别问题基本就搞定了。因为译者水平有限,文中有纰漏之处,还请读者多多指正。下面看正文吧:javascript
在很长一段时间内,this
关键字都让我感到迷惑,相信不少JavaScript的初学者也是同样。this
是JavaScript中很强大的一个特色,可是想搞懂它,你必须得花点时间。html
对于像Java、PHP这样的标准语言来讲,this
在类方法中指代的就是调用这个方法的实例。通常来讲,this
不能在方法外使用,如此简单的规则不会让人迷惑。java
可是在JavaScript中状况就有些不一样了:this
指的是当前函数的执行上下文。在JavaScript中,函数有4种调用类型:正则表达式
alert('Hello World')
console.log('Hello World')
new RegExp(\\d)
alert.call(undefined, 'Hello World')
每种调用类型都有本身定义执行上下文的方式,因此this
指代的对象和咱们预期的可能稍有不一样。express
此外严格模式也会影响执行上下文。数组
理解this
的关键在于要对函数的调用类型以及函数调用类型如何影响执行上下文有一个清晰的认识。本篇文章的目的就是解释函数调用的类型、函数调用的类型如何影响this
的取值以及演示辨认执行上下文时的常见误区。浏览器
在开始以前,咱们先熟悉几个概念:安全
parseInt
的函数调用为parseInt('15')
map.set('key', 'value')
, set
方法的执行上下文是map
,set
函数体中this
指的就是map
文章目录:app
谜之thisdom
函数调用
2.1 函数调用中的this 2.2 严格模式时函数调用中的this 2.3 误区:内部函数中的this
方法调用
3.1 方法调用中的this 3.2 误区:从对象提取的方法
构造调用
4.1 构造调用中的this 4.2 误区:忽略了new
间接调用
5.1 间接调用中的this
绑定函数
6.1 绑定函数中的this 6.2 紧密的上下文绑定
箭头函数
7.1 箭头函数中的this 7.2 使用箭头函数定义方法
结论
函数名后面加上一对小括号,括号里能够填写参数,这就是函数调用,如parseInt('18')
。
函数调用不能写为属性访问的方式,如obj.myFunc()
。属性访问的方式称为方法调用,如[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也是函数调用,第一个小括号内是函数定义,紧跟的一个小括号是调用,'World'
是参数。
函数调用中的this是全局对象
全局对象由执行环境定义。在浏览器环境中,它是window
对象。
如图,函数调用的执行上下文是全局对象。
下面的函数验证了上下文:
function sum(a, b) { console.log(this === window); // => true this.myNumber = 20; // 添加'myNumber'属性到全局对象 return a + b; } // sum以函数调用的方式调用,sum中的this是全局对象(window) sum(15, 16); // => 31 window.myNumber; // => 20
sum(15, 16)
一执行,JavaScript就会自动的把this
设置为全局对象。在浏览器中,全局对象就是window
。
当this
在任何函数做用域外被使用时(也就是在最顶层的做用域使用),它也指向全局对象:
console.log(this === window); // => true this.myString = 'Hello World!'; console.log(window.myString); // => 'Hello World!'
<!-- html文件中 --> <script type="text/javascript"> console.log(this === window); // => true </script>
严格模式时,函数调用中的
this
为undefined
严格模式是从ECMAScript 5.1时被引入的,它是JavaScript的一种限制模式,更安全,而且提供了更强大的错误检查机制。
在函数体的上方添加'use strict'
就启用了严格模式。
严格模式一旦被启用,它就会影响执行上下文,使this
在函数调用中为undefined
。
严格模式时函数调用示例:
function multiply(a, b) { 'use strict'; // 启用严格模式 console.log(this === undefined); // => true return a * b; } // multiply 在严格模式下进行函数调用,multiply中的this为undefined multiply(2, 5); // => 10
当multiply(2, 5)
被调用时,this
为undefined
。
严格模式不只在当前做用域生效,并且在内部的做用域(在函数内部定义的函数)也生效:
function execute() { 'use strict'; // 启用严格模式 function concat(str1, str2) { // 在这里严格模式也生效 console.log(this === undefined); // => true return str1 + str2; } // concat()在严格模式中进行函数调用 // this在concat()里为undefined concat('Hello', ' World!'); // => "Hello World!" } execute();
'use strict'
声明在excute
函数体的顶部以便在该函数做用域内启用严格模式。由于concat
被声明在excute
的做用域内,因此它继承了excute
的严格模式,因而concat
的函数调用时,this
也为undefined
。
单个JavaScript文件可能既包含严格模式,又包含非严格模式。因此在单个脚本文件中,即便是相同的调用类型,也可能有不一样的上下文表现:
function nonStrictSum(a, b) { // 非严格模式 console.log(this === window); // => true return a + b; } function strictSum(a, b) { 'use strict'; // 严格模式 console.log(this === undefined); // => true return a + b; } // nonStrictSum()在非严格模式下进行函数调用 // this在nonStrictSum()中为window对象 nonStrictSum(5, 6); // => 11 // strictSum()在严格模式下进行函数调用 // this在strictSum()中为undefined strictSum(8, 12); // => 20
函数调用一个常见的误区是认为内部函数和外部函数中的this
是相同的。
其实内部函数的上下文只依赖函数的调用类型,而不是外部函数的上下文。
若是要指定this
的值,咱们能够经过间接调用(使用.call()
或.apply()
)的方式改变内部函数的上下文或者建立一个绑定函数(使用.bind()
)。
下面是一个计算两个数和的例子:
var numbers = { numberA: 5, numberB: 10, sum: function() { console.log(this === numbers); // => true function calculate() { // this为window或undefined(严格模式) console.log(this === numbers); // => false return this.numberA + this.numberB; } return calculate(); } }; numbers.sum(); // => NaN或抛出TypeError错误(严格模式)
numbers.sum()
是对象上的方法调用,因此sum
里的上下文是numbers
对象。calculate
函数定义在sum
内部,因此你可能认为在calculate()
里this
也是numbers
对象。
然而calculate()
是一个函数调用,而不是方法调用,因此它的this
为全局对象window
或在严格模式时为undefined
,尽管外部函数sum
的上下文是numbers
对象。
numbers.sum()
的结果是NaN
或在严格模式时抛出一个错误:TypeError: Cannot read property 'numberA' of undefined
,由于calculate()
的this
为全局对象window
或在严格模式时为undefined
,window
上并无numberA
和numberB
。
为了解决这个问题,calculate
在执行时必须和sum
有相同的上下文,以便使用numbersA
和numbersB
属性。
一个解决方案是经过calculate.call(this)
(函数的间接调用)手动改变calculate
的上下文:
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
calculate.call(this)
仍是像一般同样执行calculate
函数,只不过它的上下文被修改成了传递的第一个参数。如今this.numbersA + this.numbersB
就等同于numbers.numbersA + numbers.numbersB
,这样就能够获得正确的结果了:5 + 5 = 15
。
方法是存储在对象属性上的函数。例如:
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()
或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); // 函数调用
理解了函数调用和方法调用的不一样能够帮助咱们正确地识别上下文。
方法调用中的
this
是该方法的全部者。
当在一个对象上调用方法时,this
指的就是该对象。
下面咱们建立一个包含自增方法的对象:
var calc = { num: 0, increment: function() { console.log(this === calc); // => true this.num += 1; return this.num; } }; //方法调用。this是calc calc.increment(); // => 1 calc.increment(); // => 2
执行calc.increment()
时,increment
函数的上下文为calc
对象,因此能实现this.num
的自增。
咱们再看一个例子,一个对象从它的原型上继承了一个方法,当继承来的方法在该对象上调用时,上下文仍然是该对象:
var myDog = Object.create({ sayName: function() { console.log(this === myDog); // => true return this.name; } }); myDog.name = 'Milo'; // 方法调用。this是myDog myDog.sayName(); // => 'Milo'
Object.create()
建立了原对象的一个子对象myDog
,它继承了sayName
方法。 当调用myDog.sayName()
时,myDog
就是上下文。
在ECMAScript 6的class
语法中,方法调用的上下文也是该对象自己:
class Planet { constructor(name) { this.name = name; } getName() { console.log(this === earth); // => true return this.name; } } var earth = new Planet('Earth'); // 方法调用。上下文是earth earth.getName(); // => 'Earth'
对象的方法能够被提取到一个单独的变量中:var alone = myObj.myMethod
。当一个方法从对象上分离,单独被调用时:alone()
,你或许会认为this
仍是该对象。
但实际上,一个方法若是不经过对象而直接调用,它就是一个函数调用:this
是全局对象window
或在严格模式中为undefined
。
建立一个绑定函数var alone = myObj.myMethod.bind(myObj)
(使用.bind()
)能够固定上下文,使上下文始终为该方法的全部者。
下面的例子声明了一个Animal
构造函数,接着建立了它的一个实例——myCat
,而后经过setTimeout()
在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" // 或者在严格模式中抛出一个TypeError错误 setTimeout(myCat.logInfo, 1000);
你可能会认为setTimeout()
会执行myCat.logInfo()
那样就会打印myCat
的信息了。
但看成为参数传递的时候,方法是从对象提取出来的,这等同于下面的例子:
setTimout(myCat.logInfo); // 等同于: var extractedLogInfo = myCat.logInfo; setTimout(extractedLogInfo);
当提取出的logInfo
被做为函数调用时,this
是全局对象或在严格模式中为undefined
(而不是myCat
对象),因此不能打印出对象的信息。
一个函数可使用.bind()
方法绑定一个对象,若是被提取的方法绑定了myCat
对象,那么上下文的问题就解决了:
function Animal(type, legs) { this.type = type; this.legs = legs; this.logInfo = function() { console.log(this === myCat); // => true console.log('The ' + this.type + ' has ' + this.legs + ' legs'); }; } var myCat = new Animal('Cat', 4); // 打印"The Cat has 4 legs" setTimeout(myCat.logInfo.bind(myCat), 1000);
myCat.logInfo.bind(myCat)
返回了一个等同于logInfo
的新函数,可是新函数的this
是myCat
。即便是进行函数调用,它的this
也是myCat
。
new
关键字跟上函数名,再加上一对小括号就是构造调用,括号内一样能够传递参数,例如:new RegExp('\\d')
。
下面这个例子声明了一个Country
函数,而后做为构造函数调用:
function Country(name, traveled) { this.name = name ? name : 'United Kingdom'; this.traveled = Boolean(traveled); // 转换为booleanl类型 } 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
。
从ECMAScript 2015开始,JavaScript容许使用class
语法定义构造函数:
class City { constructor(name, traveled) { this.name = name; this.traveled = false; } travel() { this.traveled = true; } } // 构造调用 var paris = new City('Paris', false); paris.travel();
new City('Paris')
是构造调用。对象是经过class
中声明的一个特殊方法:constructor
初始化的,constructor
中的this
为新建立的对象。
构造调用建立了一个从构造函数原型继承了属性的新的空对象,constructor
的做用是初始化这个新对象。你可能已经知道,构造调用的上下文为新建立的对象,这是下一章的主题。
当属性访问myObject.myFunction
先于new
关键字时,JavaScript会执行构造调用,而不是方法调用。例如new myObject.myFunction()
:首先是使用属性访问提取函数extractedFunction = myObject.myFunction
,而后是做为构造函数调用建立新对象new extractedFunction()
。
构造调用中的
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
被分配了初始值。
当使用class
语法(ES2015可用)时,状况也是同样,初始化过程只发生在constructor
方法中:
class Bar { constructor() { console.log(this instanceof Bar); // => true this.property = 'Default Value'; } } // 构造调用 var barInstance = new Bar(); barInstance.property; // => 'Default Value'
new Bar()
一执行,JavaScript就会建立一个空对象,而后设置constructor
方法的上下文为该对象,因而就可使用this
关键字给这个对象添加属性了:this.property = 'Default Value'
。
有些JavaScript函数不光做为构造调用时会建立一个新对象,做为函数调用时也会建立。好比RegExp
:
var reg1 = new RegExp('\\w+'); var reg2 = RegExp('\\w+'); reg1 instanceof RegExp; // => true reg2 instanceof RegExp; // => true reg1.source === reg2.source; // => true
当执行new RegExp('\\w+')
和RegExp('\\w+')
时,JavaScript会建立等同的正则表达式对象。
使用函数调用建立对象有一个潜在的问题(工厂模式除外),由于当缺失new
关键字时,一些构造函数不会建立新对象。
下面的这个例子说明了这个问题:
function Vehicle(type, wheelsCount) { this.type = type; this.wheelsCount = wheelsCount; return this; } // 函数调用 var car = Vehicle('Car', 4); car.type; // => 'Car' car.wheelsCount // => 4 car === window // => true
Vehicle
是给上下文对象设置type
和wheelsCount
属性的函数。当执行Vehicle('Car', 4)
是,返回了一个对象car
,而且它的属性也是正确的:car.type
是'Car'
、car.wheelsCount
是4
。你可能认为这不是也很好地建立并初始化了一个新对象嘛。
然而,在函数调用中this
是window
对象,结果Vehicle('Car', 4)
是在window
对象上设置属性,并无建立一个新对象。
因此当进行构造调用时,要确保使用new
关键字:
function Vehicle(type, wheelsCount) { if (!(this instanceof Vehicle)) { throw Error('Error: Incorrect invocation'); } this.type = type; this.wheelsCount = wheelsCount; return this; } // 构造调用 var car = new Vehicle('Car', 4); car.type // => 'Car' car.wheelsCount // => 4 car instanceof Vehicle // => true // 函数调用。抛出一个错误 var brokenCar = Vehicle('Broken Car', 3);
如上面代码所示,new Vehicle('Car', 4)
很好的起做用了:使用了new
关键字,一个新对象被建立并初始化了。
这个例子在构造函数中添加了一个校验this instanceof Vehicle
,以保证执行上下文是正确的对象类型,若是this
不是Vehicle
类型,就报错。这样不管何时,执行Vehicle('Broken Car', 3)
都会报错:Error: Incorrect invocation
,能够确保必须使用new
。
使用myFun.call()
或myFun.apply()
方法调用函数是间接调用。
在JavaScript中,函数自己就是对象,它的类型是Function
。
函数上的.call
和.apply()
能够用来指定调用函数时的上下文:
.call(thisArg[, arg1[, arg2[, ...]])
接收的第一个参数thisArg
做为执行上下文,后面的arg1
、arg2
、...做为实际的参数。.apply(thisArg, [arg1, arg2, ...])
接收的第一个参数做为执行上下文,后面的数组做为实际的参数。下面的示例演示了间接调用:
function increment(number) { return ++number; } increment.call(undefined, 10); // => 11 increment.apply(undefined, [10]); // => 11
increment.call()
和increment.apply()
都是接收10
做为参数执行increment
函数。
.call()
和.apply()
的不一样在于.call()
须要把参数一一列出,例如myFun.call(thisValue, 'val1', 'val2')
,而.apply()
接收一个参数数组,例如myFunc.apply(thisValue, ['val1', 'val2'])
。
间接调用中的
this
是.call()
或.apply()
的第一个参数。
以下图所示,间接调用的this
是.call()
或.apply()
的第一个参数。
下面的示例验证了间接调用的上下文:
var rabbit = { name: 'White Rabbit' }; function concatName(string) { console.log(this === rabbit); // => true return string + this.name; } // 间接调用 concatName.call(rabbit, 'Hello '); // => 'Hello White Rabbit' concatName.apply(rabbit, ['Bye ']); // => 'Bye White Rabbit'
当一个函数须要使用指定的上下文执行时,间接调用就颇有用了。例如,能够解决函数调用的上下文老是window
或undefined
(严格模式)的问题,能够用来模拟方法调用(见前面的示例)。
另外一个很实用的例子是在ES5中建立继承类时用于调用父类的构造函数:
function Runner(name) { console.log(this instanceof Rabbit); // => true this.name = name; } function Rabbit(name, countLegs) { console.log(this instanceof Rabbit); // => true // 间接调用。调用父类型的构造函数 Runner.call(this, name); this.countLegs = countLegs; } var myRabbit = new Rabbit('White Rabbit', 4); myRabbit; // { name: 'White Rabbit', countLegs: 4 }
Rabbit
中使用父类型的间接调用Runner.call(this, name)
来初始化新建立的对象。
一个函数绑定了某个对象称为绑定函数。一般它是经过调用原函数的.bind()
方法建立的。绑定函数和原函数具备相同的代码体和做用域,可是执行上下文不一样。
.bind(thisArg[, arg1[, arg2[, ...]]])
方法接收的第一个参数做为绑定函数的执行上下文,可选择的参数列表做为实际的参数,它返回一个绑定了thisArg
的新函数。
下面的代码建立了一个绑定函数,而后调用了它:
function multiply(number) { 'use strict'; return this * number; } // 建立一个指定上下文的绑定函数 var double = multiply.bind(2); // 调用绑定函数 double(3); // => 6 double(10); // => 20
multipty.bind(2)
返回了一个新的函数对象double
,它绑定了2
做为上下文,但multity
和doubly
仍然具备相同的函数体和做用域。
与.apply()
和.call()
当即执行一个函数相反,.bind()
方法只是返回了一个新函数。接下来这个新函数被调用时,它的this
是以前.bind()
的第一个参数。
绑定函数的
this
是以前.bind()
的第一个参数。
.bind()
的做用是建立一个采用第一个参数做为上下文的新函数。这是一个很强大的特色,它能够预先定义this
的值。
下面咱们看一下如何配置绑定函数的this
:
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或在严格模式时报错
numbers.getNumbers.bind(numbers)
返回了绑定numbers
对象的函数boundGetNumbers
,而后调用boundGetNumbers()
,this
就是numbers
对象,而后返回了正确的数组对象。
numbers.getNumbers
没有使用绑定方法被提取到了变量simpleGetNumbers
,接下来的函数调用simpleGetNumbers()
,this
为window
或undefined
(严格模式),而不是numbers
对象,因而simpleGetNumbers()
无法正确返回一个数组。
.bind()
建立了一个紧密的上下文绑定,使用.call()
或.apply()
也不能改变已经绑定的上下文,即便是从新绑定也不能改变。
可是构造调用能够改变绑定函数的上下文,然而不推荐这样作,构造调用主要是用来调用常规函数的,不是绑定函数,同时若是这样绑定函数也就没有意义了。
下面的示例建立了一个绑定函数,而后试图改变预约义的上下文:
function getThis() { 'use strict'; return this; } var one = getThis.bind(1); // 绑定函数调用 one(); // => 1 // 使用.call()和.apply()调用绑定函数 one.call(2); // => 1 one.apply(2); // => 1 // 从新绑定 one.bind(2)(); // => 1 // 使用构造调用的形式调用绑定函数 new one(); // => Object
如代码所示,只有new one()
能改变绑定函数的上下文,其余类型的调用this
总为1
。
箭头函数被设计用来采用简短的形式声明一个函数,并能在词法上绑定上下文。
下面是箭头函数的简单形式:
var hello = (name) => { return 'Hello ' + name; }; hello('World'); // => 'Hello World' // 只保留偶数 [1, 2, 5, 6].filter(item => item % 2 === 0); // => [2, 6]
箭头函数带来了更轻便的语法,省略了冗长的关键字function
,当函数体只有一条语句时,你甚至能够省略return
。
箭头函数是匿名的,这意味着它的name
属性是一个空字符串''
。在这种状况下它没有词法上的函数名(在递归和提取方法时有用)
和常规函数相比,它也没有arguments
对象,可是可使用ES2015的rest参数:
var sumArguments = (...args) => { console.log(typeof arguments); // => 'undefined' return args.reduce((result, item) => result + item); }; sumArguments.name // => '' sumArguments(5, 5, 6); // => 16
箭头函数中的
this
是箭头函数的外部函数的上下文。
箭头函数不会建立本身的执行上下文,而是采用定义它的外部函数的this
做为本身的上下文。
下面的示例演示了这种上下文的穿透性:
class Point { constructor(x, y) { this.x = x; this.y = y; } log() { console.log(this === myPoint); // => true setTimeout(()=> { console.log(this === myPoint); // => true console.log(this.x + ':' + this.y); // => '95:165' }, 1000); } } var myPoint = new Point(95, 165); myPoint.log();
箭头函数被setTimeout
调用时采用了和log()
方法相同的上下文——myPoint
对象。正如咱们所见,箭头函数“继承”了它的外部函数的上下文。
在这个例子中,若是你使用常规函数,它会建立本身的上下文(window
或严格模式时为undefined
),因此为了使函数中的代码正确执行,必须手动绑定上下文:setTimeout(function(){...}.bind(this))
,这样的话就太繁琐了,使用箭头函数是一个很轻便的解决方案。
若是箭头函数被定义在最顶层做用域(在任何函数的外部),那么上下文始终是全局对象(浏览器环境中为window
):
var getContext = () => { console.log(this === window); // => true return this; }; console.log(getContext() === window); // => true
箭头函数会永久地绑定词法上的上下文,就算使用能够修改上下的方法也不能改变它:
var numbers = [1, 2]; (function() { var get = () => { console.log(this === numbers); // => true return this; }; console.log(this === numbers); // => true get(); // => [1, 2] // 使用.apply()和.call()调用箭头函数 get.call([0]); // => [1, 2] get.apply([0]); // => [1, 2] // 绑定 get.bind([0])(); // => [1, 2] }).call(numbers);
在上面的代码中,一个函数采用.call(numbers)
进行了间接调用,使this
的值为numbers
,因而内部的箭头函数get
的this
也成了numbers
。
接着咱们看到,不管以什么样的方式调用get
,它始终保持初始化时的上下文numbers
。采用get.call([0])
或get.apply([0])
的形式进行间接调用,或者采用get.bind([0])()
的方式从新绑定再调用都不会影响。
须要注意的是,箭头函数不能做为构造函数。若是以构造函数的形式调用new get()
,JavaScript或抛出一个错误:TypeError: get is not a constructor
。
你也许想用箭头函数定义对象上的方法。凭心而论:与函数表达式相比,它的语法很是简短,如(param) => {...}
而不是function(param){...}
下面这个例子采用箭头函数在Period
类上定义了一个方法format()
:
function Period (hours, minutes) { this.hours = hours; this.minutes = minutes; } Period.prototype.format = () => { console.log(this === window); // => true return this.hours + ' hours and ' + this.minutes + ' minutes'; }; var walkPeriod = new Period(2, 30); walkPeriod.format(); // => 'undefined hours and undefined minutes'
由于format
是箭头函数,而且定义在了全局上下文(最顶层做用域),因此它的this
初始化为window
对象。
接下来即便对format
进行方法调用walkPeriod.format()
,它的上下文也不会改变,仍然是window
。由于箭头函数的上下文为静态上下文,不会随着调用类型的改变而改变。
this
为window
,因此this.hours
和this.minutes
为undefined
,因而方法就返回:'undefined hours and undefined minutes'
,这不是咱们指望的结果。
使用常规函数能够解决这个问题,由于它的上下文会随着调用类型的改变而改变:
function Period (hours, minutes) { this.hours = hours; this.minutes = minutes; } Period.prototype.format = function() { console.log(this === walkPeriod); // => true return this.hours + ' hours and ' + this.minutes + ' minutes'; }; var walkPeriod = new Period(2, 30); walkPeriod.format(); // => '2 hours and 30 minutes'
walkPeriod.format()
方法调用,上下文为walkPeriod
对象。因而this.hours
为2
,this.minutes
为30
,因此该方法返回了正确的结果:'2 hours and 30 minutes'
。
由于函数的调用方式是this
的来源,因此从如今起,不要再问:
this
来自哪儿?
而是要问:
函数是如何被调用的?
对于箭头函数,应该问:
箭头函数定义在哪儿?
这才是处理this
问题的正确思路,它能够确保你不会再头疼于this
的辨认了。
若是你还有辨认上下文的误区示例,或恰好遇到了一个比较难的案例,能够在下方留言,咱们一块儿来讨论一下!
传播JavaScript知识,分享本篇文章吧,你的同事会感激你的。
说了这么多,因此,不要再把你的上下文弄丢了 :)