深入理解JavaScript词法作用域与闭包

闭包

关于闭包的定义:
A closure is the combination of a function and the lexical environment within which that function was declared. –MDN
A closure is the local variables for a function - kept alive after the function has returned . –javascriptkit


词法作用域 (lexical environment)

作用域链:

  • 函数执行过程中先从自己内部找变量
  • 如果找不到再从创建当前函数的作用域中找以此往上直到window
  • 注意找的是变量的当前状态

深入理解词法作用域

相关概念

执行上下文(executionContext)

活动对象AO:可以理解为函数内部创建的变量和函数的参数等,在函数调用的时候会创建。

[[Scope]]属性: 在活动对象中找不到的时候会从Scope中找
函数在声明的时候会创建一个[[Scope]]属性 ,并将包含该函数词法作用域的作用域链存放在scope属性中,调用函数的时候我们会有执行上下文也就是会创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象才构建起函数的执行作用域链。

代码理解:

var test = 0;
 function createCompareFunction(property) {
         return function (object1,object2) {
             var value1 = object1[property];
             var value2 = object2[property];
             if(value1 > value2) {
                 return value1;
             }else  if(value1 < value2) {
                 return value2;
             }else {
                 return 0;
             }
         }       
    }
    var compare  = createCompareFunction("age");
    compare({"age":10},{"age":20});//20

理解:

首先我们创建函数createCompareFunction 
globalContext = {
	AO: {
		x : 10,
		createCompareFunction: function,
		compare : undefined
	}
	Scope: null
}

createCompareFunctionContext.[[Scope]] = globalContext

createCompareFunctionContext = { //createCompareFunction 的执行环境即执行上下文
	AO:{
		property:'age',
		argumets:'age'
	},
	Scope:{
		globalContext
	}
}
//当函数createCompareFunction调用的时候其内部匿名函数开始调用
匿名函数Context = {
	AO: {
		object1 : {age:10},
		object2 : {age:20},
		argumets: [{age:10},{age:20}]
	},
	Scope:{
		arguments:{"age"},
		property : 'age',
		createCompareFunction.[[Scope]] : globalContext
	}
}
匿名执行的时候先在其AO即活动对象中找需要的参数变量等,
找不到再从其上一级作用域即其词法作用域中找依次往上 即从[[Scope]]属性中 找
而[[Scope]]属性保存的是该函数的词法作用域链也就是说会从其词法作用域链上找。
正因为匿名函数的[[Scope]] 有引用createCompareFunction函数的活动那个对象所以在其返回的时候
其执行环境的作用域链会被销毁但是其活动对象会存在内存中。
这也就是闭包的原始原理

什么是闭包?

JavaScript高级程序设计定义:闭包是有权访问另一个函数作用域中的变量的函数。

function a() {
	var j;
	function b(){
		return j;
	}
	return b;
}
var c  = a();
console.log(c());

自己的理解:
也就是说闭包是因为
函数b被外部函数c所引用所以,函数b会引用a的活动对象,因为a的活动对象还在被引用所以才不会销毁。
所以就间接保存了a的活动对象内的变量等等。
那么外部函数就可以通过引用a内的b来引用函数a的相关数据 且暂存a的活动对象中的相关数据。

从浏览器回收机制GC机制理解闭包

在JavaScript中如果一个对象不再被引用,那么这个对象就会被GC回收。
如果两个对象互相引用,而不是被第三者所引用那么这两个对象也会被回收。
而此时函数a被函数b引用,b又被a外的c (也就是第三者)引用 于是乎函数a执行完后不会被回收,同理其内部的函数b也不会被回收内部的变量也不会被回收。
这就起到了封装数据和暂存数据的目的。
而被引用的函数b和声明该函数b的词法作用域也就是作用域链组合而成为闭包。

闭包的作用

封装数据
暂存数据

闭包典例

function car(){
  var speed = 0  /
  function fn(){
    speed++
    console.log(speed)
  }               /闭包就是这个fn函数连同他词法作用域链上的变量spedd
  return fn     
}

var speedUp = car()
speedUp()   //1
speedUp()   //2

用处:
可以暂存变量,
变量不会被销毁掉。

就是说当函数car内部函数fn被函数car外的一个变量引用的时候就创建了一个闭包。

闭包相关案例理解:

[ 01 ] 如下代码输出多少?如果想输出3,那如何改造代码?

