用自然语言的角度理解JavaScript中的this关键字

转自:http://blog.leapoahead.com/2015/08/31/understanding-js-this-keyword/

在编写JavaScript应用的时候,我们经常会使用this关键字。那么this关键字究竟是怎样工作的?它的设计有哪些好的地方,有哪些不好的地方?本文带大家全面系统地认识这个老朋友。

这里的小明是主语,如果没有这个主语,那么后面的代词『他』将毫无意义。有了主语,代词才有了可以指代的事物。

类比到JavaScript的世界中,我们在调用一个对象的方法的时候,需要先指明这个对象,再指明要调用的方法。

var xiaoming = {
  name: ‘Xiao Ming‘,
  run: function() {
    console.log(`${this.name} seems happy`);
  },
};

xiaoming.run();

在上面的例子中,第8行中的xiaoming指定了run方法运行时的主语。因此,在run中,我们才可以用this来代替xiaoming这个对象。可以看到this起了代词的作用。

同样的,对于一个JavaScript类,在将它初始化之后,我们也可以用类似的方法来理解:类的实例在调用其方法的时候,将作为主语,其方法中的this就自然变成了指代主语的代词。

class People {
  constructor(name) {
    // 在用new关键字实例化一个对象的时候,相当于在说,
    // “创建一个People类实例(主语),它(this)的name是……”
    // 所以这里的this就是新创建的People类实例
    this.name = name;
  }

  run() {
    console.log(`${this.name} seems happy.`)
  }
}

// new关键字实例化一个类
var xiaoming = new People(‘xiaoming‘);
xiaoming.run();

这就是我认为this关键字设计得精彩的地方!如果将调用方法的语句(上面代码的第16行)和方法本身的代码连起来,像英语一样读,其实是完全通顺的。

this的绑定

句子的主语是可以变的,例如在下面的场景中,run被赋值到小芳(xiaofang)身上之后,调用xiaofang.run,主语就变成了小芳!

var xiaofang = {
  name: ‘Xiao Fang‘,
};

var xiaoming = {
  name: ‘Xiao Ming‘,
  run: function() {
    console.log(`${this.name} seems happy`);
  },
};

xiaofang.run = xiaoming.run;
// 主语变成了小芳
xiaofang.run();

在这种情况下,句子还是通顺的。所以,非常完美!

但是如果小明很抠门,不愿意将run方法借给小芳以后,this就变成了小芳的话,那么小明要怎么做呢?他可以通过Function.prototype.bindrun运行时候的this永远为小明自己

var xiaofang = {
  name: ‘Xiao Fang‘,
};

var xiaoming = {
  name: ‘Xiao Ming‘,
  run: function() {
    console.log(`${this.name} seems happy`);
  },
};

// 将小明的run方法绑定(bind)后,返回的还是一个
// 函数,但是这个函数之后被调用的时候就算主语不是小明,
// 它的this依然是小明
xiaoming.run = xiaoming.run.bind(xiaoming);

xiaofang.run = xiaoming.run;
// 主语虽然是小芳,但是最后this还是小明
xiaofang.run();

那么同一个函数被多次bind之后,到底this是哪一次bind的对象呢?你可以自己尝试看看。

callapply

Function.prototype.call允许你在调用一个函数的时候指定它的this的值。

var xiaoming = {
    name: ‘Xiao Ming‘
};

function run(today, mood) {
    console.log(`Today is ${today}, ${this.name} seems ${mood}`);
}

// 函数的call方法第一个参数是this的值
// 后续只需按函数参数的顺序传参即可
run.call(xiaoming, ‘Monday‘, ‘happy‘)

Function.prototype.applyFunction.prototype.call的功能是一模一样的,区别进在于,apply里将函数调用所需的所有参数放到一个数组当中。

var xiaoming = {
    name: ‘Xiao Ming‘
};

function run(today, mood) {
    console.log(`Today is ${today}, ${this.name} seems ${mood}`);
}

// apply只接受两个参数
// 第二个参数是一个数组,这个数组的元素被按顺序
// 作为run调用的参数
run.apply(xiaoming, [‘Monday‘, ‘happy‘])

那么call/apply和上面的bind混用的时候是什么样的行为呢?这个也留给大家自行验证。但是在一般情况下,我们应该避免混用它们,否则会造成代码检查或者调试的时候难以跟踪this的值的问题。

