当前位置:   article > 正文

JavaScript函数,方法,this总结_javascript 设置函数默认this

javascript 设置函数默认this

作为一个iOS开发者,先吐槽一下JavaScript,学习JavaScript真的是无语,随意的语法,各种奇葩的设计(毕竟是10天左右设计出来的,肯定会有考虑不周的地方),总之写习惯了OC,写起来JS总是会感觉很不严谨,经常会忘记语法(跑偏到OC语法),吐槽完毕,下面开始正题!

本文主要讲述的是JavaScript相当核心的一个知识点--函数 Function,以及谈到函数不可避免的要谈到的神奇又令人头疼的的this!两者相辅相成,水乳交融,运用好的话,会事半功倍,但是如果搞不清楚其中的真正的含义,往往令人头疼不已,也是bug产生的隐患。下面详细讲解:

一:函数的定义:
在JavaScript中,定义函数的方式如下:
  1. function abs(x) {
  2. if (x >= 0) {
  3. return x;
  4. } else {
  5. return -x;
  6. }
  7. }

上述abs()函数的定义如下:

  • function指出这是一个函数定义;
  • abs是函数的名称;
  • (x)括号内列出函数的参数,多个参数以,分隔;
  • { ... }之间的代码是函数体,可以包含若干语句,甚至可以没有任何语句。

请注意,函数体内部的语句在执行时,一旦执行到return时,函数就执行完毕,并将结果返回。因此,函数内部通过条件判断和循环可以实现非常复杂的逻辑。

如果没有return语句,函数执行完毕后也会返回结果,只是结果为undefined

由于JavaScript的函数也是一个对象,上述定义的abs()函数实际上是一个函数对象,而函数名abs可以视为指向该函数的变量。

因此,第二种定义函数的方式如下:

  1. var abs = function (x) {
  2. if (x >= 0) {
  3. return x;
  4. } else {
  5. return -x;
  6. }
  7. };

在这种方式下,function (x) { ... }是一个匿名函数,它没有函数名。但是,这个匿名函数赋值给了变量abs,所以,通过变量abs就可以调用该函数。

上述两种定义完全等价,注意第二种方式按照完整语法需要在函数体末尾加一个;,表示赋值语句结束。

二:函数的调用

调用函数时,按顺序传入参数即可:

  1. abs(10); // 返回10
  2. abs(-9); // 返回9

由于JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数:

  1. abs(10, 'blablabla'); // 返回10
  2. abs(-9, 'haha', 'hehe', null); // 返回9

传入的参数比定义的少也没有问题:

abs(); // 返回NaN

此时abs(x)函数的参数x将收到undefined,计算结果为NaN

要避免收到undefined,可以对参数进行检查:

  1. function abs(x) {
  2. if (typeof x !== 'number') {
  3. throw 'Not a number';
  4. }
  5. if (x >= 0) {
  6. return x;
  7. } else {
  8. return -x;
  9. }
  10. }

arguments

JavaScript还有一个免费赠送的关键字arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments类似Array但它不是一个Array

  1. function foo(x) {
  2. console.log('x = ' + x); // 10
  3. for (var i=0; i<arguments.length; i++) {
  4. console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
  5. }
  6. }
  7. foo(10, 20, 30);
log:  x = 10
arg 0 = 10
arg 1 = 20
arg 2 = 30

利用arguments,你可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值:

  1. function abs() {
  2. if (arguments.length === 0) {
  3. return 0;
  4. }
  5. var x = arguments[0];
  6. return x >= 0 ? x : -x;
  7. }
  8. abs(); // 0
  9. abs(10); // 10
  10. abs(-9); // 9

关于函数的基础知识就讲解这么多,记住核心的一点:JavaScript的函数也是一个对象!这一点非常重要,对于后续理解this的指向。

三:方法

在一个对象中绑定函数,称为这个对象的方法。

在JavaScript中,对象的定义是这样的:

  1. var xiaoming = {
  2. name: '小明',
  3. birth: 1990
  4. };
但是,如果我们给 xiaoming绑定一个函数,就可以做更多的事情。比如,写个 age()方法,返回 xiaoming的年龄:
  1. var xiaoming = {
  2. name: '小明',
  3. birth: 1990,
  4. age: function () {
  5. var y = new Date().getFullYear();
  6. return y - this.birth;
  7. }
  8. };
  9. xiaoming.age; // function xiaoming.age()
  10. xiaoming.age(); // 今年调用是25,明年调用就变成26了

绑定到对象上的函数称为方法,和普通函数也没啥区别,但是它在内部使用了一个this关键字,这个东东是什么?

在一个方法内部,this是一个特殊变量,它始终指向当前对象,也就是xiaoming这个变量。所以,this.birth可以拿到xiaomingbirth属性。

让我们拆开写:

  1. function getAge() {
  2. var y = new Date().getFullYear();
  3. return y - this.birth;
  4. }
  5. var xiaoming = {
  6. name: '小明',
  7. birth: 1990,
  8. age: getAge
  9. };
  10. xiaoming.age(); // 25, 正常结果
  11. getAge(); // NaN

单独调用函数getAge()怎么返回了NaN请注意,我们已经进入到了JavaScript的一个大坑里。

JavaScript的函数内部如果调用了this,那么这个this到底指向谁?

答案是,视情况而定!

如果以对象的方法形式调用,比如xiaoming.age(),该函数的this指向被调用的对象,也就是xiaoming,这是符合我们预期的。

如果单独调用函数,比如getAge(),此时,该函数的this指向全局对象,也就是window

坑爹啊!

更坑爹的是,如果这么写:

  1. var fn = xiaoming.age; // 先拿到xiaoming的age函数
  2. fn(); // NaN

也是不行的!要保证this指向正确,必须用obj.xxx()的形式调用!

由于这是一个巨大的设计错误,要想纠正可没那么简单。ECMA决定,在strict模式下让函数的this指向undefined,因此,在strict模式下,你会得到一个错误:

  1. 'use strict';
  2. var xiaoming = {
  3. name: '小明',
  4. birth: 1990,
  5. age: function () {
  6. var y = new Date().getFullYear();
  7. return y - this.birth;
  8. }
  9. };
  10. var fn = xiaoming.age;
  11. fn(); // Uncaught TypeError: Cannot read property 'birth' of undefined

这个决定只是让错误及时暴露出来,并没有解决this应该指向的正确位置。

有些时候,喜欢重构的你把方法重构了一下:

  1. 'use strict';
  2. var xiaoming = {
  3. name: '小明',
  4. birth: 1990,
  5. age: function () {
  6. function getAgeFromBirth() {
  7. var y = new Date().getFullYear();
  8. return y - this.birth;
  9. }
  10. return getAgeFromBirth();
  11. }
  12. };
  13. xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined

结果又报错了!原因是this指针只在age方法的函数内指向xiaoming,在函数内部定义的函数,this又指向undefined了!(在非strict模式下,它重新指向全局对象window!)

修复的办法也不是没有,我们用一个that变量首先捕获this

  1. 'use strict';
  2. var xiaoming = {
  3. name: '小明',
  4. birth: 1990,
  5. age: function () {
  6. var that = this; // 在方法内部一开始就捕获this
  7. function getAgeFromBirth() {
  8. var y = new Date().getFullYear();
  9. return y - that.birth; // 用that而不是this
  10. }
  11. return getAgeFromBirth();
  12. }
  13. };
  14. xiaoming.age(); // 25
var that = this;,你就可以放心地在方法内部定义其他函数,而不是把所有语句都堆到一个方法中。
对于刚刚接触JavaScript的人来说,上面这些写法的this指向,已经搞得你晕头转向,不知所云,但是坚韧的你肯定会费尽心思想要彻底搞明白他的奥秘。下面详细解释一波这个善变的this!

四:迷之this!!!!!!!!!!!

在 JavaScript 中,this 是指当前函数中正在执行的上下文环境,因为这门语言拥有四种不同的函数调用类型:

  • 函数调用 alert('Hello World!')
  • 方法调用 console.log('Hello World!')
  • 构造函数调用 new RegExp('\\d')
  • 间接调用 alert.call(undefined, 'Hello World')

在以上每一项调用中,它都拥有各自独立的上下文环境,就会造成 this 所指意义有所差别。此外,严格模式也会对执行环境造成影响。

在以上每一项调用中,它都拥有各自独立的上下文环境,就会造成 this 所指意义有所差别。此外,严格模式也会对执行环境造成影响。

理解 this 关键字的关键在于理解各种不同的函数调用以及它是如何影响上下文环境的。
下面重点解释不同情况下的函数调用会怎样影响 this 以及判断上下文环境时会产生的一些常见陷阱。

1. 函数调用

函数调用 代表了该函数接收以成对的引号包含,用逗号分隔的不同参数组成的表达式。举例:parseInt('18')。这个表达式不能是属性访问如 myObject.myFunction 这样会造成方法调用。[1, 5].join(',') 同样也不是一个函数调用而是方法调用。

函数调用的一个简单例子:

  1. function hello(name) {
  2. return 'Hello' + name + '!';
  3. }
  4. // 函数调用
  5. var message = hello('World');
  6. console.log(message); // => 'Hello World!'

hello('World') 是一个函数调用:hello表达式代表了一个函数对象,接受了用成对引号包含的 World 参数。

高级一点的例子,立即执行函数 IIFE (immediately-invoked function expression):

  1. var message = (function(name) {
  2. return 'Hello ' + name + '!';
  3. })('World');
  4. console.log(message) // => 'Hello World!'
1.1 函数调用中的 this
this is the global object in a function invocation

全局对象取决于当前执行环境,在浏览器中,全局对象即 window

在函数调用中,上下文执行环境是全局对象,可以在以下函数中验证上下文:

  1. function sum(a, b) {
  2. console.log(this === window); // => true
  3. this.myNumber = 20; // 在全局对象中添加 'myNumber' 属性
  4. return a + b;
  5. }
  6. // sum() 为函数调用
  7. // this 在 sum() 中是全局对象 (window)
  8. sum(15, 16); // => 31
  9. window.myNumber; // => 20

sum(15, 16) 被调用时,JavaScript 自动将 this 设置为全局对象,即 window

this 在任何函数作用域以外调用时(最外层作用域:全局执行上下文环境),也会涉及到全局对象。

  1. console.log(this === window); // => true
  2. this.myString = 'Hello World!';
  3. console.log(window.myString); // => 'Hello World!'
  console.log(this === window); // => true
1.2  严格模式下,函数调用中的 this
this is undefined in a function invocation in strict mode
严格模式由 ECMAScript 5.1 引进,用来限制 JavaScript 的一些异常处理,提供更好的安全性和更强壮的错误检查机制。使用严格模式,只需要将 'use strict' 置于函数体的顶部。这样就可以将上下文环境中的 this 转为 undefined。这样执行上下文环境不再是全局对象,与非严格模式刚好相反。
在严格模式下执行函数的一个例子:
  1. function multiply(a, b) {
  2. 'use strict'; // 开启严格模式
  3. console.log(this === undefined); // => true
  4. return a * b;
  5. }
  6. // 严格模式下的函数调用 multiply()
  7. // this 在 multiply() 中为 undefined
  8. multiply(2, 5); // => 10

multiply(2, 5) 执行时,这个函数中的 thisundefined

严格模式不仅在当前作用域起到作用,它还会影响内部作用域,即内部声明的一切内部函数的作用域。

  1. function execute() {
  2. 'use strict'; // 开启严格模式
  3. function concat(str1, str2) {
  4. // 内部函数也是严格模式
  5. console.log(this === undefined); // => true
  6. return str1 + str2;
  7. }
  8. // 在严格模式下调用 concat()
  9. // this 在 concat() 下是 undefined
  10. concat('Hello', ' World!'); // => "Hello World!"
  11. }
  12. execute();

use strict 被插入函数执行主体的顶部,使严格模式可以控制到整个作用域。因为 concat 在执行作用域内部声明,因此它继承了严格模式。此外,concat('Hello', ' World!') 的调用中,this 也会成为 undefined

