作用域,上下文,闭包

作用域

作用域决定了你的代码里的变量和其他资源在各个区域中的可见性,为代码提供了一个安全层级,用户只能访问他们当前需要的东西。

在 JavaScript 中有两种作用域:

全局作用域:定义在函数之外的变量会被保存在全局作用域中,在代码的任何地方都是可访问的。

局部作用域函数作用域):在函数内定义的变量(局部变量)或者函数的参数,只在当前函数体内以及这个函数体嵌套的任意函数体可访问,函数外部不能访问。

访问当前作用域的变量速度比访问其他作用域的快,因为会顺着作用域链查找,直到找到你要的或者没有结果。

在一个函数中,如果局部变量和全局变量同名,局部变量会覆盖全局变量。在函数体内访问这个变量,是局部变量的值。所以,在不同的作用域,可以命名相同的变量而不导致冲突,解决了不同范围的同名变量命名问题

  var num = 1;            //声明一个全局变量
   function func() {
      var num = 2;        //声明一个局部变量
       return num;
   }
   console.log(func());    //输出:2 

局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁。(当通过闭包在函数外面引用了局部变量,当函数执行完,局部变量不会被销毁)

一个应用中全局作用域的生存周期与该应用相同。局部作用域只在该函数调用执行期间存在。 

变量声明提前

变量在整个函数体内都是有定义的,所以在赋值前是能访问的,即变量声明提升到当前函数体顶部(要是当前函数嵌套了其他函数,其他函数中声明的变量,由于作用域链的关系,其他函数体的变量,在当前函数体内是不可访问的,不存在声明提升到当前函数体的说法)

 function func() {
            console.log(num);           //输出:undefined,而非报错,因为变量num在整个函数体内都是有定义的
            var num = 1;                //声明num 在整个函数体func内都有定义
            console.log(num); //输出:1 
} func();

作用域链

从当前作用域出发,决定了哪些数据能被访问,不讲大道理,直接画图吧。

比如下图:想在函数3里访问变量a,过程是:先在函数3里找,没有就在函数2里找,再没有就在函数1里找,直到找到全局作用域,看是否有这么个变量a,是不是像顺着一根链子找呢,这个就是作用域链。(上一层的作用域也叫做父级作用域)

ECMAScript 6 引入了let和const关键字。这些关键字可以代替var。

let likes = 'Coding';
const skills = 'Javascript and PHP';

和var关键字不同,let和const关键字支持在块级声明中创建使用局部作用域

if (true) {
var name = 'Hammad';
let likes = 'Coding';、
const skills = 'JavaScript and PHP';
}
console.log(name); // logs 'Hammad'
console.log(likes); // Uncaught ReferenceError: likes is not defined
console.log(skills); // Uncaught ReferenceError: skills is not defined

上下文

上下文指的是在相同的作用域中的this的值。可以使用函数方法改变上下文,上下文是执行的时候确定的

在全局作用域中,上下文总是 Window 对象。

作为一个对象的方法,上下文就是这个方法所在的那个对象。

使用new关键字调用函数时上下文,上下文会设置为被调用的函数的实例

当在严格模式(strict mode)中调用函数时,上下文默认是 undefined。

使用 .call(), .apply() 和 .bind() 改变上下文

Call 和 Apply 函数来改变函数调用时的上下文。
context={a:1,y:2}
function hello(a,b) {
alert(this.a);
alert(a);
alert(b);
}
hello();
hello.call(context,"cc","dd"); //1,cc,dd
hello.apply(context,["cc","dd"]); //

Bind 并不是自己调用函数,它只是在函数调用之前绑定上下文和其他参数。