当方法失去主语的时候,this不再有?

其实大家可以发现我的用词,当一个function被调用的时候是有主语的时候,它是一个方法;当一个function被调用的时候是没有主语的时候,它是一个函数。当一个函数运行的时候,它虽然没有主语,但是它的this的值会是全局对象。在浏览器里,那就是window。当然了,前提是函数没有被bind过,也不是被applycall所调用。

那么function作为函数的情景有哪些呢?

首先,全局函数的调用就是最简单的一种。

function bar() {
  console.log(this === window); // 输出:true
}
bar();

立即调用的函数表达式(IIFE,Immediately-Invoked Function Expression)也是没有主语的,所以它被调用的时候this也是全局对象。

(function() {
  console.log(this === window); // 输出:true
})();

但是,当函数被执行在严格模式(strict-mode)下的时候,函数的调用时的this就是undefined了。这是很值得注意的一点。

function bar() {
  ‘use strict‘;
  console.log(‘Case 2 ‘ + String(this === undefined)); // 输出:undefined
}
bar();

不可见的调用

有时候,你没有办法看到你定义的函数是怎么被调用的。因此,你就没有办法知道它的主语。下面是一个用jQuery添加事件监听器的例子。

window.val = ‘window val‘;

var obj = {
  val: ‘obj val‘,
  foo: function() {
    $(‘#text‘).bind(‘click‘, function() {
      console.log(this.val);
    });
  }
};

obj.foo();

在事件的回调函数(第6行开始定义的匿名函数)里面,this的值既不是window,又不是obj,而是页面上idtext的HTML元素。

var obj = {
  foo: function() {
    $(‘#text‘).bind(‘click‘, function() {
      console.log(this === document.getElementById(‘text‘)); // 输出:true
    });
  }
};

obj.foo();

这是因为匿名函数是被jQuery内部调用的,我们不知道它调用的时候的主语是什么,或者是否被bind等函数修改过this的值。所以,当你将匿名函数交给程序的其他部分调用的时候,需要格外地谨慎。

如果我们想要在上面的回调函数里面使用obj的val值,除了直接写obj.val之外,还可以在foo方法中用一个新的变量that来保存foo运行时this的值。这样说有些绕口,我们看下例子便知。

window.val = ‘window val‘;

var obj = {
  val: ‘obj val‘,
  foo: function() {
    var that = this; // 保存this的引用到that,这里的this实际上就是obj
    $(‘#text‘).bind(‘click‘, function() {
      console.log(that.val); // 输出:obj val
    });
  }
};

obj.foo();

另外一种方法就是为该匿名函数bind了。

window.val = ‘window val‘;

var obj = {
  val: ‘obj val‘,
  foo: function() {
    $(‘#text‘).bind(‘click‘, function() {
      console.log(this.val); // 输出:obj val
    }.bind(this));
  }
};

obj.foo();

总结

在JavaScript中this的用法的确是千奇百怪,但是如果利用自然语言的方式来理解,一切就顺理成章了。不知道你读完这篇文章时候理解了吗?还是睡着了?亲……醒醒……

如果有任何疑问,欢迎在评论区讨论。另外,欢迎在下方订阅我的半月刊,我将为你分享有趣的技术、产品、设计的片段。

时间: 2024-10-12 22:04:03

用自然语言的角度理解JavaScript中的this关键字的相关文章

转载 深入理解JavaScript中的this关键字

转载原地址: http://www.cnblogs.com/rainman/archive/2009/05/03/1448392.html 深入理解JavaScript中的this关键字 1. 一般用处 2. this.x 与 apply().call() 3. 无意义(诡异)的this用处 4. 事件监听函数中的this 5. 总结 在JavaScript中this变量是一个令人难以摸清的关键字,this可谓是非常强大,充分了解this的相关知识有助于我们在编写面向对象的JavaScript程

深入理解JavaScript中的this关键字

1. 一般用处 2. this.x 与 apply().call() 3. 无意义(诡异)的this用处 4. 事件监听函数中的this 5. 总结 在JavaScript中this变量是一个令人难以摸清的关键字,this可谓是非常强大,充分了解this的相关知识有助于我们在编写面向对象的JavaScript程序时能够游刃有余. 1. 一般用处 对于this变量最要的是能够理清this所引用的对象到底是哪一个,也许很多资料上都有自己的解释,但有些概念讲的偏繁杂.而我的理解是:首先分析this所在