一个简单的 JavaScript 文件可能同时包含严格模式和非严格模式,所以在同一种类型调用中,可能也会有不同的上下文行为差异。

  1. function nonStrictSum(a, b) {
  2. // 非严格模式
  3. console.log(this === window); // => true
  4. return a + b;
  5. }
  6. function strictSum(a, b) {
  7. 'use strict';
  8. // 严格模式
  9. console.log(this === undefined); // => true
  10. return a + b;
  11. }
  12. // nonStrictSum() 在非严格模式下被调用
  13. // this 在 nonStrictSum() 中是 window 对象
  14. nonStrictSum(5, 6); // => 11
  15. // strictSum() 在严格模式下被调用
  16. // this 在 strictSum() 中是 undefined
  17. strictSum(8, 12); // => 20
1.3  陷阱:this 在内部函数中

一个常见的陷阱是理所应当的认为函数调用中的,内部函数中 this 等同于它的外部函数中的 this

正确的理解是内部函数的上下文环境取决于调用环境,而不是外部函数的上下文环境。

为了获取到所期望的 this,应该利用间接调用修改内部函数的上下文环境,如使用 .call() 或者 .apply 或者创建一个绑定函数 .bind()

下面的例子表示计算两个数之和:

  1. var numbers = {
  2. numberA: 5,
  3. numberB: 10,
  4. sum: function() {
  5. console.log(this === numbers); // => true
  6. function calculate() {
  7. // 严格模式下, this 是 window or undefined
  8. console.log(this === numbers); // => false
  9. return this.numberA + this.numberB;
  10. }
  11. return calculate();
  12. }
  13. };
  14. numbers.sum(); // => 严格模式下,结果为 NaN 或者 throws TypeError

numbers.sum() 是对象内的一个方法调用,因此 sum 的上下文是 numbers 对象,而 calculate 函数定义在 sum 函数内,所以会误以为在 calculatethis 也指向的是 numbers

然而 calculate() 在函数调用(而不是作为方法调用)时,此时的 this 指向的是全局对象 window 或者在严格模式下指向 undefined ,即使外部函数 sum 拥有 numbers对象作上下文环境,它也没有办法影响到内部的 this

numbers.sum() 调用的结果是 NaN 或者在严格模式下直接抛出错误 TypeError: Cannot read property 'numberA' of undefined,而绝非期待的结果 5 + 10 = 15,造成这样的原因是 calculate 并没有正确的被调用。

为了解决这个问题,正确的方法是使 calculate 函数被调用时的上下文同 sum 调用时一样,为了得到属性 numberAnumberB,其中一种办法是使用 .call() 方法。

  1. var numbers = {
  2. numberA: 5,
  3. numberB: 10,
  4. sum: function() {
  5. console.log(this === numbers); // => true
  6. function calculate() {
  7. console.log(this === numbers); // => true
  8. return this.numberA + this.numberB;
  9. }
  10. // 使用 .call() 方法修改上下文环境
  11. return calculate.call(this);
  12. }
  13. };
  14. numbers.sum(); // => 15
calculate.call(this) 同样执行 calculate 函数,但是格外的添加了 this作为第一个参数,修改了上下文执行环境。此时的 this.numberA + this.numberB 等同于 numbers.numberA + numbers.numberB,其最终的结果就会如期盼的一样为 result 5 + 10 = 15
 

2. 方法调用

方法是作为一个对象属性存储的函数,举个例子:
  1. var myObject = {
  2. // helloFunction 是对象中的方法
  3. helloFunction: function() {
  4. return 'Hello World!';
  5. }
  6. };
  7. var message = myObject.helloFunction();

helloFunction 是属于 myObject 的一个方法,调用这个方法可以使用属性访问的方式 myObject.helloFunction

方法调用表现为对象属性访问的形式,支持传入用成对引号包裹起来的一系列参数。上个例子中,myObject.helloFunction() 其实就是对象 myObject 上对属性 helloFunction 的方法调用。同样,[1, 2].join(',')/\s/.test('beautiful world') 都是方法调用。

