首先,依然回顾《js基础梳理-究竟什么是执行上下文栈(执行栈),执行上下文(可执行代码)?》中的html
3.执行上下文的生命周期
3.1 建立阶段
- 生成变量对象(Variable object, VO)
- 创建做用域链(Scope chain)
- 肯定this指向
3.2 执行阶段
- 变量赋值
- 函数引用
- 执行其余代码
咱们已经梳理了在执行上下文中的变量对象是如何生成的以及做用域链是如何创建的。本篇文章就继续梳理下,执行上下文的this指向是如何肯定的。前端
首先,执行上下文分全局执行上下文和函数执行上下文,在浏览器环境的全局执行上下文中,this指向全局对象,也就是window(接下来本篇文章都仅只浏览器环境)。这也相对简单,没有那么多复杂的状况须要考虑。java
而在函数执行上下文中,肯定this指向发生在函数执行上下文的建立阶段,而函数执行上下文又是在函数被调用后才产生的。所以,不难理解:this的指向,是在函数被调用的时候肯定的。而不是函数声明的时候肯定的。而肯定this的指向难就难在函数被调用的方式是多种多样的,因此咱们就须要从函数执行的各类方式分别去分析this的指向。数组
// 1.1 函数体在非严格模式下的全局函数执行 function fn () { console.log(this) } fn1() // => window
// 1.2 函数体在严格模式下的全局函数执行 'use strict' function fn () { console.log(this) } fn() // => undefined
// 1.3 函数体在非严格模式下的函数中的函数执行 function fn1 () { function fn2 () { console.log(this) } fn2() } fn1() // => window
// 1.4 函数体在严格模式下的函数中的函数执行 'use strict' function fn1 () { function fn2 () { console.log(this) } fn2() } fn1() // => undefined
// 1.5 函数体在非严格模式下,而函数调用在严格模式下时, this依然指向window function fn () { console.log(this) } (function () { 'use strict' fn() // => window })()
// 2.1.1函数直接在对象中声明 var obj = { a: 1, test: function () { console.log(this.a) } } obj.test(); // => 1
// 2.1.2 函数先声明,再由对象引用 function test () { console.log(this.a) } var obj = { a: 1, test: test } obj.test(); // => 1
// 2.2.1 多层对象引用,this指向离函数调用最近的对象 function test () { console.log(this.a) } var obj2 = { a: 2, test: test } var obj1 = { a: 1, obj2: obj2 } obj1.obj2.test() // => 2
// 2.3.1 将obj.foo看成函数别名赋值给一个变量 function foo () { console.log(this.a) } var obj = { a: 2, foo: foo } var bar = obj.foo // 函数别名 var a = '全局属性' bar() // => 全局属性
在2.3.1中,虽然bar是obj.foo的一个引用,可是实际上,它引用的是foo函数自己,所以此时的bar() 实际上是一个不带任何修饰的普通函数调用。所以也使用默认绑定规则。浏览器
// 2.3.2 将obj.foo看成bar的回调函数。 function foo () { console.log(this.a) } function bar (fn) { fn() } var obj = { a: 2, foo: foo } var a = '全局属性' bar(obj.foo) // => 全局属性
你们都知道全部的函数的参数都是按值传递的,(都是栈内数据的拷贝)。 基本类型传的是值自己(由于直接把值存在栈内),引用类型传的是对象在内存里面的地址(由于复杂对象在堆内,因此在栈里存对象所在的堆地址)。 所以 bar(obj.foo) 执行时,参数fn实际上引用的是foo。而foo函数执行其实就是一个不带任何修饰的普通函数调用。因此它也使用默认绑定规则。闭包
由此可扩展到 setInterval, setTimeout,以及匿名函数中的this也是使用的默认绑定规则。即非严格模式下,this指向window,严格模式下,this指向undefined。app
显示绑定规则:this指向第一个参数。函数
// 3.1.1 var xw = { name : "小王", gender : "男", age : 24, say : function(school,grade) { console.log(this.name + " , " + this.gender + " ,今年" + this.age + " ,在" + school + "上" + grade); } } var xh = { name : "小红", gender : "女", age : 12 } xw.say.call(xh, "实验小学", "六年级") // => 小红 , 女 ,今年12 ,在实验小学上六年级
在3.1.1代码示例中,当调用say时强制把它的this绑定到了xh上。post
// 3.2.1 var xw = { name : "小王", gender : "男", age : 24, say : function(school,grade) { console.log(this.name + " , " + this.gender + " ,今年" + this.age + " ,在" + school + "上" + grade); } } var xh = { name : "小红", gender : "女", age : 12 } xw.say.apply(xh,["实验小学","六年级"]) // => 小红 , 女 ,今年12 ,在实验小学上六年级
// 3.3.1 var xw = { name : "小王", gender : "男", age : 24, say : function(school,grade) { alert(this.name + " , " + this.gender + " ,今年" + this.age + " ,在" + school + "上" + grade); } } var xh = { name : "小红", gender : "女", age : 12 } xw.say.bind(xh)("实验小学","六年级") // => 小红 , 女 ,今年12 ,在实验小学上六年级
经过以上这些例子,其实也能够明显的看到call,apply,bind的区别。this
以前说到隐式丢失的问题,而显示绑定的一个变种能够解决隐式丢失的问题,这种方式被称之为硬绑定。
// 2.3.2 将obj.foo看成bar的回调函数。 function foo () { console.log(this.a) } function bar (fn) { fn() } var obj = { a: 2, foo: foo } var a = '全局属性' bar(obj.foo) // => 全局属性
将其修改为
// 3.4.1 利用call方法解决隐式丢失的问题 function foo(){ console.log(this.a); } function bar(fn){ fn.call(obj); } var obj = { a:2, foo:foo } var a = "全局属性"; bar(obj.foo); // => 2
这里依旧是建立了 bar()这个函数,可是在其内部手动调用了obj.foo.call(obj),把foo强制绑定到了obj对象,以后不管如何调用bar(),它总会手动在obj上调用foo.
再看看隐式丢失的代码示例2.3.1
// 2.3.1 将obj.foo看成函数别名赋值给一个变量 function foo () { console.log(this.a) } var obj = { a: 2, foo: foo } var bar = obj.foo // 函数别名 var a = '全局属性' bar() // => 全局属性
将其修改为
function foo(){ console.log(this.a); } var obj = { a:2, foo:foo }; var bar = obj.foo.bind(obj); var a = "global"; bar(); // => 2
总结下“显示绑定三人组”
共同点:
一、都用于控制this指向;
二、第一个参数都是this须要指向的对象,也就是上下文;
三、均可之后续参数传递;
四、没有任何参数时或者第一个参数是null时,this都指向全局对象window >
区别:
一、call后面的参数与say方法中的参数是一一对应的,而apply的第二个参数是一个数组,数组中的元素是和say方法中一一对应的。因此当传入的参数数目不肯定时,多使用apply。
二、call、apply绑定后马上执行,bind是延迟执行。换言之,当你但愿改变上下文环境以后并不是当即执行,而是回调执行的时候,就使用bind()方法吧
扩散性思考:
call,apply,bind还有什么实际使用场景?它们的原理是什么?如何本身模拟实现这三个方法?
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操做:
在这里主要关心第1,3,4步。
function Foo(a) { this.a = a; } var bar = new Foo(2); console.log(bar.a) // 2
使用new来调用Foo(...)时,会构造一个新对象并把它绑定到foo(...)调用中的this上,new是最后一种能够影响到函数调用时this绑定行为的方法,称之为new绑定。
// 5.1 箭头函数外部是普通函数调用时 function fn () { return () => { return () => { console.log(this) } } } fn()()() // => window
在这个例子中,由于包裹箭头函数的第一个普通函数是 fn,而fn的this指向window, 因此箭头函数中的 this也是 window。
以上的例子,多数为单个规则。 这里推荐一种方法:
在写这篇文章的时候,原本标题是关于this指向的全面分析,后来想一想,其实也并不全面。真正的宗师级别,会是多个规则夹在一块儿去分析this,那样的话,篇幅确定太长了,并且一时半会其实也记不了那么多。并且就这篇文章所讲的,其实也够你们去理解分析大部分基础的this指向问题了。有兴趣的话,能够本身去翻阅一下《你不知道javascritp·上卷》这本书,里面还有一些我我的以为比较少见而没有列出来的this指向问题,好比个人文章中提到了硬绑定,那其实也会有软绑定。
在这里也贴一篇蚂蚁金服前端的博文,是一个多个规则综合应用判断this指向的题。不少时候,当咱们觉得本身懂了,而老是会有人更深刻的去挖掘咱们以前没有想到的知识点,学无止境。
原文出处:https://www.cnblogs.com/hezhi/p/10127575.html