全局作用域
打开一个 js 文件,写了一行代码,这行代码所在的位置就会是全局作用域(global scope)。比如:
var name = '狗狗'
局部作用域
全局作用域只有一个,在全局使用域里面定义的其它的作用域都被称为局部作用域(local scope)。局部作用域是由函数创建的,每个函数都会创建一个局部作用域。
下面我创建了一个名字是 greet 的函数,在它里面声明了一个 name 变量,这个变量是在局部作用域之内。
// 作用域 A: 全局作用域 var greet = function() { // 作用域 B:局部作用域 // 这里是由 greet 这个函数创建的局部作用域 var name = '猫咪' }
在这个局部作用域里面定义的东西,在这个作用域的外面是访问不到的。试一下:
var greet = function() {
var name = '猫咪'
}
console.log(name
)
// 返回:undefined
在 greet 函数里声明的 name 变量,在外面不能访问到,因为这个 name 是在 greet 函数创建的局部作用域之内。
词法作用域
词法作用域(Lexical Scope)。如果你在一个函数的内部又创建了一个函数,这个内部函数可以访问到外部函数的作用域。这就是词法作用域。
看个例子:
// 作用域 A var greet = function() { // 作用域 B:在这里定义了局部变量 name var name = "小刺猬" var logger = function() { // 作用域 C:在这里使用了在爸爸(greet)那里声明的变量 name console.log(name) } logger() } greet() // 输出 “小刺猬”
greet 函数创建了一个作用域,在 greet 里面定义的 logger 函数也会创建一个作用域,logger 函数是在 greet 内部定义的,它可以访问到在它之外的作用域里的东西。这里就是我们在 logger 函数里输出了在 greet 创建的作用域里定义的 name 这个变量的值。
下面这个例子,name 变量在 function3,function2,function1 都是可以访问到的:
var name = '猫咪' var function1 = function() { // 在这里可以访问到 name var function2 = function() { // 在这里也可以访问到 name var function3 = function() { // 在这里还可以访问到 name } } }
假设你在 function3 里使用了 name 变量,JavaScript 在它里面没找到 name,就会往上一层继续寻找有没有 name,还没找到就会继续往上一层寻找,真到找到为止,如果最终结果还是没找到 name,就会报 undefined 。
闭包
闭包(closure)。指的是一种函数,这种函数使用了在它周围作用域下定义的变量。
MDN 里的关于闭包的中文文档非常好的解释了什么是闭包。闭包是一种特别的对象,也可以说是一种特别的函数,这种对象有两个部分组成,一部分是函数本身,还有一部分是创建这个函数的时候的这个函数所在的那个环境。也就是闭包是函数 + 环境。
也就是函数在某种特别情况下被创建,就会形成闭包。这个特别情况就是,函数使用了在它自己的局部作用域以外定义的变量。
看个例子:
function robot() { var name = '小猫' function greet() { console.log(`我是${name},喵 ~`) } return greet } var kitty = robot() kitty()
robot 函数里定义了一个局部变量叫 name,在 robot 函数里又返回了一个函数,名字是 greet,在这个内部函数里使用了在 robot 局部作用域下定义的变量 name。这就形成了一个闭包。也就是 kitty 就是一个闭包,这个闭包是 greet 函数,还有 greet 函数引用的 name 所组成的。
再看一个例子:
function robot(name) { function greet(greeting) { console.log(`我是${name}, ${greeting},`) } return greet } var kitty = robot('小猫') kitty('喵 ~ ') // 输出:我是小猫, 喵 ~ var puppy = robot('小狗') puppy('汪汪 ~') // 输出:我是小狗,汪汪 ~
这回 robot 函数带一个 name 参数,robot 还会返回一个 greet 函数,返回的这个 greet 函数也带一个参数,就是 greeting,在这个 greet 函数里我们用到了 robot 的 name 参数,还有 greeting 本身的参数 greeting 。
我们基于 robot 又创建了两个函数,kitty,还有 puppy,这两个东西都是闭包 。这两个函数的主体部分都是一样的,不一样的地方是,在创建它们的时候的环境是不一样的。在创建 kitty 的时候,name 的值是 “小猫”,在创建 puppy 的时候,name 的值是 “小狗”。
this
每个作用域里面都绑定了一个特别的值叫 this ,具体 this 表示的是什么要看函数是怎么被执行的。
this 翻译成中文就是“这...”,它本身并没有特殊的意义,它的意义完全取决于你在什么情景下使用它。比如你在跟朋友讨论一部电影:《Brother》,“这部电影是北野武的一部作用”,在这句话里,这(this),指的就是《Brother》这部电影。如果是在另一种语境下,这个 this 表示的可能是完全不同的意思。
在 JavaScript 里面,this 表示的东西也是要看所处的语境(context),也可以说上下文,情境,环境 ...
情形一
在全局作用域的下面,this 一般表示的是 window 对象。打开浏览器的控制台,输入:
this
会返回 window 对象里的东西。
Window {speechSynthesis: SpeechSynthesis, caches: CacheStorage, localStorage: Storage, sessionStorage: Storage, webkitStorageInfo: DeprecatedStorageInfo…}
在全局作用域下定义一个变量,你可以在这个 window 对象里得到。试一下:
var name = '小猫' this.name // '小猫' window.name // '小猫'
情形二
再试一下:
var robot = function() { console.log(this) } robot()
在定义的函数里面输出 this ,同样会得到一个 window 对象。但是如果代码使用了 'use strict' ,像这样:
'use strict'; var robot = function() { console.log(this) } robot()
返回的会是:undefined
再试一下:
var robot = { greet(){ console.log(this) } } robot.greet() // this 表示的是 greet 方法所属的 robot 对象
robot 是个对象,里面有个 greet 方法,在这个方法的里面,this 表示的是方法所属的对象,这里 this 表示的就是 robot 这个对象。
情形三
假设页面上有个元素:
<button id="signup">订阅</button>
我的代码像这样:
var signupButton = document.querySelector('#signup') var signupLog = function() { console.log(this) } signupButton.addEventListener('click', signupLog, false)
点击了页面上的 “订阅” 按钮,会在控制台上输出在 click 事件处理器,也就是 signupLog 函数里的 this 所表示的东西,这里 this 表示的是发生 click 事件的那个 button 元素。
情形四
这次 this 出问题了:
var signupButton = document.querySelector('#signup') var signupLog = function() { console.dir(this) // this 是发生 click 事件的元素 setTimeout(function () { console.log(this) // this 表示的是 window 对象 }, 1000); } signupButton.addEventListener('click', signupLog, false)
这次在 signupLog 里的 setTimeout 里的 this 指的是 window 对象。想让这里的 this 表示发生 click 事件的元素,可以这样:
var signupButton = document.querySelector('#signup') var signupLog = function() { var _this = this console.dir(_this) setTimeout(function () { console.log(_this); }, 1000); } signupButton.addEventListener('click', signupLog, false)
改变 this 的值
call(),apply(),bind() ,这些方法都可以改变 this 表示的值。
var robot = function() { console.log(this) } robot.call('hello') // this 的值是 hello
call() 与 apply() 的用法:
.call(thisArg, arg1, arg2, arg3) .apply(thisArg, [arg1, arg2])
call() 与 apply() 会执行我们的函数。
参考资料
- https://developer.mozilla.org/en/docs/Web/JavaScript/Closures
- https://toddmotto.com/everything-you-wanted-to-know-about-javascript-scope/
评论
所以 setTimeout 函数里面的 this 出输出 window,是因为 setTimeout 这个函数本身就是 window 对象的方法吗?
7 年 8 个月 以前
我再求证一下。
7 年 8 个月 以前
我感觉如果理解了作用域链的话,其实理解闭包就很简单了。闭包好像就是为了要“修复”作用域链断开的情况,又有点像 Promise 的连写 then,到最后的 catch
7 年 8 个月 以前
window.setTimeout()
7 年 8 个月 以前
皓哥用的什么代码高亮插件?
7 年 8 个月 以前
用的 highlight.js,atom 主题。
7 年 8 个月 以前