变量相关与闭包问题

变量名提升

	var num = 123;
	function foo1 () {
		console.log( num );
		var num = 456;
		console.log( num );
	}
	foo1();
  1. 预解析的过程
  2. 代码的执行过程

程序在执行过程, 会先将代码读取到内存中检查. 会将所有的声明在此时进行标记. 所谓的标记就是
让 js 解释器知道有这个名字, 后面在使用名字的时候, 不会出现未定义的错误. 这个标记过程就是提升.

声明:

  1. 名字的声明, 标识符的声明( 变量名声明 )
    • 名字的声明就是让我们的解释器知道有这个名字
    • 名字没有任何数据与之对应
  2. 函数的声明
    • 函数声明包含两部分
    • 函数声明与函数表达式有区别, 函数声明是单独写在一个结构中, 不存在任何语句, 逻辑判断等结构中
	function f() {
		function func() {
		} // 声明
	
		if ( true ) {
			function func2() {} // 函数表达式
		}
		var f = function func3 () {}; // 函数表达式
		
		this.sayHello = function () {}; // 函数表达式
		
		
		var i = 1;
		function func4 () {}  // 函数声明
		var j = 2;
	}
* 首先函数声明告诉解释器有这个名字存在. 该阶段与名字声明一样
* 告诉解释器, 这个名字对应的函数体是什么
	var num = 1;
	function num () {
		alert( num );
	}
	num();

分析:

  1. 预解析代码, 提升名字
    • 首先提升名字 num
    • 再提升函数名, 但是名字已经存在 因此只做第二部, 让名字与函数体对应上
    • 结论就是 代码中已经有一个 函数 num 了
  2. 开始执行代码, 第一话从 赋值语句开始执行
    • 给 num 赋值为 1
    • 覆盖了函数
  3. 调用 num, 由于 num 中存储是 数字 1, 因此报错
1. 预解析, 提升 num 名字和 foo1 函数
2. 执行第一句话: num = 123;
3. 执行函数调用
	* 函数调用进入函数的一瞬间也需要预解析. 解析的是变量名 num
	* 在函数内部是一个独立的空间, 允许使用外部的数据. 但是现在 num 声明同名, 即覆盖外面的
	* 执行第一句 打印 num, 没有数据 undefiend
	* 执行第二句 赋值: num = 456
	* 执行第三局 打印 num, 结果 456


代码:
if ( true ) {
	function f1 () {
		console.log( 'true' );
	}
} else {
	function f1 () {
		console.log( 'false' );
	}
}
f1();
1. 预解析: 提升 f1 函数, 只保留最后提升的内容, 所以打印是 false
2. 执行代码, 第一句话就是有一个 空的 if 结构
if ( true ) {
	
} else {
	
}
3. 执行函数调用, 得到 false



问题:
function foo() {}
var foo = function () {};

1. 上面的语法是声明, 可以提升, 因此在函数定义的上方也可以调用
2. 下面的语法是函数表达式, 函数名就是 foo, 它会提升. 提升的不是函数体
3. 函数表达式也是支持名字语法的
var foo = function func () {
};

func();
	* 函数有一个属性 name, 表示的是函数名. 只有带有名字的函数定义, 才会有 name 属性值, 否则是 ""
	* 但是, 函数表达式的名字, 只允许在函数内部使用. IE8 可以访问
	* () 可以将数据转换为表达式


新的浏览器中, 写在 if, while, do-while 结构中的函数, 都会将函数的声明转换成 特殊的函数表达式
将代码
if (...) {
	function foo () { ... }
}
转换成
if (...) {
	var foo = function foo () { ... }
}


# 词法作用域

## 作用域

域表示的就是 范围, 即 作用范围. 就是一个名字在什么地方可以被使用, 什么时候不能使用.

### 块级作用域

即块级别的作用范围
// 在 C , Java 等编程语言中, 下面的语法报错
{
	var num = 123;  // int 
	{
		console.log( num ); // => 123
	}
}
console.log( num ); // 报错