区分函数调用和方法调用是非常重要的,它们是不同类型的调用方式。主要的差别在于方法调用为访问属性的形式,如:.functionProperty() 或者 ['functionProperty'](),而函数调用为 ()

  1. ['Hello', 'World'].join(', '); // 方法调用
  2. ({ ten: function() { return 10; } }).ten(); // 方法调用
  3. var obj = {};
  4. obj.myFunction = function() {
  5. return new Date().toString();
  6. };
  7. obj.myFunction(); // 方法调用
  8. var otherFunction = obj.myFunction;
  9. otherFunction(); // 函数调用
  10. parseFloat('16.60'); // 函数调用
  11. isNaN(0); // 函数调用
2.1  方法调用中的 this
this is the object that owns the method in a method invocation

当在一个对象里调用方法时,this 代表的是对象它自身。让我们创建一个对象,其包含一个可以递增属性的方法。
  1. var calc = {
  2. num: 0,
  3. increment: function() {
  4. console.log(this === calc); // => true
  5. this.num += 1;
  6. return this.num;
  7. }
  8. };
  9. // 方法调用,this 指向 calc
  10. calc.increment(); // => 1
  11. calc.increment(); // => 2

calc.increment() 调用意味着上下文执行环境在 calc 对象里,因此使用 this.sum 递增 num 这个属性是可行的。

一个 JavaScript 对象继承方法来自于它自身的属性。当一个被继承方法在对象中调用时,上下文执行环境同样是对象本身。

  1. var myDog = Object.create({
  2. sayName: function() {
  3. console.log(this === myDog); // => true
  4. return this.name;
  5. }
  6. });
  7. myDog.name = 'Milo';
  8. // 方法调用, this 指向 myDog
  9. myDog.sayName(); // => 'Milo'

Object.create() 创建了一个新的对象 myDog 并且设置了属性,myDog 对象继承了 myName方法。当 myDog.sayName() 被执行时,上下文执行环境指向 myDog

在 ECMAScript 5 的 class 语法中, 方法调用指的是实例本身。

  1. class Planet {
  2. constructor(name) {
  3. this.name = name;
  4. }
  5. getName() {
  6. console.log(this === earth); // => true
  7. return this.name;
  8. }
  9. }
  10. var earth = new Planet('Earth');
  11. // 方法调用,上下文为 earth
  12. earth.getName(); // => 'Earth'
2.2     陷阱:方法会分离它自身的对象

一个对象中的方法可能会被提取抽离成一个变量。当使用这个变量调用方法时,开发者可能会误认为 this 指向的还是定义该方法时的对象。

如果方法调用不依靠对象,那么就是一个函数调用,即 this 指向全局对象 object 或者在严格模式下为 undefined。创建函数绑定可以修复上下文,使该方法被正确对象调用。

下面的例子创建了构造器函数 Animal 并且创建了一个实例 myCat,在 setTimeout() 定时器 1s 后打印 myCat 对象信息。

  1. function Animal(type, legs) {
  2. this.type = type;
  3. this.legs = legs;
  4. this.logInfo = function() {
  5. console.log(this === myCat); // => false
  6. console.log('The ' + this.type + ' has ' + this.legs + ' legs');
  7. }
  8. }
  9. var myCat = new Animal('Cat', 4);
  10. // 打印出 "The undefined has undefined legs"
  11. // 或者在严格模式下抛出错误 TypeError
  12. setTimeout(myCat.logInfo, 1000);

开发者可能认为在 setTimeout 下调用 myCat.logInfo() 会打印出 myCat 对象的信息。但实际上这个方法被分离了出来作为了参数传入函数内 setTimeout(myCat.logInfo),然后 1s 后会发生函数调用。当 logInfo 被作为函数调用时,this 指向全局对象 window 或者在严格模式下为 undefined,因此对象信息没有正确地被打印。

方法绑定可以使用 .bind() 方法。如果被分离的方法绑定了 myCat 对象,那么上下文问题就可以被解决了:

  1. function Animal(type, legs) {
  2. this.type = type;
  3. this.legs = legs;
  4. this.logInfo = function() {
  5. console.log(this === myCat); // => true
  6. console.log('The ' + this.type + ' has ' + this.legs + ' legs');
  7. };
  8. }
  9. var myCat = new Animal('Cat', 4);
  10. // 打印 "The Cat has 4 legs"
  11. setTimeout(myCat.logInfo.bind(myCat), 1000);
此时,myCat.logInfo.bind(myCat) 返回的新函数调用里的 this 指向了 myCat

