前面的话

  如果要问javascript中哪两个知识点容易混淆,作用域查询和this机制绝对名列前茅。前面的作用域系列已经详细介绍过作用域的知识。本系列开始将介绍javascript的另一大山脉——this机制。本文是该系列的第一篇——this的4种绑定规则

默认绑定

  全局环境中,this默认绑定到window

console.log(this === window);//true

  函数独立调用时,this默认绑定到window

function foo(){
console.log(this === window);
}
foo(); //true

  被嵌套的函数独立调用时,this默认绑定到window

//虽然test()函数被嵌套在obj.foo()函数中,但test()函数是独立调用,而不是方法调用。所以this默认绑定到window
var a = 0;
var obj = {
a : 2,
foo:function(){
function test(){
console.log(this.a);
}
test();
}
}
obj.foo();//

【IIFE】

  IIFE立即执行函数实际上是函数声明后直接调用执行

var a = 0;
function foo(){
(function test(){
console.log(this.a);
})()
};
var obj = {
a : 2,
foo:foo
}
obj.foo();//
//等价于上例
var a = 0;
var obj = {
a : 2,
foo:function(){
function test(){
console.log(this.a);
}
test();
}
}
obj.foo();//0

【闭包】

  类似地,test()函数是独立调用,而不是方法调用,所以this默认绑定到window

  [注意]函数共有4种调用方式,函数调用相关内容移步至此

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

  由于闭包的this默认绑定到window对象,但又常常需要访问嵌套函数的this,所以常常在嵌套函数中使用var that = this,然后在闭包中使用that替代this,使用作用域查找的方法来找到嵌套函数的this值

var a = 0;
function foo(){
var that = this;
function test(){
console.log(that.a);
}
return test;
};
var obj = {
a : 2,
foo:foo
}
obj.foo()();//

隐式绑定

  一般地,被直接对象所包含的函数调用时,也称为方法调用,this隐式绑定到该直接对象

function foo(){
console.log(this.a);
};
var obj1 = {
a:1,
foo:foo,
obj2:{
a:2,
foo:foo
}
} //foo()函数的直接对象是obj1,this隐式绑定到obj1
obj1.foo();// //foo()函数的直接对象是obj2,this隐式绑定到obj2
obj1.obj2.foo();//

隐式丢失

  隐式丢失是指被隐式绑定的函数丢失绑定对象,从而默认绑定到window。这种情况容易出错却又常见

【函数别名】

var a = 0;
function foo(){
console.log(this.a);
};
var obj = {
a : 2,
foo:foo
}
//把obj.foo赋予别名bar,造成了隐式丢失,因为只是把foo()函数赋给了bar,而bar与obj对象则毫无关系
var bar = obj.foo;
bar();//
//等价于
var a = 0;
var bar = function foo(){
console.log(this.a);
}
bar();//

【参数传递】

var a = 0;
function foo(){
console.log(this.a);
};
function bar(fn){
fn();
}
var obj = {
a : 2,
foo:foo
}
//把obj.foo当作参数传递给bar函数时,有隐式的函数赋值fn=obj.foo。与上例类似,只是把foo函数赋给了fn,而fn与obj对象则毫无关系
bar(obj.foo);//
//等价于
var a = 0;
function bar(fn){
fn();
}
bar(function foo(){
console.log(this.a);
});

【内置函数】

  内置函数与上例类似,也会造成隐式丢失

var a = 0;
function foo(){
console.log(this.a);
};
var obj = {
a : 2,
foo:foo
}
setTimeout(obj.foo,100);//
//等价于
var a = 0;
setTimeout(function foo(){
console.log(this.a);
},100);//

【间接引用】

   函数的"间接引用"一般都在无意间创建,最容易在赋值时发生,会造成隐式丢失

function foo() {
console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); //
//将o.foo函数赋值给p.foo函数,然后立即执行。相当于仅仅是foo()函数的立即执行
(p.foo = o.foo)(); //
function foo() {
console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); //
//将o.foo函数赋值给p.foo函数,之后p.foo函数再执行,是属于p对象的foo函数的执行
p.foo = o.foo;
p.foo();//

【其他情况】

  在javascript引擎内部,obj和obj.foo储存在两个内存地址,简称为M1和M2。只有obj.foo()这样调用时,是从M1调用M2,因此this指向obj。但是,下面三种情况,都是直接取出M2进行运算,然后就在全局环境执行运算结果(还是M2),因此this指向全局环境

var a = 0;
var obj = {
a : 2,
foo:foo
};
function foo() {
console.log( this.a );
}; (obj.foo = obj.foo)();// (false || obj.foo)();// (1, obj.foo)();//