var fnArr = [];
for (var i = 0; i < 10; i ++) {
  fnArr[i] =  function(){
    return i
  };
}
console.log( fnArr[3]() ) // 10

为什么是10而不是3呢?

fnArr[i]  =  function(){
    return i;
};此时fnArr[i]还没有执行所以return 是 i  i不保存当前的数字
举个例子:
fnArr[3] = function(){
    return i;
}而不是
fnArr[3] = function(){
    return 3;
}

闭包改造:

01:


var fnArr = [];

for (var i = 0; i < 10; i ++) {
  fnArr[i] =  (function(j){
    return function(){
      return j
        } 
  })(i)
}
console.log( fnArr[3]() ) // 3  案例一生成了10个闭包  相当于这个
    //里边有10个这样的函数 每一个函数就是一个闭包保存一个变量  10个闭包存着10个不同值

理解01

var fnArr = [];
for(var i  = 0;i<10;i++) {
	fnArr[i] = (function(j){
		return function(){
			return j;
		}
	})(i);
}
可以变形为
function a(i) {
	var j = i;
	function b(){
		return j; 
		//匿名函数内返回j但是匿名函数内没有即从其词法作用域中找寻到。 即 j是匿名函数外的参数	
	}
	return b;
}
var c = a(); //这是函数(function(j){})(i) 的变形
c(); //这是fnArr[i]()的变形  也就是说fnArr[i]引用了函数b
02:
 for(var i = 0;i<10;i++) {
        (function (j) {
            fnArr[j] = function () {
                console.log(j);
            }
        })(i)
    }
    fnArr[3]();

03: ES6语法

var fnArr = []
for (let i = 0; i < 10; i ++) { //let是es6的语法
  fnArr[i] =  function(){
    return i
  } 
}
console.log( fnArr[3]() ) // 3

[ 02 ] 封装一个Car对象

var Car = (function(){
   var speed = 0;
   function set(s){
       speed = s
   }
   function get(){
      return speed
   }
   function speedUp(){
      speed++
   }
   function speedDown(){
      speed--
   }
   return {
      setSpeed: setSpeed,
      get: get,
      speedUp: speedUp,
      speedDown: speedDown
   }
})()
Car.set(30)
Car.get() //30
Car.speedUp()
Car.get() //31
Car.speedDown()
Car.get()  //30

//这个speed得不到释放 这里边只有一个作用域所以是只有一个闭包

[ 03 ]如下代码输出多少?如何连续输出 0,1,2,3,4

for(var i=0; i<5; i++){
  setTimeout(function(){
    console.log('delayer:' + i )
  }, 0)
}
//相当于setTimeout 会把当前的执行加到任务队列 设置了五个计时器并没有开始 当开始
//执行的时候i已经成为5了 i已经执行完了

闭包修改:

for(var i=0; i<5; i++){
  (function(j){
    setTimeout(function(){
      console.log('delayer:' + j )
    }, 0)    
  })(i)
}
或者
for(var i=0; i<5; i++){
  setTimeout((function(j){
    return function(){
      console.log('delayer:' + j )
    }
  }(i)), 0)    
}

[ 04 ]如下代码输出多少?

function makeCounter() {
  var count = 0

  return function() {
    return count++
  };
}

var counter = makeCounter();  
var counter2 = makeCounter(); //再次执行的时候又画了一个新的作用域 

console.log( counter() ) // 0   
console.log( counter() ) // 1  

console.log( counter2() ) // ?  2 
console.log( counter2() ) // ?   3

[ 05 ]补全代码,实现数组按姓名、年纪、任意字段排序:

var users = [
  { name: "John", age: 20, company: "Baidu" },
  { name: "Pete", age: 18, company: "Alibaba" },
  { name: "Ann", age: 19, company: "Tecent" }
]

users.sort(byName) 
users.sort(byAge)
users.sort(byField('company'))

答案:

function byName(user1, user2){
  return user1.name > user2.name
}

function byAge (user1, user2){
  return user1.age > user2.age
}

function byFeild(field){
  return function(user1, user2){
    return user1[field] > user2[field]
  }
}
users.sort(byField('company')) 相当于把field暂存下来

函数柯里化

函数柯里化-只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

写一个 sum 函数,实现如下调用方式

console.log( sum(1)(2) ) // 3
console.log( sum(5)(-1) ) // 4
function sum(a) {
  return function(b) {
    return a + b
  }
}

你可能感兴趣的:(高级JavaScript)