ホームページ >ウェブフロントエンド >jsチュートリアル >JavaScript のスコープについて知りたいことすべて_JavaScript のヒント

JavaScript のスコープについて知りたいことすべて_JavaScript のヒント

WBOY
WBOYオリジナル
2016-05-16 15:16:121231ブラウズ

JavaScript には一連のスコープの概念があります。 JS を初めて使用する開発者はこれらの概念を理解できず、経験豊富な開発者でも理解できない場合があります。この記事の主な目的は、スコープ、クロージャ、this、名前空間、関数スコープ、グローバル スコープ、字句スコープ、パブリック/プライベート スコープなどの JavaScript の概念を理解するのに役立つことです。この記事が次の質問に答えることができれば幸いです。 🎜 >

  • スコープとは何ですか?
  • グローバル スコープとローカル スコープとは何ですか?
  • ネームスペースとスコープの違いは何ですか?
  • このキーワードは何ですか?スコープはそれにどのように影響しますか?
  • 関数スコープと字句スコープとは何ですか?
  • クロージャとは何ですか?
  • パブリック スコープとプライベート スコープとは何ですか?
  • 上記の内容を理解して作成するにはどうすればよいですか?

1. スコープとは何ですか? JavaScript では、スコープは通常、コードのコンテキストを指します。グローバルまたはローカルのスコープを定義する機能。 JavaScript のスコープを理解することは、堅牢なコードを作成し、優れた開発者になるための前提条件です。変数と関数をどこで取得するか、コードのコンテキストのスコープをどこで変更するか、高速で読みやすく、デバッグが簡単なコードの書き方を知る必要があります。

スコープを想像するのは非常に簡単です。私たちはスコープ A にいるのでしょうか、それともスコープ B にいるのでしょうか?

2. グローバル スコープとは何ですか? JavaScript コードの最初の行を記述する前は、グローバル スコープ内にいます。この時点で、変数 (通常はグローバル変数) を定義します。

// global scopevar name = 'Todd';
グローバル スコープはあなたの友人であると同時に悪夢でもあります。スコープの制御方法を学ぶのは簡単で、グローバル変数の使用方法を一度学べば、問題 (通常は名前空間の競合) に遭遇することはなくなります。 「グローバルスコープが悪い」という意見をよく聞きますが、その理由を真剣に考えたことはありません。グローバルスコープが悪いというわけではなく、使い方の問題です。クロススコープのモジュール/API を作成するときは、問題を引き起こすことなくそれらを使用する必要があります。

jQuery('.myClass');
...グローバル スコープで jQuery を取得しています。この参照を名前空間と呼ぶことができます。名前空間は通常、単語を交換できるスコープを指しますが、通常はより高いレベルのスコープを指します。上の例では、jQuery はグローバル スコープ (名前空間とも呼ばれます) 内にあります。 jQuery は、グローバル スコープで名前空間として定義され、ライブラリ内のすべてのコンテンツは名前空間の子孫になります。

2. ローカルスコープとは何ですか? 通常、ローカル スコープはグローバル スコープの後に来ます。一般に、グローバル スコープがあり、各関数は独自のローカル スコープを定義します。別の関数内で定義された関数には、外部関数にリンクされたローカル スコープがあります。
関数を定義し、その中に変数を作成すると、それらの変数はローカル変数になります。例:

// 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);
変数名はローカル変数であり、親スコープに公開されていないため、定義されていません。

3. 関数のスコープ JavaScript の関数スコープは最小スコープです。 for ループ、while ループ、if および switch のいずれもスコープを構築できません。新しい関数には新しいドメインがあるという法則があります。ドメインを作成する簡単な例は次のとおりです:

// Scope A
var myFunction = function () {
// Scope B
var myOtherFunction = function () {// Scope C};};
新しいドメインやローカル変数、関数、オブジェクトを作成するのに非常に便利です。

4. 語彙範囲 関数が別の関数内にネストされており、内部関数が外部関数のスコープにアクセスできる場合、このメソッドはレキシカル スコープ (Lexical Socpe) またはクロージャと呼ばれ、静的スコープとも呼ばれます。この問題を最もよく示す例は次のとおりです:

// Scope A
var myFunction = function () {
// Scope B
var name = 'Todd'; // defined in Scope B
var myOtherFunction = function () {
// Scope C: `name` is accessible here!};
};
MyOtherFunction はここで定義されているだけであり、呼び出されません。この呼び出し順序は、変数の出力にも影響します。ここでは、別のコンソールで関数を定義して呼び出します。

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 here
var scope2 = function () {// name is available here too
var scope3 = function () {// name is also available here!};
};
};

唯一需要注意的事情是词汇域不后项起作用,下面的方式词汇域是不起作用的:

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

能返回对name的引用,但是永远也无法返回变量本身。

5、作用域链
函数的作用域由作用域链构成。我们知道,每个函数可以定义嵌套的作用域,任何内嵌函数都有一个局部作用域连接外部函数。这种嵌套关系我们可以称为链。域一般由代码中的位置决定。当解释(resolving)一个变量,通常从作用域链的最里层开始,向外搜索,直到发现要寻找的变量、对象或者函数。

6、闭包(Closures)
闭包和词法域( Lexical Scope)很像。返回函数引用,这种实际应用,是一个可以用来解释闭包工作原理的好例子。在我们的域内部,我们可以返回对象,能够被父域使用。

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

Angular js 在$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};
};

闭包并不一定需要返回函数。单纯在中间词汇域量的范围外简单访问变量就创造了一个闭包。

7、作用域和this关键字
根据函数被触发的方式不一样,每个作用域可以绑定一个不同的this值。我们经常使用this,但是我们并不是都了解其具体指代什么。 this默认是执行最外层的全局对象,windows对象。我们能够很容易的列举出不同触发函数绑定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 class="nav">
var toggleNav = function () {
console.log(this); // this = <nav> element};
nav.addEventListener('click', toggleNav, false);

在处理this值的时候,也会遇到问题。下面的例子中,即使在相同的函数内部,作用域和this值也会不同。

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

发生了什么?我们创建了一个新的作用域且没有在event handler中触发,所以其得到预期的windows对象。如果想this值不受新创建的作用域的影响,我们能够采取一些做法。以前可能也你也见过,我们使用that创建一个对this的缓存引用并词汇绑定:

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

这是使用this的一个小技巧,能够解决新创建的作用域问题。

8、使用.call(), .apply() 和.bind()改变作用域
有时候,需要根据实际的需求来变化代码的作用域。一个简单的例子,如在循环中如何改变作用域:

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

这里的this并没有指向我们的元素,因为我们没有触发或者改变作用域。我们来看看如何改变作用域(看起来我们是改变作用域,其实我们是改变调用函数执行的上下文)。

9、.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
可以使用.call()来链式调用:

myFunction.call(scope); // invoke myFunction using .call()
10、.bind()
和上面不一样的是,.bind()并不触发函数,它仅仅是在函数触发前绑定值。非常遗憾的是其只在 ECMASCript 5中才引入。我们都知道,不能像下面一样传递参数给函数引用:

// 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);

函数并没被触发,scope可以被改变,且参数在等着传递。

11、私有和公开作用域
在许多的编程语言中,存在public和private的作用域,但是在javascript中并不存在。但是在JavaScript中通过闭包来模拟public和private的作用域。

使用JavaScript的设计模式,如Module模式为例。一个创建private的简单方式将函数内嵌到另一个函数中。如我们上面掌握的,函数决定scope,通过scope排除全局的scope:

(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

成功的创建了一个私有作用域。那么怎么让函公有呢?有一个非常好的模式(模块模式)允许通过私有和公共作用域以及一个object对象来正确的设定函数作用域。暂且将全局命名空间称为Module,里面包含了所有与模块相关的代码:

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

这儿的return 语句返回了公共的方法,只有通过命名空间才能够被访问到。这就意味着,我们使用Module 作为我们的命名空间,其能够包含我们需要的所有方法。我们可以根据实际的需求来扩展我们的模块。

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

那私有方法怎么办呢?许多的开发者采取错误的方式,其将所有的函数都至于全局作用域中,这导致了对全局命名空间污染。 通过函数我们能避免在全局域中编写代码,通过API调用,保证可以全局获取。下面的示例中,通过创建不返回函数的形式创建私有域。

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

这就意味着publicMethod 能够被调用,而privateMethod 由于私有作用域不能被调用。这些私有作用域函数类似于: helpers, addClass, removeClass, Ajax/XHR calls, Arrays, Objects等。

下面是一个有趣事,相同作用域中的对象只能访问相同的作用域,即使有函数被返回之后。这就意味我们的public方法能够访问我们的private方法,这些私有方法依然可以起作用,但是不能够在全局左右域中访问。

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

这提供了非常强大交互性和安全性机制。Javascript 的一个非常重要的部分是安全性,这也是为什么我们不能将所有的函数放在全局变量中,这样做易于被攻击。这里有个通过public和private返回Object对象的例子:

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 () {};})();

当返回匿名对象的时候,通过简单的函数引用赋值,Module可以按照对象的方式来用。

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

以上就是关于JavaScript作用域的全部内容,希望对大家的学习有所帮助。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。