显式绑定

  通过call()、apply()、bind()方法把对象绑定到this上,叫做显式绑定。对于被调用的函数来说,叫做间接调用

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

  普通的显式绑定无法解决隐式丢失问题

var a = 0;
function foo(){
console.log(this.a);
}
var obj1 = {
a:1
};
var obj2 = {
a:2
};
foo.call(obj1);//
foo.call(obj2);//

【硬绑定】

  硬绑定是显式绑定的一个变种,使this不能再被修改

var a = 0;
function foo(){
console.log(this.a);
}
var obj = {
a:2
};
var bar= function(){
foo.call(obj);
}
//在bar函数内部手动调用foo.call(obj)。因此,无论之后如何调用函数bar,它总会手动在obj上调用foo
bar();//
setTimeout(bar,100);//
bar.call(window);//

【API】

  javascript中新增了许多内置函数,具有显式绑定的功能,如数组的5个迭代方法:map()、forEach()、filter()、some()、every()

var id = 'window';
function foo(el){
console.log(el,this.id);
}
var obj = {
id: 'fn'
};
[1,2,3].forEach(foo);//1 "window" 2 "window" 3 "window"
[1,2,3].forEach(foo,obj);//1 "fn" 2 "fn" 3 "fn"

new绑定

  如果函数或者方法调用之前带有关键字new,它就构成构造函数调用。对于this绑定来说,称为new绑定

  【1】构造函数通常不使用return关键字,它们通常初始化新对象,当构造函数的函数体执行完毕时,它会显式返回。在这种情况下,构造函数调用表达式的计算结果就是这个新对象的值

function fn(){
this.a = 2;
}
var test = new fn();
console.log(test);//{a:2}

  【2】如果构造函数使用return语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果

function fn(){
this.a = 2;
return;
}
var test = new fn();
console.log(test);//{a:2}

  【3】如果构造函数显式地使用return语句返回一个对象,那么调用表达式的值就是这个对象

var obj = {a:1};
function fn(){
this.a = 2;
return obj;
}
var test = new fn();
console.log(test);//{a:1}

  [注意]尽管有时候构造函数看起来像一个方法调用,它依然会使用这个新对象作为this。也就是说,在表达式new o.m()中,this并不是o

var o = {
m: function(){
return this;
}
}
var obj = new o.m();
console.log(obj,obj === o);//{} false
console.log(obj.constructor === o.m);//true

严格模式

  【1】严格模式下,独立调用的函数的this指向undefined

function fn(){
'use strict';
console.log(this);//undefined
}
fn(); function fn(){
console.log(this);//window
}
fn();

  【2】在非严格模式下,使用函数的call()或apply()方法时,null或undefined值会被转换为全局对象。而在严格模式下,函数的this值始终是指定的值

var color = 'red';
function displayColor(){
console.log(this.color);
}
displayColor.call(null);//red var color = 'red';
function displayColor(){
'use strict';
console.log(this.color);
}
displayColor.call(null);//TypeError: Cannot read property 'color' of null

最后

  this的四种绑定规则:默认绑定、隐式绑定、显式绑定和new绑定,分别对应函数的四种调用方式:独立调用、方法调用、间接调用和构造函数调用。

  分清这四种绑定规则不算难,比较麻烦的是需要练就火眼金睛,识别出隐式丢失的情况

  说到底,javascript如此复杂的原因是因为函数过于强大。因为,函数是对象,所以原型链比较复杂;因为函数可以作为值被传递,所以执行环境栈比较复杂;同样地,因为函数具有多种调用方式,所以this的绑定规则也比较复杂

  只有理解了函数,才算理解了javascript

  以上