(function introduce(name, interest) {
console.log('Hi! I'm '+ name +' and I like '+ interest +'.');
console.log('The value of this is '+ this +'.')
}).bind(window, 'Hammad', 'Cosmology')();

函数的运行过程

第一阶段是创建阶段,是函数刚被调用但代码并未执行的时候。创建阶段主要发生了 3 件事。

创建变量对象

创建作用域链

设置上下文(this)的值

第二个阶段就是代码执行阶段,进行其他赋值操作并且代码最终被执行。

词法作用域

词法作用域(静态作用域)的意思是在函数嵌套中,内层函数可以访问父级作用域的变量等资源,在定义时就确定。

闭包

当内部函数试着访问外部函数的作用域链(词法作用域之外的变量)时产生闭包。闭包包括它们自己的作用域链、父级作用域链和全局作用域。

闭包不仅能访问外部函数的变量,也能访问外部函数的参数。

即使函数已经return,闭包仍然能访问外部函数的变量。这意味着return的函数允许持续访问外部函数的所有资源

当你的外部函数return一个内部函数,调用外部函数时return的函数并不会被调用。你必须先用一个单独的变量保存外部函数的调用,然后将这个变量当做函数来调用。

用闭包实现共有作用域和私有作用域

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

函数结尾的括号告诉解析器立即执行此函数。我们可以在其中加入变量和函数,外部无法访问。但如果我们想在外部访问它们,也就是说我们希望它们一部分是公开的,一部分是私有的。我们可以使用闭包的一种形式,称为模块模式(Module Pattern),它允许我们用一个对象中的公有作用域和私有作用域来划分函数。

模块模式

var Module = (function() {
function privateMethod() {
// do something
}
return {
publicMethod: function() {
// can call privateMethod();
}
};
})();
Module.publicMethod(); // works
Module.privateMethod(); // Uncaught ReferenceError: privateMethod is not defined

Module 的return语句包含了我们的公共函数。私有函数并没有被return。函数没有被return确保了它们在 Module 命名空间无法访问。但我们的共有函数可以访问我们的私有函数,方便它们使用有用的函数、AJAX 调用或其他东西。

一种习惯是以下划线作为开始命名私有函数,并返回包含共有函数的匿名对象。这使它们在很长的对象中很容易被管理。向下面这样:

var Module = (function () {
function _privateMethod() {
// do something
}
function publicMethod() {
// do something
}
return {
publicMethod: publicMethod,
}
})();

立即执行函数表达式(IIFE)

另一种形式的闭包是立即执行函数表达式(Immediately-Invoked Function Expression,IIFE)。这是一种在 window 上下文中自调用的匿名函数,也就是说this的值是window。它暴露了一个单一全局接口用来交互。如下所示:

(function(window) {
// do anything
})(this);

 两段好玩的闭包代码

1、返回一个函数

function createComparisonFunction(propertyName) {
   return function(object1, object2){
  var value1 = object1[propertyName];
  var value2 = object2[propertyName];
  if (value1 < value2){
    return -1;
  } else if (value1 > value2){
    return 1;
  } else {
    return 0;
  }
  };
}
var compare = createComparisonFunction("name");
//createComparisonFunction函数返回后,闭包的作用域链开始初始化,包含了Comparison作用链上的对象;Comparison返回后作用域链被销毁,但是上面的活动对象内存得不到释放,直到匿名函数被销毁
var result = compare({ name: "Nicholas" }, { name: "Greg" }); compare=' ';//解除对匿名函数的引用,释放内存。此时result已经达到目的指向了需要引用的函数

2,循环引用造成的内存泄漏

var el = document.getElementById('MyElement');
        var func = function () {
            //
        }
        el.func = func;
        func.element = el;

//通常循环引用发生在为dom元素添加闭包作为expendo的时候。
function init() {
            var el = document.getElementById('MyElement');
            el.onclick = function () {
                //……
            }
        }
        init();

解除引用:
 function init() {
            var el = document.getElementById('MyElement');
            el.onclick = function () {
                //……
            }
            el = null;
        }
        init();
原文地址:https://www.cnblogs.com/yaoyao-sun/p/10365422.html