3. 构造函数调用

构造函数调用使用 new 关键词,后面跟随可带参数的对象表达式,例:new RegExp('\\d')

以下的例子声明了一个构造函数 Country,并调用。

  1. function Country(name, traveled) {
  2. this.name = name ? this.name : 'United Kingdom';
  3. this.traveled = Boolean(traveled); // 转换为 boolean 值
  4. }
  5. Country.prototype.travel = function() {
  6. this.traveled = true;
  7. };
  8. // 构造函数调用
  9. var france = new Country('France', false);
  10. // 构造函数调用
  11. var unitedKingdom = new Country;
  12. france.travel(); // Travel to France

new City('Paris') 是一个构造器调用,这个对象初始化使用了类中特殊的方法 constructor,其中的 this 指向的是新创建的对象。

构造器调用创建了一个空的新对象,从构造器的原型中继承属性。这个构造器函数的意义在于初始化对象,因此这个类型的函数调用创建实例。

当一个属性访问 myObject.myFunction 前拥有 new 关键词,那么 JavaScript 会执行构造器调用而不是方法调用。举个例子:new myObject.myFunction() 意味着首先这个函数会解析为一个属性访问函数 extractedFunction = myObject.myFunction,然后用构造器创建一个新对象 new extractedFunction

3.1   在构造函数调用中的 this
this is the newly created object in a constructor invocation

构造器调用的环境是新创建的对象。通过传递构造函数参数来初始化新建的对象,添加属性初始化值以及事件处理器。

让我们来验证以下这个例子的上下文环境:

  1. function Foo () {
  2. console.log(this instanceof Foo); // => true
  3. this.property = 'Default Value';
  4. }
  5. // 构造函数调用
  6. var fooInstance = new Foo();
  7. fooInstance.property; // => 'Default Value'

new Foo() 建立构造器调用,它的上下文环境为 fooInstance,在 Foo 对象中初始化了 this.property 这个属性并赋予初始值。

在使用 class 语法时也是同样的情况(在 ES6 中),初始化只发生在它的 constructor 方法中。

  1. class Bar {
  2. constructor() {
  3. console.log(this instanceof Bar); // => true
  4. this.property = 'Default Value';
  5. }
  6. }
  7. // 构造函数调用
  8. var barInstance = new Bar();
  9. barInstance.property; // => 'Default Value'
当执行 new Bar() 时,JavaScript 创建了一个空对象并且它的上下文环境为 constructor 方法,因此添加属性的办法是使用 this 关键词:this.property = 'Default Value'
3.2    陷阱:忘记添加 new 关键词
一些 JavaScript 函数创建实例,不仅仅可以使用构造器的形式调用也可以利用函数调用,下面是一个 RegExp 的例子:
  1. var reg1 = new RegExp('\\w+');
  2. var reg2 = RegExp('\\w+');
  3. reg1 instanceof RegExp; // => true
  4. reg2 instanceof RegExp; // => true
  5. reg1.source === reg2.source; // => true

当执行 new RegExp('\\w+')RegExp('\\w+') 时,JavaScript 创建了两个相等的普通表达式对象。

但是使用函数调用创建对象会产生潜在的问题(包括工厂模式),当失去了 new 关键词,一些构造器会取消初始化对象。

以下例子描述了这个问题:

  1. function Vehicle(type, wheelsCount) {
  2. this.type = type;
  3. this.wheelsCount = wheelsCount;
  4. return this;
  5. }
  6. // 函数调用
  7. var car = Vehicle('Car', 4);
  8. car.type; // => 'Car'
  9. car.wheelsCount // => 4
  10. car === window // => true

Vehicle 是一个在对象上设置了 typewheelsCount 属性的函数。

当执行了 Vehicle('Car', 4) 时,会返回对象 car,它拥有正确的属性值:car.type 指向 Carcar.wheelsCount 指向 4,开发者会误以为这样创建初始化对象没有什么问题。
然而,当前执行的是函数调用,因此 this 指向的是 window 对象,所以它设置的属性其实是挂在 window 对象上的,这样是完全错误的,它并没有创建一个新对象。

