aaaa
This commit is contained in:
133
teaching/lhj/kejian/js/note/24-闭包.md
Normal file
133
teaching/lhj/kejian/js/note/24-闭包.md
Normal file
@@ -0,0 +1,133 @@
|
||||
## 闭包的引入
|
||||
|
||||
我们知道,变量根据作用域的不同分为两种:全局变量和局部变量。
|
||||
|
||||
- 函数内部可以访问全局变量和局部变量。
|
||||
|
||||
- 函数外部只能访问全局变量,不能访问局部变量。
|
||||
|
||||
- 当函数执行完毕,本作用域内的局部变量会销毁。
|
||||
|
||||
比如下面这样的代码:
|
||||
|
||||
```js
|
||||
function foo() {
|
||||
let a = 1;
|
||||
}
|
||||
|
||||
foo();
|
||||
console.log(a); // 打印报错:Uncaught ReferenceError: a is not defined
|
||||
```
|
||||
|
||||
上方代码中,由于变量 `a` 是函数内的局部变量,所以外部无法访问。
|
||||
|
||||
但是,在有些场景下,我们就是想要在函数外部访问函数内的局部变量,那要怎么办呢?这就需要引入闭包的概念。
|
||||
|
||||
## 闭包的概念和代码举例
|
||||
|
||||
### 闭包的概念
|
||||
|
||||
**闭包**(closure):指有权**访问**另一个函数作用域中**变量**的**函数**。
|
||||
|
||||
上面这个概念,出自《JavaScript 高级程序设计(第 3 版)》这本书。上面的概念中指出,闭包是一种函数;当然,你可以**把闭包理解成是一种现象**。具体解释如下。
|
||||
|
||||
简单理解就是:如果**这个作用域可以访问另外一个函数内部的局部变量**,那就产生了闭包(此时,你可以把闭包理解成是一种现象);而另外那个作用域所在的函数称之为**闭包函数**。注意,这里强调的是访问**局部变量**哦。
|
||||
|
||||
### 闭包代码举例
|
||||
|
||||
代码举例:
|
||||
|
||||
```js
|
||||
function fn1() {
|
||||
let a = 10;
|
||||
|
||||
function fn2() {
|
||||
console.log(a);
|
||||
}
|
||||
fn2();
|
||||
}
|
||||
|
||||
fn1();
|
||||
```
|
||||
|
||||
打印结果:
|
||||
|
||||
```
|
||||
10
|
||||
```
|
||||
|
||||
上方代码中,函数 fn2 的作用域 访问了 fn1 中的局部变量,那么,此时在 fn1 中就产生了闭包,fn1 称之为闭包函数。
|
||||
|
||||
### 在 chrome 浏览器控制台中,调试闭包
|
||||
|
||||
上面的代码中,要怎么验证,确实产生了闭包呢?我们可以在 chrome 浏览器的控制台中设置断点,当代码执行到 `console.log(a)`这一行的时候,如下图所示:
|
||||
|
||||

