Javascript之作用域和闭包

作用域

作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期

全局作用域

在代码中任何地方都能访问到的对象拥有全局作用域

一般来说以下三种情形拥有全局作用域

  • 最外层函数和在最外层函数外面定义的变量拥有全局作用域
  • 所有末定义直接赋值的变量自动声明为拥有全局作用域
  • 所有 window 对象的属性拥有全局作用域
// G1 var global = "global"; // 显式声明一个全局变量 function checkscope() { var local = "local"; // 显式声明一个局部变量 return global; // 返回全局变量的值 } console.log(scope); // "global" console.log(checkscope()); // "global" console.log(local); // error: local is not defined. // G2 这种写法容易造成误解,应尽量避免这种写法 function checkscope() { var local = "local"; // 显式声明一个局部变量 global = "global"; // 隐式声明一个全局变量(不好的写法) } console.log(global); // "global" console.log(local); // error: local is not defined.

局部作用域

局部作用域一般只在固定的代码片段内可访问到。
最常见的是在函数体内定义的变量,只能在函数体内使用

  • 在函数体内,局部变量的优先级高于同名的全局变量
  • 如果在函数内声明的一个局部变量或者函数参数中带有的变量和全局变量重名,那么全局变量就被局部变量所遮盖
  • JavaScript 函数里声明的所有变量(但不涉及赋值)都被「提前」至函数体的顶部(变量提升
var scope = "global"; function f() { console.log(scope); // 输出"undefined",而不是"global" var scope = "local"; // 变量在这里赋初始值,但变量本身在函数体内任何地方均是有定义的 console.log(scope); // 输出"local" }

作用域链

当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain

  • 内部环境可以通过作用域链访问所有的外部环境
  • 外部环境不能访问内部环境中的任何变量和函数
  • 每个环境都可以向上搜索作用域链,以查询变量和函数名
  • 任何环境都不能通过向下搜索作用域链而进入另一个执行环境

闭包

闭包是指有权访问另一个函数作用域中的变量的函数
由于在 Javascript 语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成定义在一个函数内部的函数。

闭包可以读取函数内部的变量(作用域链),让这些变量的值始终保持在内存中

// 计数器 var add = function() { var counter = 0; var plus = function() {return counter += 1;} return plus; } var puls2 = add(); console.log(puls2()); // 1 console.log(puls2()); // 2 console.log(puls2()); // 3

闭包注意点

  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在 IE 中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除或设置为 null,断开变量和内存的联系。
  • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(public method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

this关键字

  • thisJavaScript 的关键字,指函数执行时的上下文,跟函数定义时的上下文无关

  • 随着函数使用场合的不同,this 的值会发生变化

  • 总的原则,那就是 this 指代的是调用函数的那个对象。

  • 在全局上下文中,也就是在任何函数体外部,this 指代全局对象window

  • 在函数上下文中,也就是在任何函数体内部,this 指代调用函数的那个对象

  • call()apply() 是函数对象的方法,它的作用是改变函数的调用对象。第一个参数就表示改变后的调用这个函数的对象。因此,this 指代的就是这两个方法的第一个参数

  • call()apply() 的参数为空时,默认调用全局对象

关卡

  • 局部变量的优先级高于同名的全局变量。
  • 函数可以记忆它被创建时候的环境,可以读取上层函数内部的变量。
  • this 指代的是调用函数的那个对象,如无指代的对象,则为全局对象。
function func1() { function func2() { console.log(this) } return func2; } func1()(); // window // 函数执行时的上下文
scope = "stone"; function Func() { var scope = "sophie"; function inner() { console.log(scope); } return inner; } var ret = Func(); ret(); // "sophie" // 根据作用域链,优先级:sophie > stone
scope = "stone"; function Func() { var scope = "sophie"; function inner() { console.log(scope); } scope = "tommy"; return inner; } var ret = Func(); ret(); // "tommy" // 当函数执行到 return inner 时,局部变量 scope 的值已被更新为 "tommy" // inner() 函数的局部变量 scope 记录的并不是创建瞬间的值,而是指向 Func() 函数的局部变量的 scope 的指针, //「内部函数的变量」会随着「外层函数的变量」的值一起发生改变
scope = "stone"; function Bar() { console.log(scope); } function Func() { var scope = "sophie"; return Bar; } var ret = Func(); ret(); // "stone" // Bar() 内部没有scope,往上找,就到了全局的scope
// 挑战五 var name = "The Window"; var object = { name: "My Object", getNameFunc: function() { return function() { return this.name; }; } }; console.log(object.getNameFunc()()); // "The Window"
var name = "The Window"; var object = { name: "My Object", getNameFunc: function() { var that = this; return function() { return that.name; }; } }; console.log(object.getNameFunc()()); // "My Object"
var num = 10; var obj = { num: 20 }; obj.fn = (function (num) { this.num = num * 3; num++; return function (n) { this.num += n; num++; console.log(num); } })(obj.num) var fn = obj.fn; fn(5); // 22 obj.fn(10); // 23 console.log(num,obj.num); // 浏览器:65 30 // node: 10 30 /** * 浏览器中 * obj.fn是一个自执行函数,这时候this是window * this.num 就是上面定义的 var num = 10 * this.num = num * 3 ===> window.num = 60 * num++ ===> 这个自执行函数里面的num变量 num = 20 ===> num = 21 * * 执行fn(5) * 这么执行时,this是window, this.num 就是 window.num * this.num += n ===> window.num = 60 + 5 = 65 * 那个自执行函数里面的num++ ===> num = 22 * console.log(num) ===> 打印出来22 * * 执行obj.fn(10) * 这么执行时,this是obj, this.num 就是 obj.num * this.num += n ===> obj.num 由 20 变成了 30 * 那个自执行函数里面的num++ ===> num = 23 * console.log(num) ===> 打印出来23 * * console.log(num,obj.num) * num是window.num = 65 * obj.num = 30 * * node中顶级变量是global * (var num = 10) !== (global.num = 10) 这两个不是一回事 */
var x = 3, obj = { x: 5 }; obj.fn = (function () { this.x *= ++x; return function(y) { this.x *= (++x)+y; console.log(x); } })(); var fn = obj.fn; obj.fn(6); // 浏览器:13 node: 5 fn(4); // 浏览器:234 node: 6 console.log(obj.x, x); // 浏览器:95 234 node: 55 6 /** * 浏览器中 * obj.fn是一个自执行函数,这时候this是window * this.x 就是 window.x * x在这个函数体内没有,沿着作用域链往外找,找到var x = 3 即这里的 x 就是 window.x * this.x *= ++x ===> window.x = 3 * (3 + 1) = 12 * * 执行obj.fn(6) * 这么执行时,this为obj * this.x 就是 obj.x = 5 * x在这个函数体内没有,沿着作用域链往外找,找到闭包中的x,刚才说了 闭包中的 x 就是 window.x = 12 * this.x *= (++x)+y ===> obj.x = 5 * ((12 + 1) + 6) = 95 此时 window.x = 12 + 1 = 13 * console.log(x) 打印出来 13 * * 执行fn(4) * 这么执行时,this为window * this.x 就是 window.x = 13 * this.x *= (++x)+y ===> window.x = 13 * ((13 + 1) + 4) = 234 * console.log(x) 打印出来 234 * * console.log(obj.x, x) * obj.x = 95 * x = 234 * * * Node中 * obj.fn是一个自执行函数,这时候this是global * this.x *= ++x ===> global.x = undefined * (3 + 1) = NaN 此时最外层的 x = 4 * * 执行obj.fn(6) * this.x *= (++x)+y ===> obj.x = 5 * ((4 + 1) + 6) = 55 此时最外层的 x = 5 * console.log(x) 打印出来 5 * * 执行fn(4) * this.x *= (++x)+y ===> global.x = NaN * ((5 + 1) + 4) = NaN 此时最外层的 x = 6 * console.log(x) 打印出来 6 * * console.log(obj.x, x) * obj.x = 55 * x = 6 */
function fun(n,o) { console.log(o) return { fun:function(m){ return fun(m,n); } }; } var a = fun(0); a.fun(1); a.fun(2); a.fun(3); // undefined,0,0,0 var b = fun(0).fun(1).fun(2).fun(3); // undefined,0,1,2 var c = fun(0).fun(1); c.fun(2); c.fun(3); // undefined,0,1,1 /** * 执行var a = fun(0); 后 a = {fun: function(m){ return fun(m, 0);} * a.fun(1) → fun(1, 0) 打印 0 * a.fun(2) → fun(2, 0) 打印 0 * a.fun(3) → fun(3, 0) 打印 0 * * 执行var b = fun(0) 后 b = {fun: function(m){ return fun(m, 0);} * 再执行.fun(1) [打印 0]后 b = {fun: function(m){ return fun(m, 1);} * 再执行.fun(2) [打印 1]后 b = {fun: function(m){ return fun(m, 2);} * 再执行.fun(3) [打印 2]后 b = {fun: function(m){ return fun(m, 3);} * * 执行var c = fun(0) 后 c = {fun: function(m){ return fun(m, 0);} * 再执行.fun(1) [打印 0]后 c = {fun: function(m){ return fun(m, 1);} * c.fun(2) → fun(2, 1) 打印 1 * c.fun(3) → fun(2, 1) 打印 1 */
function Foo() { getName = function () { console.log (1); }; return this; } Foo.getName = function () { console.log (2);}; Foo.prototype.getName = function () { console.log (3);}; var getName = function () { console.log (4);}; function getName() { console.log (5);} // 请写出以下输出结果: Foo.getName(); // 2 getName(); // 4 Foo().getName(); // 1 getName(); // 1 new Foo.getName(); // 2 new Foo().getName(); // 3 new new Foo().getName(); // 3 // 详解 https://www.cnblogs.com/xxcanghai/p/5189353.html

创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: