函数的四种调用方式:
-
独立调用=默认绑定
-
方法调用=隐式绑定
-
间接调用=显示绑定
-
构造函数调用=new绑定
一、默认绑定
-
1、全局环境下,this默认绑定到window
console.lg(this === window) // true
-
函数独立调用时,this默认绑定到window
function fn() { console.log(this === window) // true}fn()
-
2、被嵌套的函数独立调用时,this默认绑定到window
var a = 0;var obj = { a:2, foo:function(){ function test() { console.log(this.a) }; test() }}obj.foo() // 0// 上面代码虽然test()函数被嵌套在obj.foo()函数中,但test()函数独立调用,而不是方法调用。所以this绑定到window
-
3、IIFE立即执行函数
// IIFE立即执行函数实际是函数声明后立即调用执行,内部的this指向了windowvar a = 0;function foo() { (function test() { console.log(this.a) })()};var obj = { a:2, foo:foo}obj.foo() // 0
等价于上例:
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(){ var that = this; function test() { console.log(this); console.log(that.a); } return test;};var obj = { a:2, foo:foo};obj.foo()() // window 2
二、隐式绑定
一般地,被直接对象所包含的函数调用,也成为方法的调用,this隐式的绑定到该直接对象。
function foo() { console.log(this.a)};var obj1 = { a:1, foo:foo, obj2:{ a:2, foo:foo }}// foo()函数的直接对象是obj1,this隐式绑定到obj1obj1.foo();// 1// foo()函数的直接对象是obj2,this隐式绑定到obj2obj1.obj2.foo(); // 2
三、隐式丢失
隐式丢失,是指被隐式绑定的函数丢失绑定对象,从而默认绑定到window。这种情况容易出错又常见。
-
1、函数别名
var a = 0;function foo(){ console.log(this.a)};var obj1 = { a:1, foo:foo};// 把obj.foo赋予别名bar,造成隐式丢失,因为只是把foo()函数赋给了bar,而bar与obj对象毫无关系。var bar = obj1.foobar() // 0
等价于:
var a = 0;var bar = function foo () { console.log(this.a)};bar() // 0
-
2、参数传递
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); //0
等价于:
var a = 0;function bar(fn){ fn()}bar(function foo() { console.log(this.a)})
-
3、内置函数
var a = 0;function foo(){ console.log(this.a)}var obj = { a:2, foo:foo}setTimeout(obj.foo,1000); // 0
-
4、间接调用
// 函数的“间接引用”一般都在无意间创建,最容易在赋值时发生,会造成隐式丢失function foo() { console.log(this.a)}var a = 2;var o = {a:3,foo:foo}var p = {a:4}o.foo(); // 3//将o.foo函数赋值给p.foo函数,然后立即执行。相当于仅仅是foo()函数的立即调用(p.foo = o.foo)();
// 另一种情况function foo() { console.log(this.a)}var a =2;var = {a:3,foo:foo}var p = {a:4}o.foo(); // 3p.foo = o.foo;//将o.foo函数赋值给p.foo函数,之后p.foo函数再执行,是属于p对象的foo函数的执行p.foo();// 4
-
5、其他情况
// 在JavaScript引擎内部,obj和obj.foo储存在两个内存地址,简称为M1和M2。只有obj.foo()这样调用时,是从M1用M2,因此this指向obj。但是下列三种情况,都是直接取出M2进行运算,然后在全局环境下指向运算结果,因此this指向全局环境。var a = 0;var obj = { a:2, foo:foo}function foo(){ console.log(this.a);}(obj.foo = obj.foo)();//0(false || obj.foo)();//0(1,obj.foo)();//0
四、显示绑定
-
1、通过call,apply,bind方法把对象绑定this上,叫做显示绑定。对于被调用的函数来说,叫做间接调用。
var a = 0;function foo(){ console.log(this.a)}var obj = { a:2}foo()// 0foo.call(obj); // 2
-
2、内置函数
// JavaScript中新增了许多内置函数,具有显示绑定的功能。如数组的5个迭代方法:map,forEach,filter,some,everyvar id = 'window';function foo(el) { console.log(el,this.id)}var obj = { id:'fn'}let arr =[1,2,3]arr.forEach(foo); // 1 "window" 2 "window" 3 "window"arr.forEach(foo,obj) // 1 "fn" 2 "fn" 3 "fn"
五、new绑定
如果函数或者方法调用之前带有关键字new,它就构成构造函数调用。对于this绑定来说,称为new绑定。
// 1、构造函数通常不适用return关键字,他们通常初始化新对象,他们通常初始化新对象,当构造函数的函数执行完毕时,它会显示返回。在这种情况下,构造函数用表达式的计算结果就是新对象的值。function fn() { console.log(this) // fn this.a = 2 }var test = new fn()console.log(test) // {a:2}
// 2、如果构造函数使用return语句,但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果function fn(){ console.log(this) // fn this.a = 2; return;}var test = new fn();console.log(test);//fn {a:2}
// 3、使用构造函数显示地使用return语句返回一个对象,那么调用表达式的值就是这个对象。var obj = {a:1};function fn(){ console.log(this) // fn this.a = 2; return obj;}var test = new fn();console.log(test);//{a:1}
六、严格模式
-
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);//redvar color = 'red';function displayColor(){ 'use strict'; console.log(this.color);}displayColor.call(null);//TypeError: Cannot read property 'color' of null