1. 使用 Function 创建函数与原来的方式创建函数
* Function 是使用字符串构建函数, 那么就可以在程序运行过程中构建函数
* 以前的函数必须一开始就写好, 再经过预解析, 一步一步的运行
* 假定从服务器那里拿到了 "[ 1, 2, 3, 4 ]"
* 将数组形式的字符串, 转换成数组对象
* var arr = ( new Function( 'return ' + str + ';' ) )();
2. 自调用函数
3. eval 函数
* 将字符串当做代码指向
4. 绘制完整的原型链结构
* 以 Person 为例
* p -> Person.prototype -> Object.prototype -> null
* Object 构造函数: o -> Object.prototype -> null
* Person 是 Function 的实例, 继承自 Function.prototype
* Object 是 Function 的实例, 继承自 Function.prototype
* Function 也是 Function 创建出来的( * )
* Function.prototype 继承自 Object.prototype -> null
# 变量名提升
```
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, 因此报错
```
var num = 123;
function foo1 () {
console.log( num );
var num = 456;
console.log( num );
}
foo1();
```
1. 预解析, 提升 num 名字和 foo1 函数
2. 执行第一句话: num = 123;
3. 执行函数调用
* 函数调用进入函数的一瞬间也需要预解析. 解析的是变量名 num
* 在函数内部是一个独立的空间, 允许使用外部的数据. 但是现在 num 声明同名, 即覆盖外面的
* 执行第一句 打印 num, 没有数据 undefiend
* 执行第二句 赋值: num = 456
* 执行第三局 打印 num, 结果 456
```
if ( ! 'a' in window ) {
var a = 123;
}
console.log( a );
```
1. 预解析, 读取提升 a, 有一个名字 a 存在了
2. in 运算符: 判断某一个字符串描述的属性名是否在 对象中
* var o = { name: 'jim' }; 'name' in o , 'age' in o
* 执行第一个判断: ! 'a' in window
* 'a' in window 真
* ! 得到 假
* if 内部的赋值不进行
3. 打印结果 a 的值为 undefined
代码:
```
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 () { ... }
}
```
# 面试题
1. getDate()
```
getYear()
getFullYear()
+new Date()
```
2. new Array() 相当于 []
* 如果没有参数: 创建一个空数组
* 如果有一个数字参数: 表示创建一个长度为 指定数字的 数组, 所有元素没有值或全为 0
* 如果有一个非数字的参数: 就表示用该参数初始化数组
* 如果数组构造函数的参数是多个数据, 都是对他的初始化
3. cancat( ... )
* 该方法会展开参数中的一级数组
# 词法作用域
## 作用域
域表示的就是 范围, 即 作用范围. 就是一个名字在什么地方可以被使用, 什么时候不能使用.
### 块级作用域
即块级别的作用范围
```
// 在 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();
```
练习:
```
var num1 = 123;
function foo1() {
var num1 = 456;
function foo2() {
num1 = 789;
function foo3 () {
console.log( num1 );
}
foo3();
}
foo2();
}
foo1();
console.log( num1 );
```
## 作用域链
可以发现只有函数可以制造作用域结构. 那么只要是代码, 至少有一个作用域, 即全局作用域.
凡是代码中有函数, 那么这个函数就构成另一个作用域. 如果函数中还有函数, 那么再这个作用域中就
又可以诞生一个作用域. 那么将这样的所有的作用域列出来, 可以有一个结构: 函数内指向函数外的链式结构.
例如:
```
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. 注意, 切记 同级的链不可混合查找
练习:
绘制作用域链
```
function f1() {
var num = 123;
function f2() {
console.log( num );
}
f2();
}
var num = 456;
f1();
```
如何分析代码
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; // 全局
}
```
4. 此时注意
```
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 () {
var o = { name: 'jim' };
return function () {
return o;
}
}
```
# 答疑
函数科里化( 高阶函数 )
定义一个函数, 该函数返回一个函数, 那么在调用的时候
```
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
```
2. 带有私有数据的函数
```
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;