原文地址:https://my.oschina.net/bgmemo/blog/195761

摘要: 这篇文章旨在帮助那些在听说过诸如域(scope),闭包(closure),关键字this,命名空间(namespace),函数域(function scope),全局域(global scope),词法作用域(lexical scope)以及公共域和私有域(public/private scope)等词汇后,想要进一步学习JavaScript的朋友。

对于一个JavaScript初学者(甚至是有经验的JavaScript开发者)而言,JavaScript语言中关于“域”(scope)的一些概念并不是那么直白或是容易理解的。

由此,这篇文章旨在帮助那些在听说过诸如域(scope),闭包(closure),关键字this,命名空间(namespace),函数域(function scope),全局域(global scope),词法作用域(lexical scope)以及公共域和私有域(public/private scope)等词汇后,想要进一步学习JavaScript的朋友。

希望这篇文章可以帮助你找到下列问题的答案:

  • 什么是域?

  • 什么是全局域、本地域?

  • 什么是命名空间以及其与域之间的不同?

  • 什么是关键字以及它是如何受域影响的?

  • 什么是功能域、词法作用域?

  • 什么是闭包?

  • 什么是公共域、私有域?

  • 如何将上述概念融会贯通?

什么是域?

在JavaScript里,域指的是代码当前的上下文语境。域可以是公共定义的,也可以是本地定义的。理解JavaScript中的域,是你写出无可挑剔的代码以及成为更好的程序员的关键。

什么是全局域?

在你开始写一行JavaScript代码的时候,你正处在我们所说的全局域中。此时我们定义一个变量,那它就被定义在全局域中:

// global scope
var name = 'Todd';

全局域是你最好的朋友,同时也是你最心悸的梦魇。学会控制各种域并不难,当你这么做之后,你就不会再遇到有关全局域的问题(多发生在与命名空间冲突时)。你或许经常听到有人说“全局域太糟糕了”,但却未听他们评判过个中缘由。其实,全局域并没有那么糟糕,因为你要在全局域当中创造可以被其他域所访问的模块和APIs,所以你必须学会扬长避短地使用它。

似乎大家都喜欢如此写jQuery代码,是不是你也这么干呢:

jQuery('.myClass');

。。。这样我们正在公共域中访问jQuery,我们可以把这种访问称之为命名空间。命名名空间在某些条件下可以理解为域,但通常它指的是最上层的域。在上面的例子里,jQuery作为命名空间存在公共域中。jQuery 命名空间在全局域中被定义,全局域就是jQuery库的命名空间,因为所有在命名空间中的东西都成为这个命名空间的派生。

什么是本地域?

本地域是指那些在全局域中定义的域。一般只能有一个全局域,定义其中的每一个函数都有自己的本地域。任何定义在其它函数里的函数都有一个连接那个外部函数的本地域。

假设我定义了一个函数,并在其中创建了几个变量,那这些变量就属于本地域。看下面的例子:

// Scope A: Global scope out here
var myFunction = function () {// Scope B: Local scope in here
};

任何属于本地域的物件对全局域都是不可见的-除非他们被暴露出来,也就是说,如果我在一个新的域中定义了一些函数和变量,它们是无法从当前那个域的外部被访问的。来看一个简单的例子:

var myFunction = function () {var name = 'Todd';console.log(name); // Todd
};
// Uncaught ReferenceError: name is not defined
console.log(name);

变量name是属于本地域的,它没有暴露给它的父域,因此它是未定义的。

函数域

在JavaScript中所有的域都是并且只能是被函数域(function scope)所创建,它们不能被for/while循环或者if/switch表达式创建。New function = new scope - 仅此而已。一个简单的例子来说明域的创建:

// Scope A
var myFunction = function () {// Scope Bvar myOtherFunction = function () {// Scope C};
};

创建新的域以及创建本地变量、函数、对象都是如此简单。

词法定义域

每当你看到一个函数在另一个函数里的时候,内部的那个函数可以访问外部的函数,这被称作词法定义域或是闭包 - 有时也被称作静态域。又来了,看下面这个例子:

// Scope A
var myFunction = function () {// Scope Bvar name = 'Todd'; // defined in Scope Bvar myOtherFunction = function () {// Scope C: `name` is accessible here!};
};

你会注意到 myOtherFunction 只是被简单的定义一下并没有被调用。调用顺序也会对域中变量该如何反应起到作用,这里我已经定义了一个函数然后在另一个Console下面调用了它:

var myFunction = function () {var name = 'Todd';var myOtherFunction = function () {console.log('My name is ' + name);};console.log(name);myOtherFunction(); // call function
};// Will then log out:
// `Todd`
// `My name is Todd`

词法作用域很好用,任何定义在父域中的变量、对象、函数,都可以被子域链访问到,举个例子:

var name = 'Todd';
var scope1 = function () {// name is available herevar scope2 = function () {// name is available here toovar scope3 = function () {// name is also available here!};};
};

唯一需要记住的是词法作用域不能反过来用。这里我们看看词法作用域是如何不工作的:

// name = undefined
var scope1 = function () {// name = undefinedvar scope2 = function () {// name = undefinedvar scope3 = function () {var name = 'Todd'; // locally scoped};};
};

我总是可以返回一个引用给最上层的name,但却从来不是变量('Todd')本身。

域链

域链给一个已知的函数建立了作用域。正如我们所知的那样,每一个被定义的函数都有自己的嵌套作用域,同时,任何被定义在其他函数中的函数都有一个本地域连接着外部的函数 - 这种连接被称作链。这就是在代码中定义作用域的地方。当我们在处理一个变量的时候,JavaScript就会开始从最里层的域向外查找直到找到要找的那个变量、对象或函数。

闭包

闭包和词法作用域非常相近。一个关于闭包如何工作的更好或者更实际的例子就是返回一个函数的引用。我们可以返回域中的东西,使得它们可以被其父域所用。

var sayHello = function (name) {var text = 'Hello, ' + name;return function () {console.log(text);};
};

我们此处所用的闭包使得sayHello里的域无法被公共域访问到。单是调用这个函数不会发生什么,因为它只是返回了一个函数而已:

sayHello('Todd'); // nothing happens, no errors, just silence...

这个函数返回了一个函数,就是说它需要分配然后才是调用:

var helloTodd = sayHello('Todd');
helloTodd(); // will call the closure and log 'Hello, Todd'

好吧,我撒谎了,你可以调用它,或许你已经看到了像这样的函数,但是这会调用你的闭包:

sayHello2('Bob')(); // calls the returned function without assignment

AngularJS就为其 $compile 方法用了上面的技术,当前作用域作为引用传递给闭包:

$compile(template)(scope);

我们可以猜测代码或许应该像下面这样:

var $compile = function (template) {// some magic stuff here// scope is out of scope, though...return function (scope) {// access to `template` and `scope` to do magic with too};
};

一个函数不是只有返回什么东西的时候才会称作闭包。简单地使词法作用域的外层可以访问其中的变量,这便创建了一个闭包。

作用域和关键字‘this’

每一个作用域都会根据函数的调用方式来绑定不同的 this 的值。我们都用过 this 关键字,但不是我们所有人都理解以及区别 this 在调用当中的变化。默认情况下 this 值得是做外层的公共对象 - window( node.js 里是 exports)。大概其看一下以不同方式调用函数时 this 值的不同:

var myFunction = function () {console.log(this); // this = global, [object Window]
};
myFunction();var myObject = {};
myObject.myMethod = function () {console.log(this); // this = Object { myObject }
};var nav = document.querySelector('.nav'); // <nav>
var toggleNav = function () {console.log(this); // this = <nav> element
};
nav.addEventListener('click', toggleNav, false);

这里还有个问题,就算在同一个函数中,作用域也是会变,this 的值也是会变:

var nav = document.querySelector('.nav'); // <nav>
var toggleNav = function () {console.log(this); // <nav> elementsetTimeout(function () {console.log(this); // [object Window]}, 1000);
};
nav.addEventListener('click', toggleNav, false);

那这里究竟发生了什么?我们新创建了一个不会从事件控制器调用的作用域,所以它也如我们所预期的那样,默认是指向 window 对象的。 如果我们想要访问这个 this 值,有几件事我们可以让我们达到目的。可能以前你就知道了,我们可以用一个像 that 这样的变量来缓存对 this 的引用:

var nav = document.querySelector('.nav'); // <nav>
var toggleNav = function () {var that = this;console.log(that); // <nav> elementsetTimeout(function () {console.log(that); // <nav> element}, 1000);
};
nav.addEventListener('click', toggleNav, false);

用 call,apply 和 bind 改变作用域

有时你会根据需要更改作用域。一个简单的证明如何在循环中更改作用域:

var links = document.querySelectorAll('nav li');
for (var i = 0; i < links.length; i++) {console.log(this); // [object Window]
}

在这里 this 值 不是指我们的元素,我们没有调用任何东西或者改变作用域。让我们来看一下如何改变作用域(看上去我们改变的是作用域,但是我们真正在做的却是更改函数被调用的上下文语境)。

.call() and .apply()

.call() 和 .apply() 这两个方法的确很美好,他们允许你传递一个函数给作用域,并绑定正确的 this 值。让我们看一下如何将 this 绑定给上面例子中的每个元素:

var links = document.querySelectorAll('nav li');
for (var i = 0; i < links.length; i++) {(function () {console.log(this);}).call(links[i]);
}

