JavaScript this 总结(含 ES6)
本文主要总结自《JavaScript 语言精粹》、部分总结自《JavaScript 高级程序设计》以及自己的经验
四种调用模式
在 JavaScript 中,this 的值取决于调用模式,有四种调用模式,分别是方法调用模式、函数调用模式、构造器调用模式、Apply、call 调用模式。
方法调用模式
当一个函数被保存为对象的一个属性时,我们称它为一个方法。当方法被调用时(通过 . 表达式或 [subscript] 下标表达式),this 绑定到该对象。
var name = "window",
lzh = {
name: "lzh",
sayName: function(){
alert(this.name); // 输出 "lzh"
}
}
lzh.sayName();
函数调用模式
当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用的,以此模式调用函数时,this 被绑定到全局对象。
这是语言设计上的一个错误。倘若语言设计正确,那么当内部函数被调用时,this 应该仍然绑定到外部函数的 this 变量。
这个设计错误的后果是方法不能利用内部函数来帮助它工作。
ECMAScript6 的箭头函数(注意只是箭头函数)基本纠正了这个设计上的错误(注意只是基本上,但不是彻底地纠正了错误)
var name = "window",
lzh = {
name: "lzh",
sayName: function(){
innerFunction();
function innerFunction(){
alert(this.name);
}
return function(){
alert(this.name);
}
}
}
lzh.sayName()();
上面这段代码 alert 的均是 window,从上面可以看出,不管外部环境的 this 是不是 window,通过函数调用模式调用的函数,this 指向 window。
来看一段 ES6 箭头函数中的 this (上面提到箭头函数基本纠正了设计上的错误)
var name = 'window';
var lzh = {
name: 'lzh',
sayName: function(){
return ()=> {
console.log(this.name);
}
}
}
var iny = {
name: 'iny'
}
lzh.sayName().apply(iny); // 输出 lzh
其实转换成 ES5 是这么干的:
var name = 'window';
var lzh = {
name: 'lzh',
sayName: function(){
var _this = this;
return function(){
console.log(_this.name);
}
}
}
var iny = {
name: 'iny'
}
lzh.sayName().apply(iny); // lzh
但如果ES6 中这么写
var name = "window";
var lzh = {
name: 'lzh',
sayName: () => {
console.log(this.name)
}
}
var iny = {
name: 'iny'
}
lzh.sayName(); // window
lzh.sayName.apply(iny); // window
转换成 ES5 却是这样的
var name = "window";
var _this = this;
var lzh = {
name: 'lzh',
sayName: function() {
console.log(_this.name)
}
};
var iny = {
name: 'iny'
}
lzh.sayName(); // window
lzh.sayName.apply(iny); // window
// 有点失望
构造器调用模式
JavaScript 是一门基于原型继承的语言。这意味着对象可以直接从其他对象继承属性。该语言是无类型的。
当今(书的中文版第一版出版时间是2009年)大多数语言都是基于类的语言。尽管原型继承极富表现力,但它未被广泛理解。
JavaScript 本身对它原型的本质也缺乏信心,所以它提供了一套和基于类的语言类似的对象构建语法。
如果在一个函数前面带上 new 来调用,那么背地里将会创建一个连接到该函数的 prototype 成员的新对象,同时 this 会绑定到那个新对象上
如果构造函数返回的不是对象,则通过 new 调用构造函数返回背地里创建的对象。
var Person = function(name){
this.name = name;
}
Person.prototype.getName = function(){
return this.name;
}
var lzh = new Person("lzh");
console.log(lzh.getName()); // lzh
Apply、call 调用模式
因为 JavaScript 是一门函数式的面向对象编程语言,所以函数可以拥有方法。
每个函数都包含两个非继承而来的方法:apply()和 call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。首先,apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是 Array 的实例,也可以是arguments 对象。call()方法与 apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于 call()方法而言,第一个参数是 this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来
function sum(num1, num2){
console.log(this);
return num1 + num2;
}
function callSum1(num1, num2){
return sum.apply(this, arguments); // 传入 arguments 对象
}
function callSum2(num1, num2){
return sum.apply(null, [num1, num2]); // 传入数组
}
function callSum3(num1, num2){
return sum.call(null, num1, num2); // 一个一个地传递参数
}
alert(callSum1(10,10)); //20
alert(callSum2(10,10)); //20
alert(callSum3(10,10)); //20
从上面的代码可以看出,apply 的第一个参数是一个改变 sum 函数的 this 的值,但这里不论传进去的是 window 还是 null,内部 console.log 出来的都是 window 对象,还可以看出,apply 的第二个参数要么是 arguments、要么是一个数组。call 从第二个参数开始,就要一个一个的传递参数,而不能传递数组或arguments。
单从上面的代码,不能很好的看出 apply、call 的长处,既然 call 能设置 this,那么就能复用其它对象的方法,比如下面这个:
var lzh = {
name: 'lzh',
say: function(something){
alert(something + this.name);
}
}
var iny = {
name: 'iny'
}
lzh.say.apply(iny, ['hi, I am ']); // 输出 hi I am iny
- iny 对象没有 say 方法,但是又希望复用 lzh 的 say 方法,那么就可以用 apply 或 call
- 这样还是不能很明显的看出 call、apply 的优越性,举个现实点的例子,如何把 arguments 转换成数组,因为 arguments 不是数组,是一个 Array like 的对象,就是有下标元素,可以通过 arguments[0]、arguments[1] 来访问它的元素,但它没有数组的各种方法,比如
pop
,shift
,slice
等,操作 arguments 会不大方便,所以我们希望把 arguments 转换成 数组。如果我们大概明白 Array.prototype.slice 的实现原理的话,我们可以利用这个方法将 arguments 转换成数组。 - 第一步,讲一下 Array.prototype.slice 简易版的大概实现原理(原版应该是使用 C++ 实现的,功能和性能上都比这个简易版的要好):
Array.prototype.slice = function(start, end){
var newArray = [];
if(start >= 0 && end <= this.length){
for(var i = start; i < end; i++){
newArray.push(this[i]);
}
}
return newArray;
}
var testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.log(testArray.slice(0, 2)); // [1, 2]
- 从上面我们可以看出,假如我们用 Array.prototype.slice.apply(arguments, 0, arguments.length),就相当于把 slice 内部的 this 换成了 arguments; 就可以把 arguments[0]、arguments[1]...等 push 到一个新数组,这样就可以成功的把 arguments 转换成数组了,于是就可以利用数组的各种方法操作参数。当然,这里只是简易地重写了一遍 slice,真实的 slice 可以不传递这里的第三个参数,默认从 0 截取到末尾。
- apply、call 在实现函数柯里化、对象继承上也有很大的作用,这里不详细展开。
匿名函数中的 this
- 在网上看了很多关于 this 的博客,都有介绍到匿名函数中的 this 指向 window 对象,但这种说法是不正确的,关键还是要看怎么调用(就是前面介绍的4中调用方式),比如下面的代码
var name = "window",
lzh = function(){
return function(){
//这里是匿名函数,但是 this 的值只有在调用的时候才能确定
alert(this.name);
}
}
var iny = {
name: 'iny',
sayName: lzh()
}
lzh()(); // window
iny.sayName(); // iny
从上面可以看出,this 的值在调用的时候决定
- 还有就是事件处理程序里面的 this
在 DOM0 级、DOM2 级的事件处理程序中(onclick/addEventListener),this 指向绑定事件的那个元素,虽然不知道浏览器内部的具体实现,但可以猜测它是由 DOM 对象以方法调用的,属于 DOM 对象的方法,而在 IE 旧版本的实现中,attachEvent 指定的事件处理程序的调用模式应该是函数调用模式,所以 this 指向 window。如有错误,还请指出。 - setTimeout、setInterval 里面的 this 也指向 window,这个应该还是由调用模式决定的。
下面看几道题目
- 题目1
var name = "window",
lzh = {
name: "lzh",
sayName: function(){
alert(this.name);
alert(name);
}
}
lzh.sayName();
题目1先 alert lzh
,再 alert window
,alert window 的原因是:sayName 实际上是一个闭包,它的活动对象里有 this(指向 lzh 对象),但没有 name,所以它往父级作用域链寻找 name
, 于是找到了全局作用域的变量对象中的 windw
,所以 alert window
,如果想了解闭包的更多内容,可以点这里。
- 题目2
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
滑动查看答案:alert 的是 "The Window"
- 题目3
var name = "window",
person = {
name: 'lzh',
getName: function(){
return this.name;
}
}
console.log(person.getName());
console.log((person.getName)());
console.log((person.getName = person.getName)());
滑动查看答案:输出顺序:lzh、lzh、window
- 如果本文对您有帮助,不妨点赞一下,您的鼓励是我的动力,我会更努力地写出好文章。
JavaScript this 总结(含 ES6)的更多相关文章
- JS的prototype和__proto__(含es6的class)
JS的prototype和__proto__(含es6的class) 一.prototype和__proto__的概念 prototype是函数的一个属性(每个函数都有一个prototype属性),这 ...
- javaScript - 面向对象 - ES5 和 ES6
javaScript - 面向对象 - ES5 和 ES6 ES5之前用 构造函数 构造函数的特点 就是一个普通函数, 他的函数名要大写.: 带方法的写法: 原型的方式: prototype 为内置的 ...
- JavaScript、TypeScript、ES6三者之间的联系和区别
ES6是什么 ECMAScript 6.0(以下简称ES6)是JavaScript语言(现在是遵循ES5标准)的下一代标准,已经在2015年6月正式发布了.它的目标,是使得JavaScript语言可以 ...
- JavaScript Learning Paths(ES5/ES6/ES-Next)
JavaScript Learning Paths(ES5/ES6/ES-Next) JavaScript Expert refs https://developer.mozilla.org/en-U ...
- javaScript高级含Es6
JavaScript高级第01天笔记 1.面向过程与面向对象 1.1面向过程 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了. 1.2 ...
- JS正则表达式语法(含ES6)(表格简要总结)
文章目录 JS正则表达式 1. JS中正则表达式定义 2. 直接量字符 3. 字符类 4. 重复字符 5. 选择,分组和引用 6. 指定匹配位置 7. 修饰符 8. String 方法 9. RegE ...
- 【转】浅谈JavaScript、ES5、ES6
什么是JavaScript JavaScript一种动态类型.弱类型.基于原型的客户端脚本语言,用来给HTML网页增加动态功能.(好吧,概念什么最讨厌了) 动态: 在运行时确定数据类型.变量使用之前不 ...
- 浅谈JavaScript、ES5、ES6
// http://es6.ruanyifeng.com/#docs/intro (ES6 文档) 什么是JavaScript JavaScript一种动态类型.弱类型.基于原型的客户端脚本语言,用来 ...
- 6周学习计划,攻克JavaScript难关(React/Redux/ES6 etc.)
作者:余博伦链接:https://zhuanlan.zhihu.com/p/23412169来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 和大家一样,最近我也看了Jo ...
随机推荐
- log4net注意事项
log4net的配置信息可以直接配置在系统的配置文件中,也可以单独写一个配置文件,文件名随便起,如log4net.config,单独的文件属性“复制到输出目录”应该是true.因为log4net框架会 ...
- python学习之——小闹钟(持续完善ing)
"啊,坏了,我忘了那啥啥了~~~" 为了不坏了,动手做一个小闹钟吧,一点点完善的过程一定美好极了,必像等待培育许久的花儿绽放一样,不多说,加油,期待↖(^ω^)↗ #! /usr/ ...
- js判断用户是否禁用了cookie
function CookieEnable() { var result = false; if (navigator.cookiesEnabled) return true; document.co ...
- java基础(1)-比较jdk5,jdk6,jdk7的新特性
jdk8已经出来好长时间了,这里自己学习时简单总结的jdk5,jdk6和jdk7的新特性:本文提纲: 一.jdk5的新特性 二.jdk6的新特性 三.jdk7的新特性 一.jdk5的新特性 首先简单介 ...
- XE3随笔9:使用不同的数据类型标记数组
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, For ...
- poj练习题的方法
poj1010--邮票问题 DFSpoj1011--Sticks dfs + 剪枝poj1020--拼蛋糕poj1054--The Troublesome Frogpoj1062--昂贵的聘礼poj1 ...
- 30、准确计算CoreText高度的方法
http://ios-iphone.diandian.com/post/2012-03-29/18389515 - (int)getAttributedStringHeightWithString:( ...
- [VBS]关机恶作剧
一.关于脚本 1)本文中的脚本完成以下功能: 随机生成3道二位数加法题,如果答题错误则在60秒后关机. 如果全答对了,也会在60后关机,但脚本会提示解除定时关机的办法 2)在脚本运行过程中,退出本脚本 ...
- 1、iOS9 HTTP 不能正常使用的解决办法
升级Xcode7.0 bata发现网络请求访问失败 输出的错误信息: The resource could not be loaded because the App Transport Securi ...
- 奇葩问题之ToolBar返回键失效
今天遇到一个奇葩问题,先说现象:接口调用成功,但是在后台业务上报错时(比如:手机号已经被注册过时,接口调用成功,但是后台返回了错误信息:手机号已被注册),toolBar的返回键失效了. 后来仔细看了一 ...