### 在 js 中采用词法作用域

所谓的 词法( 代码 )作用域, 就是代码在编写过程中体现出来的作用范围. 代码一旦写好, 不用执行,
作用范围就已经确定好了. 这个就是所谓词法作用域.

在 js 中词法作用域规则:
1. 函数允许访问函数外的数据.
2. 整个代码结构中只有函数可以限定作用域.
3. 作用规则首先使用提升规则分析
4. 如果当前作用规则中有名字了, 就不考虑外面的名字


例子1:
var num = 123;
function foo() {
	console.log( num );
}
foo();
例子2:
if ( false ) {
	var num = 123;
}
console.log( num ); // undefiend
例子3:
var num = 123;
function foo() {
	var num = 456;
	function func() {
		console.log( num );
	}
	func();
}
foo();

作用域链

可以发现只有函数可以制造作用域结构. 那么只要是代码, 至少有一个作用域, 即全局作用域.
凡是代码中有函数, 那么这个函数就构成另一个作用域. 如果函数中还有函数, 那么再这个作用域中就
又可以诞生一个作用域. 那么将这样的所有的作用域列出来, 可以有一个结构: 函数内指向函数外的链式结构.

例如:

	function f1() {
	
		function f2() {
		
		}
	}
	var num = 456;
	function f3() {
		function f4() {
		
		}
	}

绘制作用域链的步骤:

  1. 看整个全局是一条链, 即顶级链, 记为 0 级链
  2. 看全局作用域中, 有什么成员声明, 就以方格的形式绘制到 0 级练上
  3. 再找函数, 只有函数可以限制作用域, 因此从函数中引入新链, 标记为 1 级链
  4. 然后在每一个 1 级链中再次往复刚才的行为

变量的访问规则

  1. 首先看变量在第几条链上, 在该链上看是否有变量的定义与赋值, 如果有直接使用
  2. 如果没有到上一级链上找( n - 1 级链 ), 如果有直接用, 停止继续查找.
  3. 如果还没有再次往上刚找... 直到全局链( 0 级 ), 还没有就是 is not defined
  4. 注意, 切记 同级的链不可混合查找

如何分析代码

  1. 在分析代码的时候切记从代码的运行进度上来分析, 如果代码给变量赋值了, 一定要标记到图中
  2. 如果代码比较复杂, 可以在图中描述代码的内容, 有事甚至需要将原型图与作用域图合并分析
	var num = 123;
	function f1() {
		console.log( num );
	}
	function f2() {
		var num = 456;
		f1();
	}
	f2();
	var num = 123;
	function f1() {
		console.log( num );
	}
	function f2() {
		num = 456;
		f1();
	}
	f2();

补充

  1. 声明变量使用 var, 如果不使用 var 声明的变量就是全局变量( 禁用 )
  2. 因为在任何代码结构中都可以使用该语法. 那么再代码维护的时候会有问题. 所以除非特殊原因不要这么用.
  3. 下面的代码的错误
	function foo () {
		var i1 = 1  // 局部
			i2 = 2, // 全局
			i3 = 3; // 全局
		
	}
  1. 此时注意
	var arr = [];
	for ( var i = 0; i < 10; i++ ) {
		arr.push( i );
	}
	for ( var i = 0; i < 10; i++ ) {
		console.log( arr[ i ] );
	}
	// 一般都是将变量的声明全部放到开始的位置, 避免出现因为提升而造成的错误
	var arr = [], 
		i = 0;
	for ( ; i < 10; i++ ) {
		arr.push( i );
	}
	for ( i = 0; i < 10; i++ ) {
		console.log( arr[ i ] );
	}

闭包

闭包的含义就是闭合, 包起来. 简单的来说就是 一个具有封闭功能与 包裹功能的一个结构. 所谓的闭包就是,
有一个具有封闭的对外不公开的, 包裹结构, 或空间.