你可以看到我传递了当前的元素数组迭代( links[i] ),它盖面了函数的作用域以至于 this 值变成了每个元素。 我们可以用 this 绑定任何我们想要的。我们可以用 call 或者 apply 任一方法改变作用域,他们的区别是: .call(scope, arg1, arg2, arg3) 接收的是用逗号隔开的独立参数,而 .apply(scope, [arg1, arg2]) 接收的是一个参数数组。

记得用 call() or .apply() 而不是像下面这样调用你的函数非常重要:

myFunction(); // invoke myFunction

You'll let .call() handle it and chain the method:

myFunction.call(scope); // invoke myFunction using .call()

.bind()

不同于上述方法,使用 .bind() 不会调用一个函数, 它只是在函数运行前绑定了一个值。ECMASCript5 当中才引入这个方法实在是太晚太可惜了,因为它是如此的美妙。如你所知,我们不能出传递参数给函数,就像这样:

// works
nav.addEventListener('click', toggleNav, false);// will invoke the function immediately
nav.addEventListener('click', toggleNav(arg1, arg2), false);

我们可以通过在其中创建一个新的函数来搞定它:

nav.addEventListener('click', function () {toggleNav(arg1, arg2);
}, false);

还是那个问题,这个改变了作用域的同时我们也创建了一个不需要的函数,这对性能是一种浪费如果我们在循环内部绑定事件监听器。 尽管这使得我们可以传递参数进去,似乎应该算是 .bind() 的用武之地,但是这个函数不会被执行:

nav.addEventListener('click', toggleNav.bind(scope, arg1, arg2), false);

这个函数不会执行,并且作用域可以根据需要更改,但是参数还是在等待被传入。

私有域和公共域

在许多编程语言中,你将听到关于公共域和私有域,在 JavaScript 里没有这样的东西。但是我们可以通过像闭包一样的东西来模拟公共域和私有域。

我们可以通过使用 JavaScript 设计模式比如模块模式,来创建公共域和私有域。一个简单的创建私有域的途径就是把我们的函数包装进一个函数中。如我们之前学到的,函数创建作用域来使其中的东西不可被全局域访问:

(function () {// private scope inside here
})();

我们可能会紧接着创建一个新的函数在我们的应用中使用:

(function () {var myFunction = function () {// do some stuff here};
})();

当我们准备调用函数的时候,它不应在全局域里:

(function () {var myFunction = function () {// do some stuff here};
})();myFunction(); // Uncaught ReferenceError: myFunction is not defined

成功!我们就此创建了一个私有域。但是如果我像让这个函数变成公共的,要怎么做呢?有一个很好的模式(被称作模块模式)允许我们正确地处理函数作用域。这里我在全局命名空间里建立了一个包含我所有相关代码的模块:

// define module
var Module = (function () {return {myMethod: function () {console.log('myMethod has been called.');}};
})();// call module + methods
Module.myMethod();

在这里,return 的东西就是 public 方法返回的东西,它可以被全局域访问。我们的模块来关心我们的命名空间,它可以包含我们想要任意多的方法在里面:

// define module
var Module = (function () {return {myMethod: function () {},someOtherMethod: function () {}};
})();// call module + methods
Module.myMethod();
Module.someOtherMethod();

那私有方法呢?这里是很多开发者做错的地方,他们把所有的函数都堆砌在全局域里以至于污染了整个全局命名空间。可工作的函数代码不一定非在全局域里才行,除非像 APIs 这种要在全局域里可以被访问的函数。这里我们来写一个没有被返回出来的函数:

var Module = (function () {var privateMethod = function () {};return {publicMethod: function () {}};
})();

这就意味着 publicMethod 可以被调用,但是 privateMethod 则不行,因为它被域私有了!这些私有的函数可以是任何你能想到的对象或方法。

但是这里还有个有点拧巴的地儿,那就是任何在同一个域中的东西都可以访问同一域中的其他东西,就算在这儿函数被返回出去以后。也就是说,我们的公共函数可以访问私有函数,所以私有函数依然可以和全局域互动,但是不能被全局域访问。

var Module = (function () {var privateMethod = function () {};return {publicMethod: function () {// has access to `privateMethod`, we can call it:// privateMethod();}};
})();

这种互动是充满力量同时又保证了代码安全。JavaScript中很重要的一块就是保证代码的安全,这就解释了为什么我们不能接受把所有的函数都放在公共域中,因为这样的话,他们都被暴露出来很容易受到攻击。

下面有个例子,返回了一个对象,用到了 public 和 private 方法:

var Module = (function () {var myModule = {};var privateMethod = function () {};myModule.publicMethod = function () {};myModule.anotherPublicMethod = function () {};return myModule; // returns the Object with public methods
})();// usage
Module.publicMethod();

比较精巧的命名方式就是在私有方法名字前加下划线,这可以帮我们在视觉上区分公共的和私有的方法:

var Module = (function () {var _privateMethod = function () {};var publicMethod = function () {};
})();

这里我们可以借助面向对象的方式来添加对函数的引用:

var Module = (function () {var _privateMethod = function () {};var publicMethod = function () {};return {publicMethod: publicMethod,anotherPublicMethod: anotherPublicMethod}
})();

JavaScript中scope介绍相关推荐

  1. Javascript中Blob介绍

    一.Blob 是什么 Blob(Binary Large Object)表示二进制类型的大对象.在数据库管理系统中,将二进制数据存储为一个单一个体的集合.Blob 通常是影像.声音或多媒体文件.在 J ...

  2. javascript中BOM介绍、屏幕尺寸、历史记录、URL解析、计算机信息获取、定时器、三大系列及兼容代码、封装动画函数、同步和异步

    BOM介绍: BOM指的是浏览器对象模型,是用来操作浏览器的,例如浏览器弹窗.地址栏.滚动条等,浏览器顶级对象:window:页面中的所有内容都是属于window的,window可以省略:confir ...

  3. php中常见的错误类型有,JavaScript中常见的错误类型有哪些?(详细介绍)

    在JavaScript中,当发生错误时会生成描述错误类型的错误对象,此错误对象包含错误类型和编号等信息,这些信息可用于后续处理等,在本篇文章中将给大家介绍常见的错误类型以及如何处理这些错误. Java ...

  4. Javascript中 toFixed 规则介绍及其改进方法

    今天看了司徒大神的一篇博文,这篇文章专门介绍了toFixed的规则,搬来记录一下.文末有原文链接. javascript中toFixed使用的是银行家舍入规则. 银行家舍入:所谓银行家舍入法,其实质是 ...

  5. JavaScript中的预解析(变量提升)介绍!

    今天小千为大家介绍一下JavaScript中的预解析(变量提升).从什么是预解析及变量的预解析和函数的预解析及加载流程进行学习(注意:我们这里说的ES5中的预解析). 什么是解析 首先代码执行肯定需要 ...

  6. JavaScript中的全局变量介绍

    Global variables are declared outside of a function for accessibility throughout the program, while ...

  7. javascript中in用法介绍

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  8. java scope是什么意思_Tutorial:Javascript中的作用域(scope)是什么?(一)(试用FIREBUG了解)...

    From Learn About the Ext JavaScript Library Summary: 本教程讲解了Javascript中的作用域(scope)几个要点和变量可见度(variable ...

  9. JavaScript 中的作用域(scope)是指什么?

    解释: 作用域Scope是你代码中的变量(variable),函数(function)和对象(object)在运行时(runtime)的可访问性(accessibility).换句话讲,作用域Scop ...

最新文章

  1. 针对 easyui boolean 布尔值 的处理
  2. Java实现字符串反转的四种方式代码示例
  3. linux系统调用open、write、close、read以及stat函数详解
  4. LeetCode MySQL 1440. 计算布尔表达式的值(case when then else end)
  5. Linux一些软件的安装
  6. Android studio 去除软件运行时顶部原有的蓝色/绿色框
  7. oracle获取用户名,Oracle 用户名详解
  8. 【计算机视觉-从入门到精通系列】 第一章 基础知识
  9. 如何用C#代码判断一个类的类型
  10. dubbo源码 -- 服务导出
  11. solr配置oracle数据源,Solr索引Oracle数据库的基本配置
  12. Ardence BXP 3.5 - 4.1 PNP 方法
  13. opencv 特征提取 -SIFT
  14. 手机定位--GPS定位,基站定位,辅助定位
  15. 【EXCEL】去除多余行列
  16. 一键批量PDF转换成图片文件
  17. python二元一次方程组用鸡兔同笼的思路来写编程_python二元一次方程组用鸡兔同笼的思路来写编程_《应用二元一次方程组——鸡兔同笼》......
  18. 侃一侃人工智能2-零散的人工智能概念
  19. 冷月手撕408之操作系统(5)-进程概述
  20. /?、//、/'……等等是一个字符

热门文章

  1. 编程实现:为了节约用电,将用电量分成3个区间。不同的区间给出不同的收费标准。对于1至90度的电量,每度0.6元,对于91至150度的电量,每度1.1元,对于大于150度的电量,每度1.7元,编写一个J
  2. py2exe用法举例与调试技巧
  3. ref和reactive
  4. Undefined function or variable ‘fuzzypid‘.
  5. python-分割线
  6. 【通信】TETRA 与PDT的区别
  7. AES-GCM算法例程
  8. openlava一日惊魂
  9. 持续绩效管理最佳闭环
  10. three.js 常见光源类型