一、作用域与this
ES5只有全局作用域和函数作用域,没有块级作用域
作用域分为静态/词法作用域和动态作用域
0. 声明提升和暂时性死区
(1).声明提升(var)
1 | console.log(a); // undefined |
(2).暂时性死区(let)
1 | console.log(a) // Uncaught ReferenceError: a is not defined |
注意:
在es5 strict mode,赋值给未声明的变量将报错。
1.静态作用域
静态作用域指的是一段代码,在它执行之前就已经确定了它的作用域,简单来说就是在执行之前就确定了它可以应用哪些地方的作用域(变量)
首先let和const声明的全局变量不再属于window
变量的作用域,除了this以外,全部遵循词法作用域的原则。
即JS引擎总会从最近的一个域,向外层域查找;
二、动态作用域
动态作用域–函数的作用域是在函数调用的时候才决定的。
在 JavaScript 中的仅存的应用动态作用域的地方:this 引用。
动态作用域,作用域是基于调用栈的,而不是代码中的作用域嵌套;
作用域嵌套:有词法作用域一样的特性,查找变量时,总是寻找最近的作用域;
三、声明函数的方法
(1).函数声明
function a (a,b,c) { return a+b+c;}
(2).函数表达式
var a = function (a,b,c) { return a+b+c;}
(3).Function构造函数
语法: new Function(参数1, 参数2, 参数3, 方法体)
var a = new Function(‘a’,’b’,’c’,’return a+b+c’)
四、变量进入作用域的方法
1.Language-defined:所有的作用域默认都会给出 this 和 arguments 两个变量名(global没有arguments
2.Formal parameters(函数形参):函数有形参,形参会添加到函数的作用域中;
3.Function declarations(函数声明):如 function foo() {}
4.Variable declarations(变量声明):如 var foo,包括_函数表达式_
除了上下文顺序声明之外,若声明提升符合。
函数声明和变量声明总是会被移动到它们所在的作用域的顶部。
而变量的解析顺序(优先级),与变量进入作用域的4种方式的顺序一致。
1 | function testOrder(arg) { |
五、this
每个作用域都会有this。
在全局上下文(任何函数以外),this指向全局对象。
1 | console.log(this === window); // true |
this在具体位置的具体指向:
- 在函数内部时,this由函数怎么调用来确定。
简单调用,即独立函数调用。由于this没有通过call来指定,且this必须指向对象,那么默认就指向全局对象。 - 严格模式下,this保持进入execution context时被设置的值。如果没有设置,那么默认是undefined。它可以被设置为任意值(包括null/undefined/1等等基础值,不会被转换成对象)。
- 在箭头函数中,this由词法/静态作用域设置(set lexically)。它被设置为包含它的execution context的this,并且不再被调用方式影响(call/apply/bind)。
- 当函数作为对象方法调用时,this指向该对象。
- 原型链上的方法根对象方法一样,作为对象方法调用时this指向该对象。
- 在构造函数(函数用new调用)中,this指向要被constructed的新对象。
- Function.prototype上的call和apply可以指定函数运行时的this。
- ES5引进了Function.prototype.bind。f.bind(someObject)会创建新的函数(函数体和作用域与原函数一致),但this被永久绑定到someObject,不论你怎么调用。
它说创建新函数,可不是覆盖原函数,!!!! - 闭包中的this(挺绕的,但无论下次看到理不理解,都要记住闭包套的闭包有可能返回window,需要分析有没有自执行函数)
为什么下面这段代码的结果是”The Window”?object.getNameFunc是对象调用的方法,里面的this是指向object,但object里面return的闭包,有自己的this和arguments。1
2
3
4
5
6
7
8
9
10var name = "The Window";
var object = {
name: "My object",
getNameFunc: function() {
return function() {
return this.name;
};
}
}
object.getNameFunc()() // The Window
在《Javascript高级程序设计》中有写为什么匿名函数没有取得其包含作用域(外部作用域)的this对象呢?
每个函数被调用时,其活动对象都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。
在作用域和闭包说过,闭包的情况有函数作为返回值及函数作为参数被传递。同样的,还有像操作dom函数的闭包。
1 | function clickEvent () { |
再深层一些,比如clickEvent这个函数返回的也是闭包,那么无论多少层,只要执行了,都是window。
1 | function clickEvent (fun) { |
注意:当用call和apply而传进去作为this的不是对象时,将会调用内置的ToObject操作转换成对象。所以4将会装换成new Number(4),而null/undefined由于无法转换成对象,全局对象将作为this。
六、JavaScript采用Lexical Scope。(静态范围作用域)
于是,我们仅仅通过查看代码(因为JavaScript采用静态范围作用域),就可以确定各个变量到底指代哪个值。
另外,变量的查找是从里往外的,直到最顶层(全局作用域),并且一旦找到,即停止向上查找。所以内层的变量可以覆盖外层的同名变量。
七、Function vs. Block Scope
上面的内容有意无意似乎应该表明了,JS没有Block Scope。
除了Global Scope,只有function可以创建新作用域(Function Scope)。 不过这已经是老黄历了,ES6引入了Block Scope。
另外,with和try catch都可以创建Block Scope。