Rumah > Artikel > hujung hadapan web > Semua yang anda ingin ketahui tentang petua JavaScript scopes_javascript
Terdapat satu siri konsep skop dalam Javascript. Pembangun yang baru menggunakan JS tidak dapat memahami konsep ini, malah sesetengah pembangun berpengalaman mungkin tidak dapat memahaminya. Tujuan utama artikel ini adalah untuk membantu memahami beberapa konsep dalam JavaScript seperti: skop, penutupan, ini, ruang nama, skop fungsi, skop global, skop leksikal dan skop awam/peribadi Saya harap artikel ini dapat menjawab soalan berikut:
1. Apakah Skop?
Dalam JavaScript, skop biasanya merujuk kepada konteks kod. Keupayaan untuk menentukan skop global atau tempatan. Memahami skop JavaScript adalah prasyarat untuk menulis kod yang mantap dan menjadi pembangun yang baik. Anda perlu tahu tempat untuk mendapatkan pembolehubah dan fungsi, tempat untuk menukar skop konteks kod anda dan cara menulis kod yang pantas dan boleh dibaca serta mudah untuk nyahpepijat.
Membayangkan skop adalah sangat mudah, adakah kita dalam skop A atau skop B?
2. Apakah Skop Global?
Sebelum menulis baris pertama kod JavaScript, kami berada dalam skop global. Pada ketika ini kita mentakrifkan pembolehubah, biasanya pembolehubah global.
// global scopevar name = 'Todd';
Skop global ialah rakan dan mimpi ngeri anda. Belajar mengawal skop adalah mudah dan sebaik sahaja anda mempelajari cara menggunakan pembolehubah global, anda tidak akan menghadapi masalah (biasanya konflik ruang nama). Saya sering mendengar orang berkata "skop global adalah buruk", tetapi tidak pernah memikirkan sebabnya dengan serius. Bukan skop global buruk, ia adalah isu penggunaan. Semasa mencipta Modul/API silang skop, kita mesti menggunakannya tanpa menyebabkan masalah.
jQuery('.myClass');
...Kami mendapat jQuery dalam skop global, kami boleh memanggil rujukan ini ruang nama. Ruang nama biasanya merujuk kepada skop di mana perkataan boleh ditukar, tetapi yang biasanya merujuk kepada skop peringkat lebih tinggi. Dalam contoh di atas, jQuery berada dalam skop global, juga dikenali sebagai ruang nama. jQuery ditakrifkan dalam skop global sebagai ruang nama, yang berfungsi sebagai ruang arahan perpustakaan jQuery Semua kandungan dalam perpustakaan menjadi keturunan ruang nama.
2. Apakah Skop Tempatan?
Skop tempatan biasanya datang selepas skop global. Secara umumnya, terdapat skop global dan setiap fungsi mentakrifkan skop tempatannya sendiri. Mana-mana fungsi yang ditakrifkan di dalam fungsi lain mempunyai skop setempat yang dikaitkan dengan fungsi luar.
Jika anda mentakrifkan fungsi dan mencipta pembolehubah di dalamnya, pembolehubah tersebut ialah pembolehubah tempatan. Contohnya:
// Scope A: Global scope out here var myFunction = function () { // Scope B: Local scope in here};
Sebarang pembolehubah tempatan tidak kelihatan kepada pembolehubah global. Kecuali jika terdedah kepada dunia luar. Jika fungsi dan pembolehubah ditakrifkan dalam skop baharu, ia adalah pembolehubah dalam skop baharu semasa dan tidak boleh diakses di luar skop semasa. Berikut ialah contoh mudah:
var myFunction = function () { var name = 'Todd'; console.log(name); // Todd}; // Uncaught ReferenceError: name is not defined console.log(name);
Nama pembolehubah ialah pembolehubah setempat dan tidak terdedah kepada skop induk, jadi ia tidak ditakrifkan.
3. Skop fungsi
Skop fungsi dalam JavaScript ialah skop minimum. Baik gelung for mahupun while mahupun if dan suis boleh membina skop. Peraturannya ialah, fungsi baru mempunyai domain baru. Contoh mudah untuk mencipta domain adalah seperti berikut:
// Scope A var myFunction = function () { // Scope B var myOtherFunction = function () {// Scope C};};
Sangat mudah untuk mencipta domain baharu dan pembolehubah, fungsi dan objek setempat.
4. Skop Leksikal
Apabila fungsi bersarang dalam fungsi lain, dan fungsi dalam boleh mengakses skop fungsi luar, kaedah ini dipanggil Skop Leksikal (Lexical Socpe) atau penutupan, juga dikenali sebagai skop statik. Contoh yang paling menggambarkan masalah ini adalah seperti berikut:
// Scope A var myFunction = function () { // Scope B var name = 'Todd'; // defined in Scope B var myOtherFunction = function () { // Scope C: `name` is accessible here!}; };
MyOtherFunction hanya ditakrifkan di sini dan tidak dipanggil. Susunan panggilan ini juga mempengaruhi output pembolehubah. Di sini saya mentakrifkan dan memanggil fungsi dalam konsol lain.
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`
Skop leksikal lebih mudah digunakan Sebarang pembolehubah, objek dan fungsi yang ditakrifkan dalam skop induk boleh digunakan dalam rantai skop domainnya. Contohnya:
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作用域的全部内容,希望对大家的学习有所帮助。