共计 3774 个字符,预计需要花费 10 分钟才能阅读完成。
什么是作用域?
简直所有编程语言最根本的性能之一,就是可能贮存变量当中的值,并且能在之后对这个 值进行拜访或批改。事实上,正是这种贮存和拜访变量的值的能力将状态带给了程序。
若没有了状态这个概念,程序尽管也可能执行一些简略的工作,但它会受到高度限制,做 不到十分乏味。
然而将变量引入程序会引起几个很有意思的问题,这些变量住在 哪里?换句话说,它们贮存在哪里?最重要的是,程序须要时如何找到它们?
这些问题阐明须要一套设计良好的规定来存储变量,并且之后能够不便地找到这些变量。这套规定被称为作用域。
简而言之,作用域是依据名称查找变量的一套规定。
(以上摘录自《你不晓得的 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;}
}
// 示例 1
console.log(foo.bar()); // 2
// 示例 2
console.log((foo.bar)()); // 2
// 示例 3
console.log((foo.bar = foo.bar)()); // 1
// 示例 4
console.log((false || foo.bar)()); // 1
// 示例 5
console.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 的模仿实现 — 冴羽