>  기사  >  웹 프론트엔드  >  JavaScriptscopes_javascript 팁에 대해 알고 싶은 모든 것

JavaScriptscopes_javascript 팁에 대해 알고 싶은 모든 것

WBOY
WBOY원래의
2016-05-16 15:16:121205검색

Javascript에는 일련의 범위 개념이 있습니다. JS를 처음 접하는 개발자는 이러한 개념을 이해하지 못할 수도 있고, 경험이 풍부한 일부 개발자도 이해하지 못할 수도 있습니다. 이 기사의 주요 목적은 범위, 클로저, this, 네임스페이스, 함수 범위, 전역 범위, 어휘 범위 및 공개/개인 범위와 같은 JavaScript의 일부 개념을 이해하는 데 도움을 주는 것입니다.

  • 스코프란 무엇인가요?
  • 글로벌 범위와 로컬 범위란 무엇입니까?
  • 네임스페이스와 범위의 차이점은 무엇인가요?
  • 이 키워드는 무엇이며 범위는 어떻게 영향을 미치나요?
  • 함수 범위와 어휘 범위란 무엇인가요?
  • 클로저란 무엇인가요?
  • 공개 범위와 비공개 범위란 무엇인가요?
  • 위 콘텐츠를 어떻게 이해하고 제작할 수 있나요?

1. 스코프란 무엇인가요?
JavaScript에서 범위는 일반적으로 코드의 컨텍스트를 나타냅니다. 글로벌 또는 로컬 범위를 정의하는 기능. JavaScript 범위를 이해하는 것은 강력한 코드를 작성하고 좋은 개발자가 되기 위한 전제 조건입니다. 변수와 함수를 얻을 수 있는 위치, 코드 컨텍스트의 범위를 변경할 위치, 빠르고 읽기 쉽고 디버그하기 쉬운 코드를 작성하는 방법을 알아야 합니다.

범위를 상상하는 것은 매우 간단합니다. 우리가 범위 A에 있습니까, 아니면 범위 B에 있습니까?

2. 글로벌 스코프란 무엇인가요?
JavaScript 코드의 첫 번째 줄을 작성하기 전에 우리는 전역 범위에 있습니다. 이 시점에서 우리는 일반적으로 전역 변수인 변수를 정의합니다.

// global scopevar name = 'Todd';

글로벌 범위는 친구이자 악몽입니다. 범위를 제어하는 ​​방법을 배우는 것은 쉽습니다. 전역 변수를 사용하는 방법을 배우면 문제(일반적으로 네임스페이스 충돌)가 발생하지 않습니다. 나는 종종 사람들이 "전역 범위가 나쁘다"고 말하는 것을 듣지만 그 이유에 대해 진지하게 생각해 본 적이 없습니다. 전역 범위가 나쁘다는 것이 아니라 사용법의 문제입니다. 교차 범위 모듈/API를 생성하는 동안 문제를 일으키지 않고 사용해야 합니다.

jQuery('.myClass');

...전역 범위에서 jQuery를 가져오고 있으며 이 참조를 네임스페이스라고 부를 수 있습니다. 네임스페이스는 일반적으로 단어를 교환할 수 있는 범위를 나타내지만 일반적으로 더 높은 수준의 범위를 나타냅니다. 위의 예에서 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 및 스위치 모두 범위를 구축할 수 없습니다. 규칙은 새로운 기능에는 새로운 도메인이 있다는 것입니다. 도메인 생성의 간단한 예는 다음과 같습니다.

// 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으로 문의하세요.