在 js 中函数可以构成闭包. 一般函数是一个代码结构的封闭结构, 即包裹的特性, 同时根据作用域规则,
只允许函数访问外部的数据, 外部无法访问函数内部的数据, 即封闭的对外不公开的特性. 因此说函数可以构成闭包.

闭包要解决什么问题

  1. 闭包不允许外界访问
  2. 要解决的问题就是间接访问该数据

函数就可以构成闭包, 要解决的问题就是访问到函数内部的数据

	function foo () {
		var num = 123;
		return num;
	}
	var res = foo();
	console.log( res ); // => 123
  1. 这里的确是访问到函数中的数据
  2. 但是该数据不能第二次访问. 因为第二次访问的时候又要调用一次 foo, 表示又有一个新的 num = 123 出来了

在函数内的数据, 不能直接在函数外被访问, 那么再函数内如果定义一个函数, 那么再这个内部函数中是可以直接访问的

	function foo() {
		var num = Math.random();
		function func() {
			return num;
		}
		return func;
	}
	var f = foo();
	// f 可以直接访问这个 num
	var res1 = f();
	var res2 = f();

答疑

函数科里化( 高阶函数 )

定义一个函数, 该函数返回一个函数, 那么在调用的时候

	function foo() {
		function func() {
		}
		return func;
	}
	foo()()

JavaScript 模式

	function color( r, g, b ) {
		// ...
	}
	=>
	color( 255, 0, 0 )
	function color ( r ) {
		return function color( g ) {
			return color( b ) {
			}
		}
	}
	color( 255 )( 0 )( 0 )

闭包

如何获得超过一个数据

	function foo () {
		var num1 = Math.random();
		var num2 = Math.random();
		
		return {
			num1: function () {
				return num1;
			},
			num2: function () {
				return num2;
			}
		}
	}

如何完成读取一个数据和修改这个数据

	function foo () {
		var num = Math.random();
		return {
			get_num: function () {
				return num;
			},
			set_num: function ( value ) {
				num = value;
			}
		}
	}
	

基本的闭包结构

一般闭包的问题就是要想办法间接的获得函数内数据的使用权. 那么我们的可以总结出一个基本的使用模型.

  1. 写一个函数, 函数内定义一个新函数, 返回新函数, 用新函数获得函数内的数据
  2. 写一个函数, 函数内定义一个对象, 对象中绑定多个函数( 方法 ), 返回对象, 利用对象的方法访问函数内的数据

闭包的基本用法

闭包是为了实现 具有私有访问空间的 函数的

  1. 带有私有访问数据的对象
	function Person() {
		this.name = '张三';
		// setName( '' )
	}
	// 所谓的私有数据, 就是说只有函数内部可以访问的数据, 或对象内部的方法可以访问的数据
	
	// 1  最简单的实现方式
	function createPerson() {
		var __name__ = "";
		return {
			get_Name: function () {
				return __name__;
			},
			set_Name: function ( value ) {
				// 如果不姓张就报错
				if ( value.charAt( 0 ) === '张' ) {
					__name__ = value;
				} else {
					throw new Error( '姓氏不对, 不能取名' );
				}
			}
		};
	}
	// 2
	
  1. 带有私有数据的函数
	var func = function () {}
	function func () {}
	
	var foo = (function () {
		// 私有数据
		return function () {
			// 可以使用私有的数据
		
		};
	})();

闭包的性能问题?

函数执行需要内存, 那么函数中定义的变量, 会在函数执行结束后自动回收. 凡是因为闭包结构, 被引出的数据.
如果还有变量引用这些数据的话, 那么这些数据就不会被回收.

因此在使用闭包的时候如果不使用某些数据了, 一定要赋值一个 null

	var f = (function () {
		var num = 123;
		return function () {
			return num;
		};
	})();
	
	// f 引用着函数, 函数引用变量 num
	// 因此在不使用该数据的时候, 最好写上
	f = null;
posted @ 2016-08-11 21:41  anxia  阅读(208)  评论(0)    收藏  举报