应该正确的执行方式是使用 new 关键词来保证构造器被正确调用:

  1. function Vehicle(type, wheelsCount) {
  2. if (!(this instanceof Vehicle)) {
  3. throw Error('Error: Incorrect invocation');
  4. }
  5. this.type = type;
  6. this.wheelsCount = wheelsCount;
  7. return this;
  8. }
  9. // 构造函数调用
  10. var car = new Vehicle('Car', 4);
  11. car.type // => 'Car'
  12. car.wheelsCount // => 4
  13. car instanceof Vehicle // => true
  14. // 函数调用,会报错。
  15. var brokenCat = Vehicle('Broken Car', 3);
new Vehicle('Car', 4) 可以正确运行:一个新的对象被创建和初始化,因为 new 关键词代表了当前为构造器调用。
在构造器函数中添加验证:this instanceof Vehicle,可以保证当前的执行上下文是正确的对象类型。如果 this 不是指向 Vehicle,那么就存在错误。 如果 Vehicle('Broken Car', 3) 表达式没有 new 关键词而被执行,就会抛出错误:Error: Incorrect invocation

4. 间接调用

间接调用表现为当一个函数使用了 .call() 或者 .apply() 方法。

在 JavaScript 中,函数为一等对象,这意味着函数是一个对象,对象类型即为 Function
函数的一系列方法中,.call().apply() 被用来配置当前调用的上下文环境。

方法 .call(thisArg[, arg1[, arg2[, ...]]]) 接收第一个参数 thisArg 作为执行的上下文环境,以及一系列参数 arg1, arg2, ...作为函数的传参被调用。
并且,方法 .apply(thisArg, [args]) 接收 thisArg作为上下文环境,剩下的参数可以用类数组对象 [args] 传递。

间接调用的例子:

  1. function increment(number) {
  2. return ++number;
  3. }
  4. increment.call(undefined, 10); // => 11
  5. increment.apply(undefined, [10]); // => 11

increment.call()increment.apply() 同时传递了参数 10 调用 increment 函数。

两个方法最主要的区别为 .call() 接收一组参数,如 myFunction.call(thisValue, 'value1', 'value2'),而 .apply() 接收一串参数作为类数组对象传递,如 myFunction.apply(thisValue, ['value1', 'value2'])

4.1    间接调用中的 this
this is the first argument of .call() or .apply() in an indirect invocation

很明显,在间接调用中,this 指向的是 .call().apply()传递的第一个参数。

  1. var rabbit = { name: 'White Rabbit' };
  2. function concatName(string) {
  3. console.log(this === rabbit); // => true
  4. return string + this.name;
  5. }
  6. // 间接调用
  7. concatName.call(rabbit, 'Hello '); // => 'Hello White Rabbit'
  8. concatName.apply(rabbit, ['Bye ']); // => 'Bye White Rabbit'

当函数执行需要特别指定上下文时,间接调用非常有用,它可以解决函数调用中的上下文问题(this 指向 window 或者严格模式下指向 undefined),同时也可以用来模拟方法调用对象。

另一个实践例子为,在 ES5 中的类继承中,调用父级构造器。

  1. function Runner(name) {
  2. console.log(this instanceof Rabbit); // => true
  3. this.name = name;
  4. }
  5. function Rabbit(name, countLegs) {
  6. console.log(this instanceof Rabbit); // => true
  7. // 间接调用,调用了父级构造器
  8. Runner.call(this, name);
  9. this.countLegs = countLegs;
  10. }
  11. var myRabbit = new Rabbit('White Rabbit', 4);
  12. myRabbit; // { name: 'White Rabbit', countLegs: 4 }
Runner.call(this, name)Rabbit 里间接调用了父级方法初始化对象。
另外,还有bind绑定函数的使用也要注意this的值,这里不再一一介绍,上面已经介绍了很多。

总结

因为函数调用会极大地影响到 this,所以从现在开始不要直接问自己:

this 是从哪里来的?

而是要开始思考:

当前函数是怎么被调用的?

以上思路可以帮助开发者减少判断 this 带来的烦恼。

如有错误,欢迎指正!




声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/63536
推荐阅读
相关标签
  

闽ICP备14008679号