什么是作用域?
简直所有编程语言最根本的性能之一,就是可能贮存变量当中的值,并且能在之后对这个 值进行拜访或批改。事实上,正是这种贮存和拜访变量的值的能力将状态带给了程序。
若没有了状态这个概念,程序尽管也可能执行一些简略的工作,但它会受到高度限制,做 不到十分乏味。
然而将变量引入程序会引起几个很有意思的问题,这些变量住在 哪里?换句话说,它们贮存在哪里?最重要的是,程序须要时如何找到它们?
这些问题阐明须要一套设计良好的规定来存储变量,并且之后能够不便地找到这些变量。 这套规定被称为作用域。
简而言之,作用域是依据名称查找变量的一套规定。
(以上摘录自《你不晓得的JS》)
什么是this?
JavaScript有两种作用域:词法(动态)作用域和动静作用域(即this)。
词法作用域即函数和变量的作用域在定义的时候就确定了(即申明的地位)。如:
var value = 1;function foo() { console.log(value);}function bar() { var value = 2; foo();}bar();// 后果是 ???
答案是1. foo定义在全局对象,所以作用域即window, foo外部没有找到value,就往作用域链下面找,即window,window对象有申明value,则打印1.
注:浏览器的全局对象默认是window
在看一个例子(摘取《你不晓得的JS》):
function identify() {return this.name.toUpperCase(); } function speak() {var greeting = "Hello, I'm " + identify.call( this ); console.log( greeting ); } var me = {name: "Kyle" }; var you = { name: "Reader" }; identify.call( me ); // KYLE identify.call( you ); // READER speak.call( me ); // Hello, 我是 KYLE speak.call( you ); // Hello, 我是 READER
这段代码能够在不同的上下文对象(me 和 you)中重复使用函数 identify() 和 speak(), 不必针对每个对象编写不同版本的函数。
如果不应用 this,那就须要给 identify() 和 speak() 显式传入一个上下文对象(context)。
function identify(context) { return context.name.toUpperCase(); } function speak(context) { var greeting = "Hello, I'm " + identify( context ); console.log( greeting ); }var me = { name: "Kyle" }; var you = { name: "Reader" }; identify(you); // READER speak(me); //Hello, I'm KYLE
随着你的应用模式越来越简单,显式传递上下文对象会让代码变得越来越凌乱,应用 this 提供了一种更优雅的形式来隐式“传递”一个对象援用。也就是动静作用域。this
指的是函数运行时所在的环境(上下文对象)。
理解了作用域和this,接下来进入本文的主题:
如何确定函数的运行环境?
var obj = { foo: function () { console.log(this.bar) }, bar: 1};var foo = obj.foo;var bar = 2;obj.foo() // 1 foo() // 2
同样都是foo函数,为什么后果不一样呢?这种差别的起因,就在于函数体外部应用了this
关键字。很多教科书会通知你,this
指的是函数运行时所在的环境。对于obj.foo()
来说,foo
运行在obj
环境,所以this
指向obj
;对于foo()
来说,foo
运行在全局环境,所以this
指向全局环境。所以,两者的运行后果不一样。这种解释没错,然而教科书往往不通知你,为什么会这样?也就是说,函数的运行环境到底是怎么决定的?举例来说,为什么
obj.foo()
就是在obj
环境执行,而一旦var foo = obj.foo
,foo()
就变成在全局环境执行?
这篇文章从内存的数据结构以及函数如何存储办法进行了解释。
上面简要总结下:
JavaScript 中函数也是对象。下面代码中foo函数有一块独立的内存来保留函数,obj.foo保留的是这块内存的地址。因为函数是一个独自的值,所以它能够在不同的环境(上下文)执行(即被任意对象调用)。
比方obj.foo() foo是在obj外部的指向函数的属性,并通过 obj.foo 间接援用了函数,所以函数调用地位上下文对象是obj,所以,this 指代的是 foo函数的调用者obj,打印1
foo() 是间接应用不带任何润饰的函数援用进行调用, 相当于独立调用,而非由其余对象所调用,这种状况在非严格环境下,this会默认绑定到全局对象window, 所以打印2
注:严格环境下会是undefined
上面再来看几个例子:
例一:
通过上述解释,funs通过obj.funs()调用,所以funs内this是obj,尽管fun2定义在funs外部,然而下面说过:this
指的是函数运行时
所在的环境(上下文对象)。 和定义在哪里无关。funs() 通过函数援用间接调用,所以会应用默认绑定到window
上述例子咱们晓得了this绑定的两种状况:
- 隐式绑定:由指定对象间接调用函数,绑定到指定对象
- 默认绑定:非严格模式下window,严格模式下undefined
那么如何让例一中fun2()的this也绑定到obj呢?
革新下:
let obj = { funs() { let fun2 = function() { console.log(this) } //obj fun2.call(this) }}obj.funs()
通过call, apply, bind等办法能够显示指定this绑定到哪个对象。这便是this的显示绑定。
new
当咱们应用new来调用函数的时候,此时函数被当作构造函数来解决,具体new的实现能够参考这篇文章,构造函数内的this绑定到新生成的对象。
function foo(a) { this.a = a; } var bar = new foo(2); console.log( bar.a ); // 2
this绑定的四种规定
至此,上述内容能够总结出四种规定来确定this的值:
- 函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。 var bar = new foo()
- 函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是 指定的对象。 var bar = foo.call(obj2)
- 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上 下文对象。 var bar = obj1.foo()
- 如果都不是的话,应用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到 全局对象。 var bar = foo()
以上出自《你不晓得的JS》
绑定例外
在某些场景下 this 的绑定行为会出其不意,你认为该当利用其余绑定规定时,实际上利用 的可能是默认绑定规定。
function foo() { console.log( this.a ); } var a = 2; foo.call( null ); // 2
如果你把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值 在调用时会被疏忽,理论利用的是默认绑定规定。
var value = 1;var foo = { value: 2, bar: function () { return this.value; }}//示例1console.log(foo.bar()); // 2//示例2console.log((foo.bar)()); // 2//示例3console.log((foo.bar = foo.bar)()); // 1//示例4console.log((false || foo.bar)()); // 1//示例5console.log((foo.bar, foo.bar)()); // 1
另一个须要留神的是,你有可能(无意或者无心地)创立一个函数的“间接援用”,在这 种状况下,调用这个函数会利用默认绑定规定。
如示例3 赋值表达式 foo.bar = foo.bar 的返回值是指标函数的援用,函数理论是间接应用不带任何润饰的(匿名)函数援用进行调用, 相似与通过 foo() 调用(注:这里若调用foo()会报not function谬误哦,因为全局未声明) 而不是 foo.bar()调用 。依据咱们之前说过的,这里会利用默认绑定。示例4,5 别离应用 逻辑运算和逗号运算符返回了指标函数的援用,因而后果同示例3。
留神:对于默认绑定来说,决定 this 绑定对象的并不是调用地位是否处于严格模式,而是 函数体是否处于严格模式。如果函数体处于严格模式,this 会被绑定到 undefined,否则 this 会被绑定到全局对象。
参考起源
书籍《你不晓得的JavaScript》
JavaScript 的 this 原理 -- 阮一峰
JavaScript深刻之从ECMAScript标准解读this -- 冴羽
JavaScript深刻之new的模仿实现 -- 冴羽