function func() {if (true) {var tmp = 123;}console.log(tmp); // 123}之所以能够访问,是因为var关键字声明的变量有一个变量提升的过程。而在ES6场景,推荐使用let关键字定义变量:function func() {if (true) {let tmp = 123;}console.log(tmp); // ReferenceError: tmp is not defined}这种方式,能够避免很多错误。var obj = {foo: function(){alert(this === obj); }};obj.foo(); // true这个准则也适用于当调用函数时使用new操作符来创建对象的实例的情况下。在这种情况下,在函数的作用域内部this的值被设置为新创建的实例:function foo(){alert(this);}new foo() // foofoo() // window当调用一个为绑定函数时,this默认情况下是全局上下文,在浏览器中它指向window对象。需要注意的是,ES5引入了严格模式的概念, 如果启用了严格模式,此时上下文默认为undefined。var color = "blue";function changeColor(){var anotherColor = "red";function swapColors(){var tempColor = anotherColor;anotherColor = color;color = tempColor;// 这里可以访问color, anotherColor, 和 tempColor}// 这里可以访问color 和 anotherColor,但是不能访问 tempColorswapColors();}changeColor();// 这里只能访问colorconsole.log("Color is now " + color);上述代码一共包括三个执行环境:全局环境、changeColor()的局部环境、swapColors()的局部环境。 上述程序的作用域链如下图所示:
从上图发现。内部环境可以通过作用域链访问所有的外部环境,但是外部环境不能访问内部环境中的任何变量和函数。 这些环境之间的联系是线性的、有次序的。
对于标识符解析(变量名或函数名搜索)是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始, 然后逐级地向后(全局执行环境)回溯,直到找到标识符为止。
闭包
闭包是指有权访问另一函数作用域中的变量的函数。换句话说,在函数内定义一个嵌套的函数时,就构成了一个闭包, 它允许嵌套函数访问外层函数的变量。通过返回嵌套函数,允许你维护对外部函数中局部变量、参数、和内函数声明的访问。 这种封装允许你在外部作用域中隐藏和保护执行环境,并且暴露公共接口,进而通过公共接口执行进一步的操作。可以看个简单的例子:
function foo(){var localVariable = "private variable";return function bar(){return localVariable;}}var getLocalVariable = foo();getLocalVariable() // private variable模块模式最流行的闭包类型之一,它允许你模拟公共的、私有的、和特权成员:var Module = (function(){var privateProperty = "foo";function privateMethod(args){// do something}return {publicProperty: "",publicMethod: function(args){// do something},privilegedMethod: function(args){return privateMethod(args);}};})();模块类似于一个单例对象。由于在上面的代码中我们利用了(function() { ... })();的匿名函数形式,因此当编译器解析它的时候会立即执行。 在闭包的执行上下文的外部唯一可以访问的对象是位于返回对象中的公共方法和属性。然而,因为执行上下文被保存的缘故, 所有的私有属性和方法将一直存在于应用的整个生命周期,这意味着我们只有通过公共方法才可以与它们交互。(function(window){ var foo, bar;function private(){// do something}window.Module = {public: function(){// do something }};})(this);对于保护全局命名空间免受变量污染而言,这种表达式非常有用,它通过构建函数作用域的形式将变量与全局命名空间隔离, 并通过闭包的形式让它们存在于整个运行时(runtime)。在很多的应用和框架中,这种封装源代码的方式用处非常的流行, 通常都是通过暴露一个单一的全局接口的方式与外部进行交互。var o = {};function f(a, b) {return a + b;}// 将函数f作为o的方法,实际上就是重新设置函数f的上下文f.call(o, 1, 2); // 3f.apply(o, [1, 2]); // 3两个结果是相同的,函数f在对象o的上下文中被调用,并提供了两个相同的参数1和2。if(!("bind" in Function.prototype)){Function.prototype.bind = function(){var fn = this, context = arguments[0], args = Array.prototype.slice.call(arguments, 1);return function(){return fn.apply(context, args.concat(arguments));}}}bind()方法通常被用在上下文丢失的场景下,例如面向对象和事件处理。之所以要这么做, 是因为节点的addEventListener方法总是为事件处理器所绑定的节点的上下文中执行回调函数, 这就是它应该表现的那样。但是,如果你想要使用高级的面向对象技术,或需要你的回调函数成为某个方法的实例, 你将需要手动调整上下文。这就是bind方法所带来的便利之处:function MyClass(){this.element = document.createElement("div");this.element.addEventListener("click", this.onClick.bind(this), false);}MyClass.prototype.onClick = function(e){// do something};回顾上面bind方法的源代码,你可能会注意到有两次调用涉及到了Array的slice方法:Array.prototype.slice.call(arguments, 1);[].slice.call(arguments);我们知道,arguments对象并不是一个真正的数组,而是一个类数组对象,虽然具有length属性,并且值也能够被索引, 但是它们不支持原生的数组方法,例如slice和push。但是,由于它们具有和数组类似的行为,数组的方法能够被调用和劫持, 因此我们可以通过类似于上面代码的方式达到这个目的,其核心是利用call方法。
MyClass.prototype.init = function(){// call the superclass init method in the context of the "MyClass" instanceMySuperClass.prototype.init.apply(this, arguments);}也就是利用call或apply在子类(MyClass)的实例中调用超类(MySuperClass)的方法。var obj = {// ...addAll: function (pieces) {var self = this;_.each(pieces, function (piece) {self.add(piece);});},// ...}在上面的例子中,最直接的想法是直接使用this.add(piece),但不幸的是,在JavaScript中你不能这么做, 因为each的回调函数并未从外层继承this值。在该回调函数中,this的值为window或undefined, 因此,我们使用临时变量self来将外部的this值导入内部。我们还有两种方法解决这个问题:var obj = {// ...addAll: function (pieces) {_.each(pieces, function (piece) {this.add(piece);}.bind(this));},// ...}使用ES6中的箭头函数var obj = {// ...addAll: function (pieces) {_.each(pieces, piece => this.add(piece));},// ...}在ES6版本中,addAll方法从它的调用者处获得了this值,内部函数是一个箭头函数,所以它集成了外部作用域的this值。function hello(a, callback) {callback(a);}hello("weiwei", function(a) {console.log(this === global); // trueconsole.log(a); // weiwei});小结