this是JavaScript中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。

一、this到底指向什么?

this既不指向函数自身,也不指向函数的词法作用域,具体指向什么,取决于你是怎么调用函数。

  1. 直接使用不带任何修饰的函数引用进行调用(即:方法名 + 括号), this指向全局对象(非严格模式)或者undefined(严格模式), 这种绑定称为默认绑定
function foo() {
console.log(this.a);
}
var a = 2;
foo(); // 2

严格模式:

function foo() {
"use strict"
console.log(this.a);
}
var a = 2;
foo(); // undefiend
  1. 如果方法是某个对象的一个属性,通过该对象调用方法(即调用位置存在上下文对象),则this指向该对象, 这种绑定称为隐式绑定.
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
}
obj.foo(); // 2

注意事项

  • 如果多个对象之间形成了引用链,方法中的this指向其最顶层或者最后一层对象。
function foo() {
console.log(this.a);
}
var obj1 = {
a: 42,
foo: foo
} var obj2 = {
a: 3,
obj1: obj1
}
obj2.obj1.foo() // 42
  • 隐式丢失,被隐式绑定的函数会丢失绑定对象,也即会应用默认绑定,从而把this绑定到全局对象或者undefined上。

    这种情况常出现在回调函数上:
function foo() {
console.log(this.a);
}
function doFoo(fn) {
// fn 其实引用的是foo
fn();
}
var obj = {
a: 2,
foo: foo
}
var a = "oops, global"; doFoo(obj.foo) // 输出"oops, global"
  1. 使用call, apply方法调该函数时,this指向call,apply方法所传入的对象,这种方式成为显示绑定

    js中几乎所有的函数都有call()和apply()方法,这两个方法的第一个参数是一个对象,他们会把这个对象绑定到this.

function foo() {
console.log(this.a);
}
var obj = {
a: 2,
}
foo.call(obj); // 2

显示绑定仍然存在绑定丢失的问题:例如

function foo() {
console.log(this.a);
}
function doFoo(fn) {
fn();
}
var obj = {
a: 2,
}
var a = "oops, global"; doFoo.call(obj, foo); // 输出oops, global

解决办法:

  • ++硬绑定++

    在doFoo函数内,直接绑定obj对象
function doFoo(fn) {
fn.call(obj);
}

硬绑定一种常见的应用常见是创建一个可重复使用的辅助函数

function foo(something) {
console.log(this.a, something);
return this.a + something;
}
// 简单的辅助绑定函数
function bind(fn, obj) {
return function() {
return fn.apply(obj, arguments);
}
}
var obj = {a:2};
var bar = bind(foo, obj);
var b = bar(3); // 输出2 3
console.log(b); // 输出5

ES5中提供了内置的硬绑定的方法: Function.prototype.bind, 用法如下:

function foo(something) {
console.log(this.a, something);
return this.a + something;
}
var obj = {
a:2;
}
var bar = foo.bind(obj);
var b = bar(3); // 2 3
console.log(b); // 5

bind(..) 会返回一个硬编码的新函数,它会把参数设置为 this的上下文并调用原始函数。

4. new绑定

在javascript中,所有的函数都可以用new来调用,这种函数调用被称为构造函数调用。使用new来调用函数时,会自动执行下面操作:

1. 创建一个全新的对象;
2. 这个新对象会被执行[[原型]链接;
3. 这个新对象会绑定到函数调用的this
4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象

示例:

function foo(a) {
this.a = a; // 在这里,this绑定到bar对象
}
var bar = new foo(2);
console.log(bar.a); // 2

二、如果函数调用时,既满足隐式绑定、又满足显示绑定或new绑定,该怎么办?

new绑定优先级 > 显示绑定 > 隐式绑定 > 默认绑定

  • 比较显示绑定和隐式绑定,new绑定和隐式绑定
function foo(something) {
this.a = something;
}
var obj1 = {foo: foo};
var obj2 = {}; obj1.foo.call(obj2, 3); // foo函数的调用同时出现了隐式绑定和显示绑定,则显示绑定优先,this指向 obj2
console.log(obj2.a); // obj2.a = 3 var bar = new obj1.foo(4); // 同时出现了隐式绑定和new绑定, this指向new绑定创建的对象bar, 而不是obj1
console.log(bar.a); // 输出4
  • 比较new绑定和显示绑定:
function foo(something) {
this.a = something;
}
var obj1 = {}; var bar = foo.bind( obj1 ); bar( 2 ); console.log( obj1.a ); // 2 var baz = new bar(3); console.log( obj1.a ); // 2 console.log( baz.a ); // 3

bar 被硬绑定到 obj1 上,但是 new bar(3) 并没有像我们预计的那样把 obj1.a

修改为 3。相反, new 修改了硬绑定(到 obj1 的)调用 bar(..) 中的 this

三、特殊情况

  1. 调用call或者apply时,参数传入null或者undefined, 此时this会采用默认绑定
function foo() {
console.log(this.a);
}
var a = 2;
foo.call(null); // 输出2
  1. 函数的间接引用,此时this采用默认绑定。间接引用最常在赋值时发生:
function foo() {
console.log(this.a);
} var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
(p.foo = o.foo)(); // 2

赋值表达式 p.foo = o.foo 的返回值是目标函数的引用,因此调用位置是 foo() 而不是p.foo() 或者 o.foo()

  1. 箭头函数

前面提到的四种绑定规则对箭头函数不适用,箭头函数中的this是根据外层作用域来决定的。

function foo() {
return (a) => {
console.log(this.a);
}
} var obj1 = {a:2};
var obj2 = {a:3}; var bar = foo.call(obj1); // 箭头函数的外层作用域中的this指向 obj1, 所以箭头函数中this也指向obj1,箭头函数的绑定无法被修改
bar.call(obj2); // 输出2,

四、 小结

判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置,找到后就可以顺序应用这四条规则判断this的绑定对象。

  1. 由new调用? 绑定到新创建的对象;
  2. 由call或者apply调用?绑定到指定对象;
  3. 由上下文调用?绑定到上下文对象;
  4. 默认:严格模式下绑定到undefined,否则绑定到全局对象;

另外还需要注意上面提到的几种特殊情况。

JS中this的那些事儿的更多相关文章

  1. 浅谈JS中的闭包

    浅谈JS中的闭包 在介绍闭包之前,我先介绍点JS的基础知识,下面的基础知识会充分的帮助你理解闭包.那么接下来先看下变量的作用域. 变量的作用域 变量共有两种,一种为全局变量,一种为局部变量.那么全局变 ...

  2. node(基础)_node.js中的http服务以及模板引擎的渲染

    一.前言 本节的内容主要涉及: 1.node.js中http服务 2.node.js中fs服务 3.node.js中模板引擎的渲染 4.利用上面几点模拟apache服务器 二.知识 1.node.js ...

  3. js中JSON和JSONP的区别,让你从懵逼到恍然大悟

    说到AJAX就会不可避免的面临两个问题,第一个是AJAX以何种格式来交换数据?第二个是跨域的需求如何解决?这两个问题目前都有不同的解决方案,比如数据可以用自定义字符串或者用XML来描述,跨域可以通过服 ...

  4. 前端走进机器学习生态,在 Node.js 中使用 Python

    这次给大家带来一个好东西,它的主要用途就是能让大家在 Node.js 中使用 Python 的接口和函数.可能你看到这里会好奇,会疑惑,会不解,我 Node.js 大法那么好,干嘛要用 Python ...

  5. 5.0 JS中引用类型介绍

    其实,在前面的"js的六大数据类型"文章中稍微说了一下引用类型.前面我们说到js中有六大数据类型(五种基本数据类型 + 一种引用类型).下面的章节中,我们将详细讲解引用类型. 1. ...

  6. 总结iOS开发中的断点续传那些事儿

    前言 断点续传概述 断点续传就是从文件赏赐中断的地方重新开始下载或者上传数据,而不是从头文件开始.当下载大文件的时候,如果没有实现断点续传功能,那么每次出现异常或者用户主动的暂停,都会从头下载,这样很 ...

  7. 【repost】JS中的异常处理方法分享

    我们在编写js过程中,难免会遇到一些代码错误问题,需要找出来,有些时候怕因为js问题导致用户体验差,这里给出一些解决方法 js容错语句,就是js出错也不提示错误(防止浏览器右下角有个黄色的三角符号,要 ...

  8. JS中给正则表达式加变量

    前不久同事询问我js里面怎么给正则中添加变量的问题,遂写篇博客记录下.   一.字面量 其实当我们定义一个字符串,一个数组,一个对象等等的时候,我们习惯用字面量来定义,例如: var s = &quo ...

  9. js中几种实用的跨域方法原理详解(转)

    今天研究js跨域问题的时候发现一篇好博,非常详细地讲解了js几种跨域方法的原理,特分享一下. 原博地址:http://www.cnblogs.com/2050/p/3191744.html 下面正文开 ...

随机推荐

  1. Qt_模块简介

    Qt4 和 Qt5最大的区别之一就是底层架构有了修改.Qt5引入了模块化的概念,讲众多功能细分到几个模块之中.需要达到,用什么模块知道到哪个模块去寻找. Qt5模块分为Essentials Modul ...

  2. (记忆化搜索 )The Triangle--hdu --1163

    http://poj.org/problem?id=1163     Description 73 88 1 02 7 4 44 5 2 6 5 (Figure 1) Figure 1 shows a ...

  3. tarjan算法的补充POJ2533tarjan求度

    做题时又遇到了疑惑,说明一开始就没有完全理解 基于dfs的tarjan,搜索时会有四种边 树枝边:DFS 时经过的边,即 DFS 搜索树上的边 前向边:与 DFS 方向一致,从某个结点指向其某个子孙的 ...

  4. Java学习--循环语句

    1. break public class BreakDemo{ // 完成一个四则运算的功能 public static void main(String args[]){ for(int i=0; ...

  5. 前端开发 - JavaScript

    本节内容 一.如何编写 二.变量 三.数据类型 四.其他 五.语句与异常 六.函数 JavaScript是一门编程语言,浏览器内置了JavaScript语言的解释器,所以在浏览器上按照JavaScri ...

  6. c# List使用中遇到的问题

    最近在项目上写的方法,想通过减少访问数据层,将需要重复调用的值存入List,无意中碰到的一个巨坑,至今仍不明所以,在此写出来,一来是看看有没有同道中人,二来是看看有没有大牛能解惑. 逻辑如下: 1.从 ...

  7. WPF学习笔记(4):获取DataGridTemplateColumn模板定义的内容控件

    在之前的DataGrid的DataGridTemplateColumn列中,自定义了一个TextBox控件,但是在C#代码中提示找不到这个控件,导致无法对该控件进行操作.在网上搜索后,发现一些处理方法 ...

  8. .NET MVC 学习笔记(七)— 控制input控件

    .NET MVC 学习笔记(七)— 控制input控件 画面中有时候需要输入数字,这时就需要控制input的输入.以下为保留两位有效数字. /* * 初始化数字输入 */ function initD ...

  9. 【转】C#中dynamic的正确用法

    原文:http://www.cnblogs.com/qiuweiguo/archive/2011/08/03/2125982.html dynamic是FrameWork4.0的新特性.dynamic ...

  10. svn重新安装后报You need to upgrade the working copy first错误

    问题来源 最近重新安装了操作系统,安装了一个最新版的svn,提交代码的时候报了一个错误:You need to upgrade the working copy first,!网上找了很多解决办法,都 ...