深入理解this机制系列第一篇——this的4种绑定规则的更多相关文章

  1. 深入理解javascript函数系列第一篇——函数概述

    × 目录 [1]定义 [2]返回值 [3]调用 前面的话 函数对任何一门语言来说都是一个核心的概念.通过函数可以封装任意多条语句,而且可以在任何地方.任何时候调用执行.在javascript里,函数即 ...

  2. 深入理解javascript函数系列第一篇

    前面的话 函数对任何一门语言来说都是核心的概念.通过函数可以封装任意多条语句,而且可以在任何地方.任何时候调用执行.在javascript里,函数即对象,程序可以随意操控它们.函数可以嵌套在其他函数中 ...

  3. 深入理解this机制系列第二篇——this绑定优先级

    前面的话 上一篇介绍过this的绑定规则,那如果在函数的调用位置上同时存在两种以上的绑定规则应该怎么办呢?本文将介绍this绑定的优先级 显式绑定 pk 隐式绑定 显式绑定胜出 function fo ...

  4. 深入理解javascript作用域系列第一篇——内部原理

    × 目录 [1]编译 [2]执行 [3]查询[4]嵌套[5]异常[6]原理 前面的话 javascript拥有一套设计良好的规则来存储变量,并且之后可以方便地找到这些变量,这套规则被称为作用域.作用域 ...

  5. 深入理解javascript作用域系列第一篇

    前面的话 javascript拥有一套设计良好的规则来存储变量,并且之后可以方便地找到这些变量,这套规则被称为作用域.作用域貌似简单,实则复杂,由于作用域与this机制非常容易混淆,使得理解作用域的原 ...

  6. 深入理解javascript对象系列第一篇——初识对象

    × 目录 [1]定义 [2]创建 [3]组成[4]引用[5]方法 前面的话 javascript中的难点是函数.对象和继承,前面已经介绍过函数系列.从本系列开始介绍对象部分,本文是该系列的第一篇——初 ...

  7. 深入理解DOM事件机制系列第一篇——事件流

    × 目录 [1]历史 [2]事件冒泡 [3]事件捕获[4]事件流 前面的话 javascript操作CSS称为脚本化CSS,而javascript与HTML的交互是通过事件实现的.事件就是文档或浏览器 ...

  8. 深入理解定时器系列第一篇——理解setTimeout和setInterval

    × 目录 [1]setTimeout [2]setInterval [3]运行机制[4]作用[5]应用 前面的话 很长时间以来,定时器一直是javascript动画的核心技术.但是,关于定时器,人们通 ...

  9. 深入理解this机制系列第三篇——箭头函数

    × 目录 [1]痛点 [2]解决 [3]基本用法[4]回调函数[5]注意事项 前面的话 this机制与函数调用有关,而作用域则与函数定义有关.有没有什么是可以将this机制和作用域联系起来的呢?本文将 ...

随机推荐

  1. bootstrap内置网格式布局系统:

    bootstrap分为12栏,若想要一个元素占用一定的栏数的宽度,可以在这个元素上用一个特定的类,就比如说span1.span2....类. 定义的布局: 定义page-header类,在这个类当中为 ...

  2. django _meta方法

    models.Book._meta.'concrete_model': <class 'books.models.Book'> models.Book._meta.'related_fke ...

  3. gulp-babel使用

    各大浏览器厂商对es2015功能支持不完全,等到全部支持会等很长时间,如果现在使用es2015,可以选择babel一个将ES6/ES7写的代码转换为ES5代码的编译器. 我们选择使用gulp自动化编译 ...

  4. 【Beta】Daily Scrum Meeting总结

    团队博客目录:FTD团队博客目录 一.项目预期计划和现实进展 更换网络请求框架为okHttp 完成 补充和完善服务器的API 完成(可与web端互连) 补充和完善app与服务器交互的类和方法 完成 完 ...

  5. Javascript外部对象

    Window 浏览器: - location:地址 - history:历史 - Document:文档 - screen:窗口 - navigator:帮助 > 1.外部对象就是浏览器提供的A ...

  6. phpcms v9常用方法

    1.联动菜单根据地区id显示地区名称的方法: 显示效果: 四川 >> 攀枝花 >> 仁和区 [字段名字为 area] {get_linkage($info['area'],1, ...

  7. WPF整理-使用ResourceDictionary管理Logical Resources

    “Logical resources may be of various types, such as brushes, geometries, styles, and templates.Placi ...

  8. winform设置文本框宽度 根据文字数量和字体返回宽度

    _LinkLabel.Width = TextRenderer.MeasureText(_LinkLabel.Text, _LinkLabel.Font).Width;

  9. 阿里聚安全受邀参加SFDC安全大会,分享互联网业务面临问题和安全创新实践

    现今,技术引领的商业变革已无缝渗透入我们的日常生活,「技术改变生活」的开发者们被推向了创新浪潮的顶端.国内知名的开发者技术社区 SegmentFault 至今已有四年多了,自技术问答开始,他们已经发展 ...

  10. [.net 面向对象程序设计进阶] (18) 多线程(Multithreading)(三) 利用多线程提高程序性能(下)

    [.net 面向对象程序设计进阶] (18) 多线程(Multithreading)(二) 利用多线程提高程序性能(下) 本节导读: 上节说了线程同步中使用线程锁和线程通知的方式来处理资源共享问题,这 ...