用自然语言的角度理解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.bind让run
运行时候的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
的对象呢?你可以自己尝试看看。
call
与apply
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.apply和Function.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
过,也不是被apply
或call
所调用。
那么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
,而是页面上id
为text
的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
的用法的确是千奇百怪,但是如果利用自然语言的方式来理解,一切就顺理成章了。
用自然语言的角度理解JavaScript中的this关键字的更多相关文章
- 转载 深入理解JavaScript中的this关键字
转载原地址: http://www.cnblogs.com/rainman/archive/2009/05/03/1448392.html 深入理解JavaScript中的this关键字 1. 一 ...
- 如何理解JavaScript中的this关键字
前言 王福朋老师的 JavaScript原型和闭包系列 文章看了不下三遍了,最为一个初学者,每次看的时候都会有一种 "大彻大悟" 的感觉,而看完之后却总是一脸懵逼.原型与闭包 可以 ...
- 浅显易懂的理解JavaScript中的this关键字
在JavaScript中this变量是一个令人难以摸清的关键字,this可谓是非常强大,充分了解this的相关知识有助于我们在编写面向对象的JavaScript程序时能够游刃有余. 1. 一般用处 对 ...
- 正确理解JavaScript中的this关键字
JavaScript有this关键字,this跟JavaScript的执行上下文密切相关,很多前端开发工程师至今对this关键字还是模棱两可,本文将结合代码讲解下JavaScript的this关键字. ...
- 深入理解JavaScript中的this关键字
1. 一般用处 2. this.x 与 apply().call() 3. 无意义(诡异)的this用处 4. 事件监听函数中的this 5. 总结 在JavaScript中this变量是一个令人难以 ...
- 理解JavaScript中的this关键字
JavaScript中this关键字理解 在爬虫的过程中遇到了前端的js代码,对于this关键字理解的不是很清楚,所以写下这篇笔记,不足之处,希望得以改之. this的指向在函数定义的时候无法确定,只 ...
- 理解javascript中的with关键字
说起js中的with关键字,很多小伙伴们的第一印象可能就是with关键字的作用在于改变作用域,然后最关键的一点是不推荐使用with关键字.听到不推荐with关键字后,我们很多人都会忽略掉with关键字 ...
- 理解JavaScript中的原型继承(2)
两年前在我学习JavaScript的时候我就写过两篇关于原型继承的博客: 理解JavaScript中原型继承 JavaScript中的原型继承 这两篇博客讲的都是原型的使用,其中一篇还有我学习时的错误 ...
- 如何理解JavaScript中的函数
转: 如何理解JavaScript中的函数 JS中的函数简介 JS中的函数是一种通过调用来完成具体业务的一段代码块.最核心的目的是将可重复执行的操作进行封装,然后供调用方无限制的调用. JS中的函数的 ...
随机推荐
- Maven提高篇系列之(一)——多模块 vs 继承
这是一个Maven提高篇的系列,包含有以下文章: Maven提高篇系列之(一)——多模块 vs 继承 Maven提高篇系列之(二)——配置Plugin到某个Phase(以Selenium集成测试为例) ...
- Node.js基于Express框架搭建一个简单的注册登录Web功能
这个小应用使用到了node.js bootstrap express 以及数据库的操作 :使用mongoose对象模型来操作 mongodb 如果没了解过的可以先去基本了解一下相关概念~ 首先注 ...
- 实现虚拟模式的动态数据加载Windows窗体DataGridView控件 .net 4.5 (一)
实现虚拟模式的即时数据加载Windows窗体DataGridView控件 .net 4.5 原文地址 :http://msdn.microsoft.com/en-us/library/ms171624 ...
- (旧)子数涵数·C语言——条件语句
首先,我们讲一下理论知识,在编程中有三种结构,分别是顺序结构.条件结构.循环结构,如果用流程图来表示的话就是: 那么在C语言中,如何灵活运用这三种结构呢?这就需要用到控制语句了. 而条件语句便是控制语 ...
- linux系统如何将系统中的文件名改为英文?
由于我们经常在命令行模式下进入文件,那么中英文的切换常常会影响我们输入的效率. 那么如何将原来的中文修改成英文的字幕呢? 如下图所示: -------------------------------- ...
- Hibernate的缓存技术详解
转载注明出处:http://www.cnblogs.com/xiaoming0601/p/5882980.html 一.什么是缓存: 并不是指计算机的内存或者CPU的一二级缓存:缓存是指为了降低应用程 ...
- 非线性数据拟合-nls
code{white-space: pre;} pre:not([class]) { background-color: white; }if (window.hljs && docu ...
- JavaScript 中有关数组对象的方法
JS 处理数组多种方法 js 中的数据类型分为两大类:原始类型和对象类型. 原始类型包括:数值.字符串.布尔值.null.undefined 对象类型包括:对象即是属性的集合,当然这里又两个特殊的对象 ...
- margin和padding对行内元素的影响
这个是在面试的时候,面试官问我的一个小问题 自己没有考虑过inline元素设置margin和padding的问题 学习的过程记录下来 1)inline元素的高度是由元素的内容决定的(字体的大小和行高) ...
- 趣味问题:画图(c++实现)
描述:在一个定义了直角坐标系的纸上,画一个(x1,y1)到(x2,y2)的矩形指将横坐标范围从x1到x2,纵坐标范围从y1到y2之间的区域涂上颜色.下图给出了一个画了两个矩形的例子.第一个矩形是(1, ...