正确理解JavaScript中的this关键字

JavaScript有this关键字,this跟JavaScript的执行上下文密切相关,很多前端开发工程师至今对this关键字还是模棱两可,本文将结合代码讲解下JavaScript的this关键字. this和对象的关系 首先来看下面的代码: var person = { name:'Theo Wong', gender:'male', getName:function(){ console.log(person.name); } }; person.getName(); 定义了一个perso

深入理解javascript中的this 关键字(转载)

在JavaScript中this变量是一个令人难以摸清的关键字,this可谓是非常强大,充分了解this的相关知识有助于我们在编写面向对象的JavaScript程序时能够游刃有余. 1.一般用处 对于this关键字最重要的是明确this所引用的对象是哪一个,也许很多资料上都有自己的解释,但有些概念将的偏繁杂.而我的理解是:首先分析this所在的函数是当做哪个对象的方法调用的,则该对象就是this所引用的对象. Example 一: var obj = {}; obj.x = 100; obj.y

理解javascript中的with关键字

说起js中的with关键字,很多小伙伴们的第一印象可能就是with关键字的作用在于改变作用域,然后最关键的一点是不推荐使用with关键字.听到不推荐with关键字后,我们很多人都会忽略掉with关键字,认为不要去管它用它就可以了.但是有时候,我们在看一些代码或者面试题的时候,其中会有with关键字的相关问题,很多坑是你没接触过的,所以还是有必要说说with这一个关键字. 一.基本说明 在js高级程序设计中是这样描述with关键字的:with语句的作用是将代码的作用域设置到一个特定的作用域中,基本

深入理解JavaScript中创建对象模式的演变(原型)

创建对象的模式多种多样,但是各种模式又有怎样的利弊呢?有没有一种最为完美的模式呢?下面我将就以下几个方面来分析创建对象的几种模式: Object构造函数和对象字面量方法 工厂模式 自定义构造函数模式 原型模式 组合使用自定义构造函数模式和原型模式 动态原型模式.寄生构造函数模式.稳妥构造函数模式 第一部分:Object构造函数和对象字面量方法 我之前在博文<javascript中对象字面量的理解>中讲到过这两种方法,如何大家不熟悉,可以点进去看一看回顾一下.它们的优点是用来创建单个的对象非常方

深入理解JavaScript中的属性和特性

深入理解JavaScript中的属性和特性? JavaScript中属性和特性是完全不同的两个概念,这里我将根据自己所学,来深入理解JavaScript中的属性和特性. 主要内容如下: 理解JavaScript中理解对象的本质.理解对象与类的关系.对象与引用类型的关系 对象属性如何进行分类 属性中特性的理解 第一部分:理解JavaScript中理解对象的本质.理解对象与类的关系.对象与引用类型的关系 对象的本质:ECMA-262把对象定义为:无序属性的集合,其属性可以包含基本值.对象或者函数.即

【干货理解】理解javascript中实现MVC的原理

理解javascript中的MVC MVC模式是软件工程中一种软件架构模式,一般把软件模式分为三部分,模型(Model)+视图(View)+控制器(Controller); 模型:模型用于封装与应用程序的业务逻辑相关的数据以及对数据处理的方法.模型有对数据直接访问的权利.模型不依赖 "视图" 和 "控制器", 也就是说 模型它不关心页面如何显示及如何被操作. 视图:视图层最主要的是监听模型层上的数据改变,并且实时的更新html页面.当然也包括一些事件的注册或者aja

JavaScript大杂烩6 - 理解JavaScript中的this

在JavaScript开发中,this是很常用的一个关键字,但同时也是一个很容易引入bug的一个关键字,在这里我们就专门总结一下页面中可能出现的this关键字(包括几种在其他页面文件中出现的this). JavaScript中的this关键字通常只使用在函数中,它指向当前函数的调用者,这是this关键字的本质,所有的使用方式都是围绕这个展开的,让我们来看一下在各种性质的函数中this的用法.1. 在对象的函数中使用this var person = { name: 'Frank', say: f