|
||||
|
||||
上图中, Local 指的是局部作用域,Global 指的是 全局作用域;而 Closure 则是**闭包**,fn1 是一个闭包函数。
|
||||
|
||||
## 闭包的作用:可以读取函数内部的变量
|
||||
|
||||
我们来看看下面这段闭包的代码:
|
||||
|
||||
```js
|
||||
function fn1() {
|
||||
let a = 20;
|
||||
|
||||
function fn2() {
|
||||
console.log(a);
|
||||
}
|
||||
return fn2;
|
||||
}
|
||||
|
||||
const foo = fn1(); // 执行 fn1() 之后,会得到一个返回值。foo 代表的就是 fn2 函数
|
||||
foo();
|
||||
```
|
||||
|
||||
上方代码中,foo 代表的就是整个 fn2 函数。当执行了 `foo()` 语句之后(相当于执行了 ),fn1 函数内就产生了闭包。
|
||||
|
||||
一般来说,在 fn1 函数执行完毕后,它里面的变量 a 会立即销毁。但此时由于产生了闭包,所以 **fn1 函数中的变量 a 不会立即销毁,因为 fn2 函数还要继续调用变量 a**。只有等所有函数把变量 a 调用完了,变量 a 才会销毁。
|
||||
|
||||
而且,可以看出, 在执行 `foo()`语句之后,竟然能够打印出 `20`,这就完美通过闭包实现了:全局作用局成功访问到了局部作用域中的变量 a。
|
||||
|
||||
因此,我们可以看出,闭包的主要作用就是:延伸了变量的作用范围。
|
||||
|
||||
上面的代码也可以简写成:
|
||||
|
||||
```js
|
||||
function fn1() {
|
||||
let a = 20;
|
||||
|
||||
return function () {
|
||||
console.log(a);
|
||||
};
|
||||
}
|
||||
|
||||
const foo = fn1(); // 执行 fn1() 之后,会得到一个返回值。这个返回值是函数
|
||||
foo();
|
||||
```
|
||||
|
||||
## 闭包的作用:让这些变量的值始终保持在内存中,不会在函数调用后被自动清除。
|
||||
|
||||
```
|
||||
function f1(){
|
||||
var n=999;
|
||||
nAdd=function(){n+=1}
|
||||
|
||||
function f2(){
|
||||
alert(n);
|
||||
}
|
||||
return f2;
|
||||
}
|
||||
|
||||
var result=f1();
|
||||
result(); // 999
|
||||
nAdd();
|
||||
result(); // 1000
|
||||
|
||||
```
|
||||
解释:
|
||||
> 在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
|
||||
|
||||
> 为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
74
teaching/lhj/kejian/js/note/原型链.md
Normal file
74
teaching/lhj/kejian/js/note/原型链.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# 原型链和原型继承
|
||||
|
||||
在学习原型和原型链之前 我们先来复习一下 通过构造函数来创建对象的方式
|
||||
|
||||
### 构造函数
|
||||
任何函数都可以做为构造函数使用
|
||||
构造函数和普通函数的区别主要在于 调用的时候的差异
|
||||
|
||||
普通函数:
|
||||
```
|
||||
function create(){
|
||||
this.role="admin"
|
||||
}
|
||||
create()
|
||||
|
||||
```
|
||||
|
||||
构造函数:
|
||||
|
||||
```
|
||||
function create(){
|
||||
this.role="admin"
|
||||
}
|
||||
var juese=new create()
|
||||
|
||||
```
|
||||
此处的new 也属于运算符的一种 作用是创建一个用户定义的对象类型或具有构造函数的内置对象的实例
|
||||
|
||||
* new 操作符的过程
|
||||
new 关键字会进行如下的操作:
|
||||
|
||||
1. 创建一个空的简单JavaScript对象(即{});
|
||||
|
||||
2.链接该对象(设置该对象的constructor)到另一个对象 ;
|
||||
3. 将步骤1新创建的对象作为this的上下文 ;
|
||||
4. 如果该函数没有返回对象,则返回this。
|
||||
|
||||
* 分析 ```new foo() ``` 的执行过程
|
||||
|
||||
1. 一个继承自 Foo.prototype 的新对象被创建。
|
||||
2. 使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
|
||||
3.由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)
|
||||
|
||||
|
||||
|
||||
接下来我们把上面创建的对象打印一下看看会出现什么结果
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
然后我们会发现在我们定义的role属性下面多了一个```__proto__``` 属性 这个```__proto__```属性指的就是我们当前对象的原型
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 原型对象
|
||||
无论什么时候,只要创建一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。普通对象没有 prototype,但有``` __proto__``` 属性。
|
||||
|
||||
```
|
||||
function Person() {
|
||||
|
||||
}
|
||||
Person.prototype.name = 'Perty';
|
||||
var person = new Person();
|
||||
person.name = 'Kevin';
|
||||
console.log(person.name) // Kevin
|
||||
delete person.name
|
||||
console.log(person.name) // Perty
|
||||
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user