From 2a872d975d3865c7b091dc8d21ec718cd97f64e5 Mon Sep 17 00:00:00 2001 From: xiejie <745007854@qq.com> Date: Wed, 3 Nov 2021 10:05:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=9F=A5=E8=AF=86=E5=9B=BE?= =?UTF-8?q?=E8=B0=B1=E5=92=8C=E5=BA=8F=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../let、var、const的区别.html | 41 + .../let、var、const的区别.md | 336 + 02. 值和引用/值和引用.js | 36 + 02. 值和引用/值和引用.md | 320 + 03. 包装类型/包装类型.js | 45 + 03. 包装类型/包装类型.md | 158 + 04. 数据类型的转换/数据类型的转换.js | 149 + 04. 数据类型的转换/数据类型的转换.md | 530 ++ 05. 运算符/运算符.js | 181 + 05. 运算符/运算符.md | 1369 +++ 06. 原型和原型链/原型和原型链.js | 99 + 06. 原型和原型链/原型和原型链.md | 368 + 07. 执行栈和执行上下文/执行栈和执行上下文.js | 120 + 07. 执行栈和执行上下文/执行栈和执行上下文.md | 318 + 08. 作用域和作用域链/作用域和作用域链.html | 25 + 08. 作用域和作用域链/作用域和作用域链.js | 37 + 08. 作用域和作用域链/作用域和作用域链.md | 423 + 09. this指向/this指向.js | 264 + 09. this指向/this指向.md | 803 ++ 10. 闭包/闭包.js | 80 + 10. 闭包/闭包.md | 430 + .../DOM 事件的注册和移除.html | 40 + .../DOM 事件的注册和移除.md | 199 + 12. DOM事件的传播机制/DOM 事件的传播机制.html | 47 + 12. DOM事件的传播机制/DOM 事件的传播机制.md | 316 + 13. 阻止事件的默认行为/阻止事件默认行为.html | 23 + 13. 阻止事件的默认行为/阻止事件默认行为.md | 170 + 14. 递归/递归.js | 49 + 14. 递归/递归.md | 146 + 15. 属性描述符/属性描述符.js | 154 + 15. 属性描述符/属性描述符.md | 315 + .../class 和构造函数区别.js | 121 + .../class 和构造函数区别.md | 578 ++ 17. 浮点数精度问题/浮点数精度问题.js | 29 + 17. 浮点数精度问题/浮点数精度问题.md | 241 + 18. 严格模式/严格模式.js | 49 + 18. 严格模式/严格模式.md | 294 + 19. 函数防抖和节流/函数防抖和节流.html | 87 + 19. 函数防抖和节流/函数防抖和节流.md | 333 + 20. 垃圾回收与内存泄漏/垃圾回收与内存泄漏.md | 182 + 21. WeakSet和WeakMap/WeakSet 和 WeakMap.js | 101 + 21. WeakSet和WeakMap/WeakSet 和 WeakMap.md | 552 ++ 22. 深浅拷贝/深浅拷贝.html | 231 + 22. 深浅拷贝/深浅拷贝.md | 532 ++ 23. 函数柯里化/函数柯里化.js | 116 + 23. 函数柯里化/函数柯里化.md | 318 + 24. Node的事件循环/Node的事件循环.html | 61 + 24. Node的事件循环/Node的事件循环.js | 131 + 24. Node的事件循环/Node的事件循环.md | 549 ++ 25. eval/eval.md | 45 + javascript 面试题汇总.md | 8026 +++++++++++++++++ 知识图谱.dio | 127 + 52 files changed, 20294 insertions(+) create mode 100644 01. let、var、const的区别/let、var、const的区别.html create mode 100644 01. let、var、const的区别/let、var、const的区别.md create mode 100644 02. 值和引用/值和引用.js create mode 100644 02. 值和引用/值和引用.md create mode 100644 03. 包装类型/包装类型.js create mode 100644 03. 包装类型/包装类型.md create mode 100644 04. 数据类型的转换/数据类型的转换.js create mode 100644 04. 数据类型的转换/数据类型的转换.md create mode 100644 05. 运算符/运算符.js create mode 100644 05. 运算符/运算符.md create mode 100644 06. 原型和原型链/原型和原型链.js create mode 100644 06. 原型和原型链/原型和原型链.md create mode 100644 07. 执行栈和执行上下文/执行栈和执行上下文.js create mode 100644 07. 执行栈和执行上下文/执行栈和执行上下文.md create mode 100644 08. 作用域和作用域链/作用域和作用域链.html create mode 100644 08. 作用域和作用域链/作用域和作用域链.js create mode 100644 08. 作用域和作用域链/作用域和作用域链.md create mode 100644 09. this指向/this指向.js create mode 100644 09. this指向/this指向.md create mode 100644 10. 闭包/闭包.js create mode 100644 10. 闭包/闭包.md create mode 100644 11. DOM事件的注册和移除/DOM 事件的注册和移除.html create mode 100644 11. DOM事件的注册和移除/DOM 事件的注册和移除.md create mode 100644 12. DOM事件的传播机制/DOM 事件的传播机制.html create mode 100644 12. DOM事件的传播机制/DOM 事件的传播机制.md create mode 100644 13. 阻止事件的默认行为/阻止事件默认行为.html create mode 100644 13. 阻止事件的默认行为/阻止事件默认行为.md create mode 100644 14. 递归/递归.js create mode 100644 14. 递归/递归.md create mode 100644 15. 属性描述符/属性描述符.js create mode 100644 15. 属性描述符/属性描述符.md create mode 100644 16. Class和普通构造器的区别/class 和构造函数区别.js create mode 100644 16. Class和普通构造器的区别/class 和构造函数区别.md create mode 100644 17. 浮点数精度问题/浮点数精度问题.js create mode 100644 17. 浮点数精度问题/浮点数精度问题.md create mode 100644 18. 严格模式/严格模式.js create mode 100644 18. 严格模式/严格模式.md create mode 100644 19. 函数防抖和节流/函数防抖和节流.html create mode 100644 19. 函数防抖和节流/函数防抖和节流.md create mode 100644 20. 垃圾回收与内存泄漏/垃圾回收与内存泄漏.md create mode 100644 21. WeakSet和WeakMap/WeakSet 和 WeakMap.js create mode 100644 21. WeakSet和WeakMap/WeakSet 和 WeakMap.md create mode 100644 22. 深浅拷贝/深浅拷贝.html create mode 100644 22. 深浅拷贝/深浅拷贝.md create mode 100644 23. 函数柯里化/函数柯里化.js create mode 100644 23. 函数柯里化/函数柯里化.md create mode 100644 24. Node的事件循环/Node的事件循环.html create mode 100644 24. Node的事件循环/Node的事件循环.js create mode 100644 24. Node的事件循环/Node的事件循环.md create mode 100644 25. eval/eval.md create mode 100644 javascript 面试题汇总.md create mode 100644 知识图谱.dio diff --git a/01. let、var、const的区别/let、var、const的区别.html b/01. let、var、const的区别/let、var、const的区别.html new file mode 100644 index 0000000..ed609a6 --- /dev/null +++ b/01. let、var、const的区别/let、var、const的区别.html @@ -0,0 +1,41 @@ + + + +
+ + + +
+
+
+
+这就是 *JavaScript* 中最原始的创建对象的方式,一个对象是通过克隆另外一个对象所得到的。就像克隆羊多莉一样,通过克隆可以创造一个一模一样的对象,被克隆的对象是新对象的原型对象。
+
+
+
+
+
+
+
+但是,随着 *JavaScript* 语言的发展,这样创建对象的方式还是太过于麻烦了。开发者还是期望 *JavaScript* 能够像 *Java、C#* 等标准面向对象语言一样,通过类来批量的生成对象。于是出现了通过构造函数来模拟类的形式。
+
+
+
+来看下面的例子:
+
+
+
+```js
+function Computer(name, price) {
+ // 属性写在类里面
+ this.name = name;
+ this.price = price;
+}
+// 方法挂在原型对象上面
+Computer.prototype.showSth = function () {
+ console.log(`这是一台${this.name}电脑`);
+}
+
+const apple = new Computer("苹果", 12000);
+console.log(apple.name); // 苹果
+console.log(apple.price); // 12000
+apple.showSth(); // 这是一台苹果电脑
+
+const huawei = new Computer("华为", 7000);
+console.log(huawei.name); // 华为
+console.log(huawei.price); // 7000
+huawei.showSth(); // 这是一台华为电脑
+```
+
+
+
+在上面的例子中,我们书写了一个 *Computer* 的函数,我们称之为构造函数,为了区分普通函数和构造函数,一般构造函数的函数名**首字母会大写**。
+
+
+
+区别于普通函数的直接调用,构造函数一般通过配合 *new* 关键字一起使用,每当我们 *new* 一次,就会生成一个新的对象,而在构造函数中的 *this* 就指向这个新生成的对象。
+
+
+
+在上面的例子中,我们 *new* 了两次,所以生成了两个对象,我们把这两个对象分别存储到 *apple* 和 *huawei* 这两个变量里面。
+
+
+
+有一个非常有意思的现象,就是我们在书写 *Computer* 构造函数的实例方法的时候,并没有将这个方法书写在构造函数里面,而是写在了 *Computer.prototype* 上面,那么这个 *Computer.prototype* 是啥呢?
+
+
+
+这个 *Computer.prototype* 实际上就是 *Computer* 实例对象的原型对象。要搞清楚这个,看下面的图:
+
+
+
+
+
+
+
+这是最重要的一个三角关系,也是我往往要求学生记下来的三角关系。
+
+
+
+通过上图,我们可以得出以下的结论:
+
+
+
+- *JavaScript* 中每个对象都有一个原型对象。可以通过 \__*proto*__ 属性来访问到对象的原型对象。
+- 构造函数的 *prototype* 属性指向一个对象,这个对象是该构造函数实例化出来的对象的原型对象。
+- 原型对象的 *constructor* 属性也指向其构造函数。
+- 实例对象的 *constructor* 属性是从它的原型对象上面访问到。
+
+
+
+实践才是检验真理的唯一标准。接下来我们在代码中来验证一下:
+
+
+
+```js
+function Computer(name, price) {
+ // 属性写在类里面
+ this.name = name;
+ this.price = price;
+}
+// 方法挂在原型对象上面
+Computer.prototype.showSth = function () {
+ console.log(`这是一台${this.name}电脑`);
+}
+
+const apple = new Computer("苹果", 12000);
+
+console.log(apple.__proto__ === Computer.prototype); // true
+console.log(apple.__proto__.constructor === Computer); // true
+```
+
+
+
+在上面的代码中,*apple* 是从 *Computer* 这个构造函数中实例化出来的对象,我们通过 \__*proto*__ 来访问到 *apple* 的原型对象,而这个原型对象和 *Computer.prototype* 是等价的。另外, 我们也发现 *apple* 和它原型对象的 *constructor* 属性都指向 *Computer* 这个构造函数。
+
+
+
+接下来我们还可以来验证内置的构造函数是不是也是这样的关系,如下:
+
+```js
+function Computer(name, price) {
+ // 属性写在类里面
+ this.name = name;
+ this.price = price;
+}
+// 方法挂在原型对象上面
+Computer.prototype.showSth = function () {
+ console.log(`这是一台${this.name}电脑`);
+}
+
+const apple = new Computer("苹果", 12000);
+
+console.log(apple.__proto__ === Computer.prototype); // true
+console.log(apple.__proto__.constructor === Computer); // true
+
+// 数组的三角关系
+var arr = [];
+console.log(arr.__proto__ === Array.prototype); // true
+
+// 其实所有的构造函数的原型对象都相同
+console.log(Computer.__proto__ === Array.__proto__); // true
+console.log(Computer.__proto__ === Date.__proto__); // true
+console.log(Computer.__proto__ === Number.__proto__); // true
+console.log(Computer.__proto__ === Function.__proto__); // true
+console.log(Computer.__proto__ === Object.__proto__); // true
+console.log(Computer.__proto__); // {}
+```
+
+通过上面的代码,我们发现所有的构造函数,无论是自定义的还是内置的,它们的原型对象都是同一个对象。
+
+
+
+如果你能够把上面的三角关系理清楚,恭喜你,你已经把整个原型和原型链的知识掌握一大部分。
+
+
+
+如果你还想继续往下深究,那么上面的图可以扩展成这样:
+
+
+
+
+
+
+
+在 *JavaScript* 中,每一个对象,都有一个原型对象。而原型对象上面也有一个自己的原型对象,一层一层向上找,最终会到达 *null*。
+
+
+
+我们可以在上面代码的基础上,继续进行验证,如下:
+
+
+
+```js
+function Computer(name, price) {
+ // 属性写在类里面
+ this.name = name;
+ this.price = price;
+}
+// 方法挂在原型对象上面
+Computer.prototype.showSth = function () {
+ console.log(`这是一台${this.name}电脑`);
+}
+
+var apple = new Computer("苹果", 12000);
+
+console.log(apple.__proto__.__proto__); // [Object: null prototype] {}
+console.log(apple.__proto__.__proto__.__proto__); // null
+console.log(apple.__proto__.__proto__ === Object.prototype); // true
+```
+
+
+
+可以看到,在上面的代码中,我们顺着原型链一层一层往上找,最终到达了 *null*。
+
+但是目前来看我们这个图还是不完整,既然构造函数的原型对象也是对象,那么必然该对象也有自己的原型,所以完整的图其实如下:
+
+
+
+下面可以简单验证一下,如下:
+
+```js
+// 自定义构造函数函数
+function Computer() {}
+
+console.log(Computer.__proto__.__proto__.__proto__); // null
+console.log(Computer.__proto__.constructor.__proto__ === Computer.__proto__); // true
+console.log(Computer.__proto__.__proto__.constructor.__proto__ === Computer.__proto__); // true
+
+```
+
+
+
+## 真题解答
+
+
+
+- 说一说你对 *JavaScript* 中原型与原型链的理解?(美团 *2019*年)
+
+> 参考答案:
+>
+> - 每个对象都有一个 \__*proto*__ 属性,该属性指向自己的原型对象
+> - 每个构造函数都有一个 *prototype* 属性,该属性指向实例对象的原型对象
+> - 原型对象里的 *constructor* 指向构造函数本身
+>
+> 如下图:
+>
+>
+>
+> 每个对象都有自己的原型对象,而原型对象本身,也有自己的原型对象,从而形成了一条原型链条。
+>
+> 当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
+
+
+
+- 对一个构造函数实例化后,它的原型链指向什么?
+
+> 参考答案:
+>
+> 指向该构造函数实例化出来对象的原型对象。
+>
+> 对于构造函数来讲,可以通过 *prototype* 访问到该对象。
+>
+> 对于实例对象来讲,可以通过隐式属性 \__*proto*__ 来访问到。
+
+
+
+-*EOF*-
+
diff --git a/07. 执行栈和执行上下文/执行栈和执行上下文.js b/07. 执行栈和执行上下文/执行栈和执行上下文.js
new file mode 100644
index 0000000..8ba656e
--- /dev/null
+++ b/07. 执行栈和执行上下文/执行栈和执行上下文.js
@@ -0,0 +1,120 @@
+// function a(){
+// var i= 10;
+// console.log(i);
+// }
+
+// a()
+
+// // 创建一个执行上下文环境
+// // 开始执行代码
+
+
+// var i = 1;
+// console.log(i);
+
+// 在执行上面的全局代码之前,还有一个准备工作
+// 创建一个全局上下文
+
+// console.log("Hello");
+// function foo () {
+// // ....
+// function bar () {
+// return 'I am bar';
+// }
+// return bar();
+// }
+// foo();
+// console.log("World");
+
+// const foo = function(i){
+// console.log(b);
+// console.log(c);
+// var a = "Hello";
+// var b = function privateB(){};
+// function c(){}
+// }
+// foo(10);
+
+// 生成一个 foo 的函数上下文环境
+
+// 1. 创建上下文阶段
+
+// vo 里面要确定的东西
+// - 确定函数的形参(并赋值)
+// - 函数环境会初始化创建 Arguments对象(并赋值)
+// - 确定普通字面量形式的函数声明(并赋值)
+// - 变量声明,函数表达式声明(未赋值)
+
+// fooExecutionContext = {
+// // vo = {
+// // i : 10,
+// // arguments : {0 : 10, length : 1},
+// // c : 指向 c 那个函数
+// // a : undefined
+// // b : undefined
+// // }
+// // this,
+// // scope
+// }
+
+// 2. 执行代码
+// vo = {
+// i : 10,
+// arguments : {0 : 10, length : 1},
+// c : 指向 c 那个函数
+// a : "Hello"
+// b : privateB 函数
+// }
+
+
+(function () {
+ console.log(typeof foo);
+ console.log(typeof bar);
+ var foo = "Hello";
+ var bar = function () {
+ return "World";
+ }
+
+ function foo() {
+ return "good";
+ }
+ console.log(foo, typeof foo);
+})()
+
+// 上面的代码也会创建一个函数上下文
+
+// 上下文的分为两个阶段:1. 创建阶段 2. 执行阶段
+// 1. 创建阶段
+// vo 里面要确定的东西
+// - 确定函数的形参(并赋值)
+// - 函数环境会初始化创建 Arguments对象(并赋值)
+// - 确定普通字面量形式的函数声明(并赋值)
+// - 变量声明,函数表达式声明(未赋值)
+// 在进行变量声明的时候,如果发现该变量名已经存在,则不会再声明
+// executionContext = {
+// vo : {
+// // foo : 指向 foo 函数
+// // bar : undefiend
+// },
+// // this,
+// // scope
+// }
+
+// 2. 执行代码
+// vo : {
+// // foo : "Hello"
+// // bar : function A
+// },
+
+
+// console.log(typeof foo); // function
+// console.log(typeof bar); // undefined
+// var foo = "Hello";
+// var bar = function A() {
+// return "World";
+// }
+
+// function foo() {
+// return "good";
+// }
+// console.log(foo, typeof foo); // Hello, string
\ No newline at end of file
diff --git a/07. 执行栈和执行上下文/执行栈和执行上下文.md b/07. 执行栈和执行上下文/执行栈和执行上下文.md
new file mode 100644
index 0000000..82b929f
--- /dev/null
+++ b/07. 执行栈和执行上下文/执行栈和执行上下文.md
@@ -0,0 +1,318 @@
+# 执行栈和执行上下文
+
+
+
+## 经典真题
+
+
+
+- 谈谈你对 *JavaScript* 执行上下文栈理解
+
+
+
+## 执行上下文
+
+
+
+执行上下文,英文全称为 *Execution Context*,一句话概括就是“代码(全局代码、函数代码)执行前进行的准备工作”,也称之为“执行上下文环境”。
+
+运行 *JavaScript* 代码时,当代码执行进入一个环境时,就会为该环境创建一个执行上下文,它会在你运行代码前做一些准备工作,如确定作用域,创建局部变量对象等。
+
+具体做了什么我们后面再说,先来看下 *JavaScript* 执行环境有哪些?
+
+
+
+***JavaScript* 中执行环境**
+
+1. 全局环境
+2. 函数环境
+3. *eval* 函数环境 (已不推荐使用)
+
+那么与之对应的执行上下文类型同样有 *3* 种:
+
+1. 全局执行上下文
+2. 函数执行上下文
+3. *eval* 函数执行上下文
+
+
+
+*JavaScript* 运行时首先会进入全局环境,对应会生成全局上下文。程序代码中基本都会存在函数,那么**调用函数**,就会进入函数执行环境,对应就会生成该函数的执行上下文。
+
+由于代码中会声明多个函数,对应的函数执行上下文也会存在多个。在 *JavaScript* 中,通过栈的存取方式来管理执行上下文,我们可称其为执行栈,或函数调用栈(*Call Stack*)。
+
+
+
+## 栈数据结构
+
+
+
+先来简单复习一下栈这种数据结构。
+
+要简单理解栈的存取方式,我们可以通过类比乒乓球盒子来分析。如下图:
+
+
+
+栈遵循**“先进后出,后进先出”**的规则,或称 ***LIFO*** (”*Last In First Out*“)规则。
+
+如图所示,我们只能从栈顶取出或放入乒乓球,最先放进盒子的总是最后才能取出。
+
+栈中**“放入/取出”**,也可称为**“入栈/出栈”**。
+
+总结栈数据结构的特点:
+
+1. 后进先出,先进后出
+2. 出口在顶部,且仅有一个
+
+
+
+**执行栈(函数调用栈)**
+
+理解完栈的存取方式,我们接着分析 *JavaScript* 中如何通过栈来管理多个执行上下文。
+
+程序执行进入一个执行环境时,它的执行上下文就会被创建,并被推入执行栈中(入栈);程序执行完成时,它的执行上下文就会被销毁,并从栈顶被推出(出栈),控制权交由下一个执行上下文。
+
+因为 *JavaScript* 在执行代码时最先进入全局环境,所以**处于栈底的永远是全局环境的执行上下文**。而处于**栈顶的是当前正在执行函数的执行上下文**。
+
+当函数调用完成后,它就会从栈顶被推出,理想的情况下,闭包会阻止该操作,闭包可以参阅《闭包》章节。
+
+而全局环境只有一个,对应的全局执行上下文也只有一个,只有当页面被关闭之后它才会从执行栈中被推出,否则一直存在于栈底。
+
+
+
+下面我们来看一段具体的代码示例:
+
+```js
+function foo () {
+ function bar () {
+ return 'I am bar';
+ }
+ return bar();
+}
+foo();
+```
+
+对应图解如下:
+
+
+
+
+
+**执行上下文的数量限制(堆栈溢出)**
+
+执行上下文可存在多个,虽然没有明确的数量限制,但如果超出栈分配的空间,会造成堆栈溢出。常见于递归调用,没有终止条件造成死循环的场景。
+
+```js
+// 递归调用自身
+function foo() {
+ foo();
+}
+foo();
+// 报错: Uncaught RangeError: Maximum call stack size exceeded
+```
+
+
+
+## 执行上下文生命周期
+
+
+
+前面我们有说到,运行 *JavaScript* 代码时,当代码执行进入一个环境时,就会为该环境创建一个执行上下文,它会在你运行代码前做一些准备工作。接下来我们就来看一下具体会做哪些准备工作。
+
+具体要做的事,和执行上下文的生命周期有关。
+
+执行上下文的生命周期有两个阶段:
+
+1. 创建阶段(**进入**执行上下文):函数被调用时,进入函数环境,为其创建一个执行上下文,此时进入创建阶段。
+2. 执行阶段(代码**执行**):执行函数中代码时,此时执行上下文进入执行阶段。
+
+
+
+**创建阶段**
+
+创建阶段要做的事情主要如下:
+
+1. 创建变量对象(*VO:variable object*)
+
+ - 确定函数的形参(**并赋值**)
+
+ - 函数环境会初始化创建 *Arguments*对象(**并赋值**)
+ - 确定普通字面量形式的函数声明(**并赋值**)
+ - 变量声明,函数表达式声明(**未赋值**)
+
+2. 确定 *this* 指向(***this* 由调用者确定**)
+
+3. 确定作用域(**词法环境决定,哪里声明定义,就在哪里确定**)
+
+
+
+这里有必要说一下变量对象。
+
+当处于执行上下文的建立阶段时,我们可以将整个上下文环境看作是一个对象。该对象拥有 *3* 个属性,如下:
+
+```js
+executionContextObj = {
+ variableObject : {}, // 变量对象,里面包含 Arguments 对象,形式参数,函数和局部变量
+ scopeChain : {},// 作用域链,包含内部上下文所有变量对象的列表
+ this : {}// 上下文中 this 的指向对象
+}
+```
+
+可以看到,这里执行上下文抽象成为了一个对象,拥有 *3* 个属性,分别是**变量对象**,**作用域链**以及 ***this* 指向**,这里我们重点来看一下变量对象里面所拥有的东西。
+
+在函数的建立阶段,首先会建立 *Arguments* 对象。然后确定形式参数,检查当前上下文中的函数声明,每找到一个函数声明,就在 *variableObject* 下面用函数名建立一个属性,属性值就指向该函数在内存中的地址的一个引用。
+
+如果上述函数名已经存在于 *variableObject*(简称 *VO*) 下面,那么对应的属性值会被新的引用给覆盖。
+
+最后,是确定当前上下文中的局部变量,如果遇到和函数名同名的变量,则会忽略该变量。
+
+
+
+**执行阶段**
+
+1. 变量对象赋值
+ - 变量赋值
+ - 函数表达式赋值
+2. 调用函数
+3. 顺序执行其它代码
+
+
+
+两个阶段要做的事情介绍完毕,接下来我们来通过代码来演示一下这两个阶段做的每一件事以及变量对象是如何变化的。
+
+```js
+const foo = function(i){
+ var a = "Hello";
+ var b = function privateB(){};
+ function c(){}
+}
+foo(10);
+```
+
+首先在建立阶段的变量对象如下:
+
+```js
+fooExecutionContext = {
+ variavleObject : {
+ arguments : {0 : 10,length : 1}, // 确定 Arguments 对象
+ i : 10, // 确定形式参数
+ c : pointer to function c(), // 确定函数引用
+ a : undefined, // 局部变量 初始值为 undefined
+ b : undefined // 局部变量 初始值为 undefined
+ },
+ scopeChain : {},
+ this : {}
+}
+```
+
+由此可见,在建立阶段,除了 *Arguments*,函数的声明,以及形式参数被赋予了具体的属性值外,其它的变量属性默认的都是 *undefined*。并且普通形式声明的函数的提升是在变量的上面的。
+
+一旦上述建立阶段结束,引擎就会进入代码执行阶段,这个阶段完成后,上述执行上下文对象如下,变量会被赋上具体的值。
+
+```js
+fooExecutionContext = {
+ variavleObject : {
+ arguments : {0 : 10,length : 1},
+ i : 10,
+ c : pointer to function c(),
+ a : "Hello",// a 变量被赋值为 Hello
+ b : pointer to function privateB() // b 变量被赋值为 privateB() 函数
+ },
+ scopeChain : {},
+ this : {}
+}
+```
+
+我们看到,只有在代码执行阶段,局部变量才会被赋予具体的值。在建立阶段局部变量的值都是 *undefined*。
+
+这其实也就解释了变量提升的原理。
+
+
+
+接下来我们再通过一段代码来加深对函数这两个阶段的过程的理解,代码如下:
+
+```js
+(function () {
+ console.log(typeof foo);
+ console.log(typeof bar);
+ var foo = "Hello";
+ var bar = function () {
+ return "World";
+ }
+
+ function foo() {
+ return "good";
+ }
+ console.log(foo, typeof foo);
+})()
+```
+
+这里,我们定义了一个 *IIFE*,该函数在建立阶段的变量对象如下:
+
+```js
+fooExecutionContext = {
+ variavleObject : {
+ arguments : {length : 0},
+ foo : pointer to function foo(),
+ bar : undefined
+ },
+ scopeChain : {},
+ this : {}
+}
+```
+
+首先确定 *Arguments* 对象,接下来是形式参数,由于本例中不存在形式参数,所以接下来开始确定函数的引用,找到 *foo* 函数后,创建 *foo* 标识符来指向这个 *foo* 函数,之后同名的 *foo* 变量不会再被创建,会直接被忽略。
+
+然后创建 *bar* 变量,不过初始值为 *undefined*。
+
+建立阶段完成之后,接下来进入代码执行阶段,开始一句一句的执行代码,结果如下:
+
+```js
+(function () {
+ console.log(typeof foo); // function
+ console.log(typeof bar); // undefined
+ var foo = "Hello"; // foo 被重新赋值 变成了一个字符串
+ var bar = function () {
+ return "World";
+ }
+
+ function foo() {
+ return "good";
+ }
+ console.log(foo, typeof foo); //Hello string
+})()
+```
+
+
+
+## 真题解答
+
+
+
+- 谈谈你对 *JavaScript* 执行上下文栈理解
+
+> 参考答案:
+>
+> **什么是执行上下文?**
+>
+> 简而言之,执行上下文是评估和执行 *JavaScript* 代码的环境的抽象概念。每当 *Javascript* 代码在运行的时候,它都是在执行上下文中运行。
+>
+> **执行上下文的类型**
+>
+> *JavaScript* 中有三种执行上下文类型。
+>
+> - **全局执行上下文:**这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事,创建一个全局的 *window* 对象(浏览器的情况下),并且设置 *this* 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
+> - **函数执行上下文:**每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。
+> - ***Eval* 函数执行上下文:**执行在 *eval* 函数内部的代码也会有它属于自己的执行上下文。
+>
+> **调用栈**
+>
+> 调用栈是解析器(如浏览器中的的 *JavaScript* 解析器)的一种机制,可以在脚本调用多个函数时,跟踪每个函数在完成执行时应该返回控制的点。(如什么函数正在执行,什么函数被这个函数调用,下一个调用的函数是谁)
+>
+> - 当脚本要调用一个函数时,解析器把该函数添加到栈中并且执行这个函数。
+> - 任何被这个函数调用的函数会进一步添加到调用栈中,并且运行到它们被上个程序调用的位置。
+> - 当函数运行结束后,解释器将它从堆栈中取出,并在主代码列表中继续执行代码。
+> - 如果栈占用的空间比分配给它的空间还大,那么则会导致“栈溢出”错误。
+
+
+
+-*EOF*-
\ No newline at end of file
diff --git a/08. 作用域和作用域链/作用域和作用域链.html b/08. 作用域和作用域链/作用域和作用域链.html
new file mode 100644
index 0000000..d080ed2
--- /dev/null
+++ b/08. 作用域和作用域链/作用域和作用域链.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+最后输出的结果为 *2、4、12*
+
+- 泡泡 *1* 是全局作用域,有标识符 *foo*;
+- 泡泡 *2* 是作用域 *foo*,有标识符 *a、bar、b*;
+- 泡泡 *3* 是作用域 *bar*,仅有标识符 *c*。
+
+值得注意的是:**块语句(大括号“{ }”中间的语句),如 *if* 和 *switch* 条件语句或 *for* 和 *while* 循环语句,不像函数,它们不会创建一个新的作用域**。在块语句中定义的变量将保留在它们已经存在的作用域中。
+
+```js
+if (true) {
+ // 'if' 条件语句块不会创建一个新的作用域
+ var name = 'Hammad'; // name 依然在全局作用域中
+}
+console.log(name); // logs 'Hammad'
+```
+
+*JS* 的初学者经常需要花点时间才能习惯变量提升,而如果不理解这种特有行为,就可能导致 *bug* 。
+
+正因为如此, *ES6* 引入了块级作用域,让变量的生命周期更加可控。
+
+
+
+### 块级作用域
+
+
+
+块级作用域可通过新增命令 *let* 和 *const* 声明,所声明的变量在指定块的作用域外无法被访问。
+
+块级作用域在如下情况被创建:
+
+1. 在一个函数内部
+2. 在一个代码块(由一对花括号包裹)内部
+
+*let* 声明的语法与 *var* 的语法一致。你基本上可以用 *let* 来代替 *var* 进行变量声明,但会将变量的作用域限制在当前代码块中。块级作用域有以下几个特点:
+
+
+
+- 声明变量不会提升到代码块顶部
+
+*let、const* 声明并不会被提升到当前代码块的顶部,因此你需要手动将 *let、const* 声明放置到顶部,以便让变量在整个代码块内部可用。
+
+```js
+function getValue(condition) {
+ if (condition) {
+ let value = "blue";
+ return value;
+ } else {
+ // value 在此处不可用
+ return null;
+ }
+ // value 在此处不可用
+}
+```
+
+
+
+- 禁止重复声明
+
+如果一个标识符已经在代码块内部被定义,那么在此代码块内使用同一个标识符进行 *let* 声明就会导致抛出错误。例如:
+
+```js
+var count = 30;
+let count = 40; // Uncaught SyntaxError: Identifier 'count' has already been declared
+```
+
+在本例中, *count* 变量被声明了两次:一次使用 *var* ,另一次使用 *let*。
+
+因为 *let* 不能在同一作用域内重复声明一个已有标识符,此处的 *let* 声明就会抛出错误。但如果在嵌套的作用域内使用 *let* 声明一个同名的新变量,则不会抛出错误。
+
+```js
+var count = 30;
+// 不会抛出错误
+if (condition) {
+ let count = 40;
+ // 其他代码
+}
+```
+
+
+
+- 循环中的绑定块作用域的妙用
+
+开发者可能最希望实现 *for* 循环的块级作用域了,因为可以把声明的计数器变量限制在循环内。
+
+例如,以下代码在 *JS* 经常见到:
+
+```html
+
+
+
+```
+
+```js
+var btns = document.getElementsByTagName('button')
+for (var i = 0; i < btns.length; i++) {
+ btns[i].onclick = function () {
+ console.log('第' + (i + 1) + '个')
+ }
+}
+```
+
+我们要实现这样的一个需求: 点击某个按钮, 提示"点击的是第 *n* 个按钮"。
+
+此处我们先不考虑事件代理,万万没想到,点击任意一个按钮,后台都是弹出“第四个”。
+
+这是因为 *i* 是全局变量,执行到点击事件时,此时 *i* 的值为 *3*。
+
+那该如何修改,最简单的是用 *let* 声明 *i*
+
+```js
+for (let i = 0; i < btns.length; i++) {
+ btns[i].onclick = function () {
+ console.log('第' + (i + 1) + '个')
+ }
+}
+```
+
+
+
+## 作用域链
+
+
+
+### 什么是自由变量
+
+
+
+首先认识一下什么叫做**自由变量** 。
+
+如下代码中,*console.log(a)* 要得到 *a* 变量,但是在当前的作用域中没有定义 *a*(可对比一下 *b*)。当前作用域没有定义的变量,这成为自由变量 。
+
+自由变量的值如何得到 ?
+
+需要向父级作用域寻找(注意:这种说法并不严谨,下文会重点解释)。
+
+```js
+var a = 100
+function fn() {
+ var b = 200
+ console.log(a) // 这里的 a 在这里就是一个自由变量
+ console.log(b)
+}
+fn()
+```
+
+
+
+### 什么是作用域链
+
+
+
+如果父级也没呢?
+
+再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链 。
+
+```js
+var a = 100
+function f1() {
+ var b = 200
+ function f2() {
+ var c = 300
+ console.log(a) // 100 自由变量,顺作用域链向父作用域找
+ console.log(b) // 200 自由变量,顺作用域链向父作用域找
+ console.log(c) // 300 本作用域的变量
+ }
+ f2()
+}
+f1()
+```
+
+
+
+### 关于自由变量的取值
+
+
+
+关于自由变量的值,上文提到要到父作用域中取,其实有时候这种解释会产生歧义。
+
+```js
+var x = 10
+function fn() {
+ console.log(x)
+}
+function show(f) {
+ var x = 20;
+ (function () {
+ f() // 10,而不是 20
+ })()
+}
+show(fn)
+```
+
+在 *fn* 函数中,取自由变量 *x* 的值时,要到哪个作用域中取 ?
+
+要到创建 *fn* 函数的那个作用域中取,**无论 *fn* 函数将在哪里调用**。
+
+所以,不要在用以上说法了。相比而言,用这句话描述会更加贴切:**要到创建这个函数的那个域”。作用域中取值,这里强调的是“创建”,而不是“调用”**,切记切记,其实这就是所谓的"静态作用域"。
+
+再来看一个例子:
+
+```js
+const food = "rice";
+const eat = function () {
+ console.log(`eat ${food}`);
+};
+(function () {
+ const food = "noodle";
+ eat(); // eat rice
+})();
+```
+
+在本示例中,最终打印的结果为 *eat rice*。因为对于 *eat( )* 函数来说,创建该函数时它的父级上下文为全局上下文,所以 *food* 的值为 *rice*。
+
+如果我们将代码稍作修改,改成如下:
+
+```js
+const food = "rice";
+(function () {
+ const food = "noodle";
+ const eat = function () {
+ console.log(`eat ${food}`);
+ };
+ eat(); // eat noodle
+})();
+```
+
+这个时候,打印出来的值就为 *eat noodle*。因为对于 *eat( )* 函数来讲,创建它的时候父级上下文为 *IIFE*,所以 *food* 的值为 *noodle*。
+
+
+
+## 作用域与执行上下文
+
+
+
+许多开发人员经常混淆作用域和执行上下文的概念,误认为它们是相同的概念,但事实并非如此。
+
+我们知道 *JavaScript* 属于解释型语言,*JavaScript* 的执行分为:解释和执行两个阶段,这两个阶段所做的事并不一样。
+
+
+
+**解释阶段**
+
+- 词法分析
+- 语法分析
+- 作用域规则确定
+
+
+
+**执行阶段**
+
+- 创建执行上下文
+- 执行函数代码
+- 垃圾回收
+
+
+
+*JavaScript* 解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。
+
+执行上下文最明显的就是 *this* 的指向是执行时确定的。而作用域访问的变量是编写代码的结构确定的。
+
+作用域和执行上下文之间最大的区别是:
+
+**执行上下文在运行时确定,随时可能改变,作用域在定义时就确定,并且不会改变**。
+
+
+
+## 真题解答
+
+
+
+- 谈谈你对作用域和作用域链的理解?
+
+> 参考答案:
+>
+> **什么是作业域 ?**
+>
+> *ES5* 中只存在两种作用域:全局作用域和函数作用域。
+>
+> 在 *JavaScript* 中,我们将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套子作用域中根据标识符名称进行变量(变量名或者函数名)查找。*ES6* 新增了块级作用域。
+>
+> **什么是作用域链 ?**
+>
+> 当访问一个变量时,编译器在执行这段代码时,会首先从当前的作用域中查找是否有这个标识符,如果没有找到,就会去父作用域查找,如果父作用域还没找到继续向上查找,直到全局作用域为止。
+>
+> 而作用域链,就是有当前作用域与上层作用域的一系列变量对象组成,它保证了当前执行的作用域对符合访问权限的变量和函数的有序访问。
+>
+> 作用域链有一个非常重要的特性,**那就是作用域中的值是在函数创建的时候,就已经被存储了,是静态的**。
+>
+> 所谓静态,就是说作用域中的值一旦被确定了,永远不会变。**函数可以永远不被调用,但是作用域中的值在函数创建的时候就已经被写入了,**并且存储在函数作用域链对象里面。
+
+
+
+-*EOF*-
+
diff --git a/09. this指向/this指向.js b/09. this指向/this指向.js
new file mode 100644
index 0000000..927fb0b
--- /dev/null
+++ b/09. this指向/this指向.js
@@ -0,0 +1,264 @@
+// 只要这个函数是以普通函数的形式被调用
+// function fn1(){
+// console.log(this); // 指向全局对象
+// }
+// fn1();
+
+// 如果是严格模式,那么 this 的值为 undefiend
+// function fn2(){
+// 'use strict'
+// console.log(this);
+// }
+// fn2();
+
+// 上面介绍了以函数的形式调用,this 的指向
+// 这种题目有一种变形
+// var foo = {
+// bar : 10,
+// func(){
+// console.log(this);
+// console.log(this.bar);
+// }
+// }
+// var fn2 = foo.func;
+// fn2();
+// foo.func();
+
+// 如果一个函数是以对象的方法的形式被调用
+// 那么 this 指向该对象
+// var stu = {
+// name : "zhangsan",
+// fn(){
+// return this;
+// }
+// }
+// console.log(stu.fn() === stu);
+
+// var stu = {
+// name : "zhangsan",
+// son : {
+// name : "zhangxiaosan",
+// fn(){
+// return this.name;
+// }
+// }
+// }
+// console.log(stu.son.fn());
+
+// var o1 = {
+// text : 'o1',
+// fn(){
+// return this.text;
+// }
+// }
+
+// var o2 = {
+// text : 'o2',
+// fn(){
+// return o1.fn();
+// }
+// }
+
+// var o3 = {
+// text : 'o3',
+// fn(){
+// var fn2 = o1.fn;
+// return fn2(); // 这里就相当于是普通函数的形式被调用
+// }
+// }
+// console.log(o1.fn()); // o1
+// console.log(o2.fn()); // o1
+// console.log(o3.fn()); // undefined
+
+// call
+// A.call(B)
+// A 通常是一个方法(函数)
+// B 通常是一个对象
+// 调用 A 方法,但是 this 指向 B 这个对象
+
+// var obj = {};
+// function fn(){
+// return this;
+// }
+// console.log(fn() === global);
+// console.log(fn.call(obj) === obj);
+
+// 下面的情况,this 指向全局对象
+// console.log(fn.call());
+// console.log(fn.call(null));
+// console.log(fn.call(undefined));
+// 总之,this 就指向你传入进去的对象
+
+// console.log(fn.call(true));
+
+// call 第一个参数是 this 指向的对象
+// 之后的参数就是参数列表,这些参数会传递给前面的方法
+// function add(a, b){
+// return a + b;
+// }
+// console.log(add.call(null, 1, 2));
+
+// call 一个经常的应用,就是调用原生的方法
+
+// var obj = {};
+// hasOwnProperty 该方法是查看一个对象是否有某一个属性或者方法
+// 这个属性或者方法必须是自身就有的,而不是继承而来
+// console.log(obj.hasOwnProperty('toString')); // false
+// console.log(obj.toString()); // [object Object]
+
+// 通过上面的例子,我们可以知道
+// obj 能够调用 toString,但是 toString 这个方法并不是他自身所拥有的
+// 来自于它的原型对象上面
+
+// obj.hasOwnProperty = function(){
+// return 'aaaaa';
+// }
+// console.log(obj.hasOwnProperty('toString')); // aaaaa
+
+// 上面我们对 hasOwnProperty 这个方法进行了覆盖
+// 使用 call 可以调用原生的方法
+// console.log(Object.prototype.hasOwnProperty.call(obj, 'toString'));;
+
+// apply
+// 该方法和 call 基本上一模一样
+// 区别仅仅是后面参数的区别,call 后面是参数列表
+// 而 apply 后面是一个参数数组
+
+// 使用 apply 调用原生方法
+
+// var arr = [1, 2, 3, 4, 5];
+
+// console.log(Math.max.apply(null, arr));
+
+// console.log(Array.prototype.slice.apply({ 0: 1, 1: 2, 2: 3 }));
+// console.log(Array.prototype.slice.apply({ 0: 1, 1: 2, 2: 3, length:3 }));
+// console.log(Array.prototype.slice.apply({ 0: 1, 1: 2, 2: 3, length:5 }));
+// console.log(Array.prototype.slice);
+
+// bind 绑定 this 指向,返回一个新的函数
+
+// var d = new Date();
+// console.log(d);
+// console.log(d.getTime());
+
+// var fn = d.getTime;
+// fn();
+// 上面的调用方式,使得 this 指向了全局对象,而非 Date 实例对象
+// 下面使用 bind 来绑定
+
+// var fn = d.getTime.bind(d);
+// console.log(fn());
+
+// bind 示例2
+// var counter = {
+// count : 0,
+// add(){
+// this.count++;
+// }
+// }
+// var obj = {
+// count : 100
+// }
+// var fn = counter.add.bind(obj);
+// fn();
+// console.log(counter.count);
+// console.log(obj.count);
+
+
+// var counter = {
+// count : 0,
+// add(){
+// 'use strict'
+// this.count++;
+// }
+// }
+
+// function callback(fn){
+// fn();
+// }
+
+// callback(counter.add);
+// console.log(counter.count); // 1
+
+// var obj = {
+// name : "zhangsan",
+// arr : [1,2,3],
+// print(){
+// this.arr.forEach(function(n){
+// console.log(this.name);
+// console.log(this === global);
+// }.bind(this))
+// }
+// }
+// obj.print();
+
+// bind 方法结合 call 方法使用
+
+
+// console.log([1, 2, 3].slice(0, 1));
+
+// slice 方法来源于 Array.prototype
+
+// console.log(Array.prototype.slice.call([1, 2, 3], 0, 1));
+
+// call 方法来源于 Function.prototype
+// var slice = Function.prototype.call.bind(Array.prototype.slice);
+// 这里就相当于改写了 slice 方法
+// 以前用 slice 方法 [1,2,3].slice(0,1)
+// console.log(slice([1,2,3], 0, 1));
+
+// function fn(){
+// console.log(this.v);
+// }
+
+// var obj = {
+// v : 123
+// }
+
+// var func = Function.prototype.call.bind(Function.prototype.bind);
+// func(fn, obj)();
+
+
+// 箭头函数 this 指向
+// var x = 20;
+// const obj = {
+// x: 10,
+// test: () => {
+// console.log(this); // {}
+// console.log(this.x); // undefined
+// }
+// }
+// obj.test();
+
+// var obj = {
+// name: '张三',
+// times: [1, 2, 3],
+// print: function () {
+// this.times.forEach((n)=>{
+// console.log(this.name);
+// });
+// }
+// };
+
+// obj.print()
+
+// var name = "JavaScript";
+// const obj = {
+// name: "PHP",
+// test: function () {
+// const i = ()=> {
+// console.log(this.name);
+// // i 是以函数的形式被调用的,所以 this 指向全局
+// // 在浏览器环境中打印出 JavaScript,node 里面为 undeifned
+// }
+// i();
+// }
+// }
+// obj.test();
+
+// 箭头函数不能作为构造函数
+const Test = (name, age) => {
+ this.name = name;
+ this.age = age;
+};
+const test = new Test("xiejie", 18);
\ No newline at end of file
diff --git a/09. this指向/this指向.md b/09. this指向/this指向.md
new file mode 100644
index 0000000..cd289d5
--- /dev/null
+++ b/09. this指向/this指向.md
@@ -0,0 +1,803 @@
+# *this* 指向
+
+
+
+## 经典真题
+
+
+
+- *this* 的指向哪几种 ?
+
+
+
+## *this* 指向总结
+
+
+
+*this* 关键字是一个非常重要的语法点。毫不夸张地说,不理解它的含义,大部分开发任务都无法完成。
+
+*this* 可以用在构造函数之中,表示实例对象。除此之外,*this* 还可以用在别的场合。**但不管是什么场合,*this* 都有一个共同点:它总是返回一个对象**。
+
+
+
+关于 *this* 的指向,有一种广为流传的说法就是“谁调用它,*this* 就指向谁”。
+
+这样的说法没有太大的问题,但是并不是太全面。总结起来,*this* 的指向规律有如下几条:
+
+
+
+- 在函数体中,非显式或隐式地简单调用函数时,在严格模式下,函数内的 *this* 会被绑定到 *undefined* 上,在非严格模式下则会被绑定到全局对象 *window/global* 上。
+
+
+
+- 一般使用 *new* 方法调用构造函数时,构造函数内的 *this* 会被绑定到新创建的对象上。
+
+
+
+- 一般通过 *call/apply/bind* 方法显式调用函数时,函数体内的 *this* 会被绑定到指定参数的对象上。
+
+
+
+- 一般通过上下文对象调用函数时,函数体内的 *this* 会被绑定到该对象上。
+
+
+
+- 在箭头函数中,*this* 的指向是由外层(函数或全局)作用域来决定的。
+
+
+
+当然,真实环境多种多样,下面我们就来根据实战例题逐一梳理。
+
+
+
+### 全局环境中的 *this*
+
+例题 *1*:
+
+```js
+function f1() {
+ console.log(this);
+}
+
+function f2() {
+ 'use strict'
+ console.log(this);
+}
+
+f1(); // window or global
+f2(); // undefined
+```
+
+这种情况相对简单、直接,函数在浏览器全局环境下被简单调用,在非严格模式下 *this* 指向 *window*,在通过 *use strict* 指明严格模式的情况下指向 *undefined*。
+
+虽然上面的题目比较基础,但是需要注意上面题目的变种,例如
+
+例题 *2*:
+
+```js
+const foo = {
+ bar : 10,
+ fn : function(){
+ console.log(this); // window or global
+ console.log(this.bar); // undefined
+ }
+}
+var fn1 = foo.fn;
+fn1();
+```
+
+这里的 *this* 仍然指向 *window*。虽然 *fn* 函数在 *foo* 对象中作为该对象的一个方法,但是在赋值给 *fn1* 之后,*fn1* 仍然是在 *window* 的全局环境下执行的。因此上面的代码仍然会输出 *window* 和 *undefined*。
+
+还是上面这道题目,如果改成如下的形式
+
+例题 *3*:
+
+```js
+const foo = {
+ bar : 10,
+ fn : function(){
+ console.log(this); // { bar: 10, fn: [Function: fn] }
+ console.log(this.bar); // 10
+ }
+}
+foo.fn();
+```
+
+这时,*this* 指向的是最后调用它的对象,在 *foo.fn( )* 语句中,this 指向的是 *foo* 对象。
+
+
+
+### 上下文对象调用中的 *this*
+
+
+
+例题 *4*:
+
+```js
+const student = {
+ name: 'zhangsan',
+ fn: function () {
+ return this;
+ }
+}
+console.log(student.fn() === student); // true
+```
+
+在上面的代码中,*this* 指向当前的对象 *student*,所以最终会返回 *true*。
+
+当存在更复杂的调用关系时,如以下代码中的嵌套关系,*this* 将指向最后调用它的对象,例如
+
+
+
+例题 *5*:
+
+```js
+const student = {
+ name: 'zhangsan',
+ son: {
+ name: 'zhangxiaosan',
+ fn: function () {
+ return this.name
+ }
+ }
+}
+console.log(student.son.fn()); // zhangxiaosan
+```
+
+在上面的代码中,*this* 会指向最后调用它的对象,因此输出的是 *zhangxiaosan*。
+
+至此,*this* 的上下文对象调用已经介绍得比较清楚了。我们再来看一道比较高阶的题目
+
+例题 *6*:
+
+```js
+const o1 = {
+ text: 'o1',
+ fn: function () {
+ return this.text;
+ }
+}
+
+const o2 = {
+ text: 'o2',
+ fn: function () {
+ return o1.fn();
+ }
+}
+
+const o3 = {
+ text: 'o3',
+ fn: function () {
+ var fn = o1.fn;
+ return fn();
+ }
+}
+
+console.log(o1.fn()); // o1
+console.log(o2.fn()); // o1
+console.log(o3.fn()); // undefined
+```
+
+答案是 *o1、o1、undefined*。
+
+这里主要讲一下为什么第三个是 *undefined*。这里将 *o1.fn* 赋值给了 *fn*,所以 *fn* 等价于 *function () { return this.text; }*,然后该函数在调用的时候,是直接 *fn( )* 的形式调用的,并不是以对象的形式,相当于还是全局调用,指向 *window*,所以打印出 *undefined*。
+
+
+
+### *this* 指向绑定事件的元素
+
+
+
+*DOM* 元素绑定事件时,事件处理函数里面的 *this* 指向绑定了事件的元素。
+
+这个地方一定要注意它和 *target* 的区别,*target* 是指向触发事件的元素。
+
+示例如下:
+
+```html
+
+
+
+
+有些时候我们会遇到一些困扰,比如在 *div* 节点的事件函数内部,有一个局部的 *callback* 方法,该方法被作为普通函数调用时,*callback* 内部的 *this* 是指向全局对象 *window* 的
+
+例如:
+
+```html
+
+
+
+
+接下来我们再来看一个例子,来证明箭头函数的 *this* 指向始终是指向的外层作用域。
+
+```js
+var name = "JavaScript";
+const obj = {
+ name: "PHP",
+ test: function () {
+ const i = function () {
+ console.log(this.name);
+ // i 是以函数的形式被调用的,所以 this 指向全局
+ // 在浏览器环境中打印出 JavaScript,node 里面为 undeifned
+ }
+ i();
+ }
+}
+obj.test(); // JavaScript
+```
+
+接下来我们将 i 函数修改为箭头函数,如下:
+
+```js
+var name = "JavaScript";
+const obj = {
+ name : "PHP",
+ test : function(){
+ const i = ()=>{
+ console.log(this.name);
+ // 由于 i 为一个箭头函数,所以 this 是指向外层的
+ // 所以 this.name 将会打印出 PHP
+ }
+ i();
+ }
+}
+obj.test();// PHP
+```
+
+最后需要说一点的就是,箭头函数不能作为构造函数,如下:
+
+```js
+const Test = (name, age) => {
+ this.name = name;
+ this.age = age;
+};
+const test = new Test("xiejie", 18);
+// TypeError: Test is not a constructor
+```
+
+
+
+## 真题解答
+
+
+
+- *this* 的指向哪几种 ?
+
+> 参考答案:
+>
+> 总结起来,*this* 的指向规律有如下几条:
+>
+> - 在函数体中,非显式或隐式地简单调用函数时,在严格模式下,函数内的 *this* 会被绑定到 *undefined* 上,在非严格模式下则会被绑定到全局对象 *window/global* 上。
+> - 一般使用 *new* 方法调用构造函数时,构造函数内的 *this* 会被绑定到新创建的对象上。
+> - 一般通过 *call/apply/bind* 方法显式调用函数时,函数体内的 *this* 会被绑定到指定参数的对象上。
+> - 一般通过上下文对象调用函数时,函数体内的 *this* 会被绑定到该对象上。
+> - 在箭头函数中,*this* 的指向是由外层(函数或全局)作用域来决定的。
+
+
+
+-*EOF*-
\ No newline at end of file
diff --git a/10. 闭包/闭包.js b/10. 闭包/闭包.js
new file mode 100644
index 0000000..bcd367f
--- /dev/null
+++ b/10. 闭包/闭包.js
@@ -0,0 +1,80 @@
+// 外部函数
+// function eat(){
+// var food = "鸡翅";
+// return function(){
+// console.log(food);
+// }
+// }
+// // 执行到第 8 行代码时,形成了一个闭包(封闭的空间)
+// // 在该闭包中,引用了 food 这个变量
+// // 所以 food 是没有被销毁的
+// var i = eat();
+// i();
+
+// 作用域是在函数创建的时候就确定下来了
+// var food = "rice";
+// // function eat(){
+// // console.log(food);
+// // }
+// (function(){
+// var food = "noodle";
+// function eat(){
+// console.log(food);
+// }
+// eat();
+// })()
+
+// 这里就是一个普通的计数器
+// var count = 0; // 全局变量
+// function counter() {
+// count++;
+// console.log(count);
+// }
+// for (var i = 0; i < 10; i++) {
+// counter();
+// }
+// 上面的代码有一个最大的问题,就是全局污染
+
+// ...
+// var count = 5;
+// console.log(count);
+
+// 接下来我们使用闭包来解决上面的全局污染的问题
+// function counter() {
+// var count = 0; // 计数器变量
+// return function(){
+// count++;
+// console.log(count);
+// }
+// }
+// var func = counter();
+// for (var i = 0; i < 10; i++) {
+// func();
+// }
+
+// var count = "aaa";
+
+
+// 下面这段代码也是一个闭包
+// 一个函数中要嵌套一个内部函数,并且内部函数要访问外部函数的变量
+// 内部函数要被外部引用
+// var a = 100;
+// setTimeout(function () {
+// console.log(++a);
+// }, 1000);
+
+// 有些时候因为闭包所存在的问题
+for (let i = 1; i <= 3; i++) {
+ setTimeout(function () {
+ console.log(i);
+ }, 1000);
+}
+
+
+// for (var i = 1; i <= 3; i++) {
+// (function (i) {
+// setTimeout(function () {
+// console.log(i);
+// }, 1000);
+// })(i);
+// }
diff --git a/10. 闭包/闭包.md b/10. 闭包/闭包.md
new file mode 100644
index 0000000..d908133
--- /dev/null
+++ b/10. 闭包/闭包.md
@@ -0,0 +1,430 @@
+# 闭包
+
+
+
+## 经典真题
+
+
+
+- 闭包是什么?闭包的应用场景有哪些?怎么销毁闭包?
+
+
+
+## 为什么需要闭包
+
+
+
+首先我们来看一下为什么需要闭包。先看下嘛的例子:
+
+```js
+function eat(){
+ var food = "鸡翅";
+ console.log(food);
+}
+eat(); // 鸡翅
+console.log(food); // 报错
+```
+
+在上面的例子中,我们声明了一个名为 *eat* 的函数,并对它进行调用。
+
+*JavaScript* 引擎会创建一个 *eat* 函数的执行上下文,其中声明 *food* 变量并赋值。
+
+当该方法执行完后,上下文被销毁,*food* 变量也会跟着消失。这是因为 *food* 变量属于 *eat* 函数的局部变量,它作用于 *eat* 函数中,会随着 *eat* 的执行上下文创建而创建,销毁而销毁。所以当我们再次打印 *food* 变量时,就会报错,告诉我们该变量不存在。
+
+
+
+那么,如何在函数销毁后也能继续使用变量 *food* 呢?
+
+这就涉及到了要使用闭包。
+
+
+
+## 什么是闭包
+
+
+
+要解释闭包,可以从**广义**和**狭义**上去理解。
+
+
+
+- 广义上的闭包:所有的函数就是闭包。
+- 狭义上的闭包:需要满足两个条件。
+ - 一个函数中要嵌套一个内部函数,并且内部函数要访问外部函数的变量
+ - 内部函数要被外部引用
+
+
+
+关于广义上闭包的含义,估计很多人很难理解,我就正常写个函数,怎么这玩意儿就变成闭包了?
+
+关于这一点,我们稍后再来解释。
+
+我们先来看一下狭义上的闭包。
+
+```js
+function eat(){
+ var food = '鸡翅';
+ return function(){
+ console.log(food);
+ }
+}
+var look = eat();
+look(); // 鸡翅
+look(); // 鸡翅
+```
+
+在这个例子中,*eat* 函数返回一个函数,并在这个内部函数中访问 *food* 这个局部变量。调用 *eat* 函数并将结果赋给 *look* 变量,这个 *look* 指向了 *eat* 函数中的内部函数,然后调用它,最终输出 *food* 的值。
+
+按照之前的说法,这个 *food* 变量应该当 *eat* 函数调用完后就销毁,后续为什么还能通过调用 *look* 方法访问到这个变量呢?
+
+这就是因为闭包起了作用。返回的内部函数和它外部的变量 *food* 实际上就是一个闭包。
+
+闭包的实质,就是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使离开了创造它的环境也不例外。
+
+
+
+这里提到了自由变量,它又是什么呢?
+
+**自由变量可以理解成跨作用域的变量,比如子作用域访问父作用域的变量。**
+
+如下代码中,*console.log(a)* 要得到 *a* 变量,但是在当前的作用域中没有定义 *a*(可对比一下 *b*)。当前作用域没有定义的变量,这成为自由变量 。
+
+```js
+var a = 100
+function fn() {
+ var b = 200
+ console.log(a) // 这里的 a 就是一个自由变量,需要顺着作用域链来查找 a 变量的值
+ console.log(b)
+}
+fn()
+```
+
+
+
+## 闭包的原理
+
+
+
+接下来,我们来看一下闭包的原理。
+
+要解释闭包的原理,这里需要回答 *2* 个问题。
+
+**(1)为什么函数内部可以访问外部函数的变量?**
+
+原因很简单,当一个函数上下文产生的时候,会确定 *3* 个东西:变量对象、作用域链条以及 *this* 指向。
+
+正因为有作用域链的存在,所以能够通过作用域链来访问到外部函数的变量。
+
+
+
+**(2)为什么当外部函数的上下文执行完以后,其中的局部变量还是能通过闭包访问到呢?**
+
+其实用上一个问题的答案再延伸一下,这个问题的答案就出来了。
+
+在介绍作用域的时候,我们有介绍过作用域是在函数创建的时候就确定下来了(参阅《作用域》章节)。
+
+所以即使外部函数的上下文结束了,但内部的函数只要不销毁(被外部引用了,就不会销毁),就会一直引用着刚才上下文的作用域链对象,那么包含在作用域链中的变量也就可以一直被访问到。
+
+
+
+综上所述,闭包其实就是利用到了作用域链的知识。
+
+
+
+把这个理解了,闭包的原理也就明白了。
+
+那么为什么说每一个函数都是一个闭包呢?
+
+因为每一个函数都能通过作用域链访问到全局上下文中的变量,例如:
+
+```js
+var stuName = "zhangsan";
+function test(){
+ console.log(stuName);
+}
+test();
+```
+
+在上面的代码中,我们在 *test* 函数中访问了自由变量 *stuName*,这个被引用的自由变量将和这个函数一同存在。
+
+
+
+## 闭包的优缺点
+
+
+
+**闭包的优点**
+
+先来看看闭包的优点,主要有以下 *2* 点:
+
+- 通过闭包可以让外部环境访问到函数内部的局部变量。
+- 通过闭包可以让局部变量持续保存下来,不随着它的上下文环境一起销毁。
+
+看下面这个例子:
+
+```javascript
+var count = 0; // 全局变量
+function compute() { // 将计数器加 1
+ count++;
+ console.log(count);
+}
+for (var i = 0; i < 100; i++) {
+ compute(); // 循环 100 次
+}
+```
+
+这个例子是对一个全局变量进行加 *1* 的操作,一共加 *100* 次,得到值为 *100* 的结果。
+
+但是因为使用了全局变量,所以存在全局变量污染的问题。
+
+下面用闭包的方式重构它:
+
+```javascript
+function compute() {
+ var count = 0; // 局部变量
+ return function () {
+ count++; // 内部函数访问外部变量
+ console.log(count);
+ }
+}
+var func = compute(); // 引用了内部函数,形成闭包
+for (var i = 0; i < 100; i++) {
+ func();
+}
+// 在外面新创建一个 count 的变量,完全不冲突
+var count = "Hello";
+console.log(count);
+for (var i = 0; i < 100; i++) {
+ func();
+}
+```
+
+这个例子就不再使用全局变量,其中 *count* 这个局部变量依然可以被保存下来。我们甚至可以在外面新创建一个 *count* 变量,完全不会和内部的 *count* 变量产生冲突。
+
+
+
+**闭包的缺点**
+
+说完闭包的优点,接下来来看一下闭包的缺点。
+
+局部变量本来应该在函数退出时被解除引用,但如果局部变量被封闭在闭包形成的环境中,那么这个局部变量就能一直生存下去。也就是说,闭包会将局部变量保存下来。如果大量使用闭包,而其中的变量又未得到清理,闭包的确会使一些数据无法被及时销毁,从而造成内存泄漏。
+
+但是使用闭包的一部分原因,是我们选择主动把一些变量封闭在闭包中,因为可能在以后还需要使用这些变量。
+
+把这些变量放在闭包中和放在全局作用域中,对内存方面的影响是一样的,所以这里并不能说成是内存泄漏。如果在将来需要回收这些变量,我们可以手动把这些变量设置为 *null*。
+
+
+
+如果要说闭包和内存泄漏有关系的地方,那就是使用闭包的同时比较容易形成循环引用,如果闭包的作用域中保存着一些 *DOM* 节点,这个时候就有可能造成内存泄漏。
+
+但这本身并非闭包的问题,也并非 *JavaScript* 的问题。在 *IE* 浏览器中,由于 *BOM* 和 *DOM* 中的对象是使用 *C++* 以 *COM* 对象的方式实现的,而 *COM* 对象的垃圾收集机制采用的是引用计数策略。在基于引用计数策略的垃圾回收机制中,如果两个对象之间形成了循环引用,那么这两个对象都无法被回收,但循环引用造成的内存泄漏在本质上也不是闭包造成的。
+
+同样,如果要解决循环引用带来的内存泄漏问题,我们只需要把循环引用中的变量设为 *null* 即可。将变量设置为 *null* 意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存。
+
+
+
+## 闭包的应用场景
+
+
+
+最后,我们来看一下闭包的一些实际的应用场景。
+
+
+
+```js
+var a = 100;
+setTimeout(function () {
+ console.log(++a);
+}, 1000);
+```
+
+上面是一段很简单的代码,但是这实际上就在你毫无察觉的情况下使用用到了闭包。
+
+在这个例子中,用到了时间函数 *setTimeout*,并在等待 *1* 秒钟后对变量 *a* 进行加 *1* 的操作。
+
+之所以说这是闭包,是因为 *setTimeout* 中的匿名函数对外部变量(自由变量)进行访问,然后该函数又被 *setTimeout* 方法引用。满足了形成闭包的两个条件。所以你看,即使外部上下文结束了,*1* 秒后仍然能对变量 *a* 进行加 *1* 操作。
+
+
+
+在 *DOM* 的事件操作中,也经常用到闭包,比如下面这个例子:
+
+```html
+
+```
+
+```js
+(function(){
+ var cnt = 0; // 计数器
+ var count = document.getElementById("count");
+ count.onclick = function(){
+ console.log(++cnt);
+ }
+})()
+```
+
+*onclick* 指向的函数中访问了外部变量 *cnt*,同时该函数又被 *onclick* 事件引用了,满足 *2* 个条件,是闭包。
+
+所以当外部上下文结束后,你继续点击按钮,在触发的事件处理方法中仍然能访问到变量 cnt。
+
+再比如,*img* 对象经常用于进行数据上报,如下所示:
+
+```js
+const report = function (src) {
+ var img = new Image();
+ img.src = src;
+}
+report('http://xxx.com/getUserInfo');
+```
+
+但是通过查询后台的记录我们得知,因为一些低版本的浏览器的实现存在 *bug*,在这些浏览器下使用 *report* 函数进行数据上报时会丢失 *30%* 左右的数据,也就是说,*report* 函数并不是每一次都成功发起了 *HTTP* 请求。
+
+丢失数据的原因是 *img* 是 *report* 函数中的局部变量,当 *report* 函数在调用结束后,*img* 局部变量随即被销毁,而此时或许还没来得及发出 *HTTP* 请求,所以此次请求就会丢失掉。
+
+现在我们把 *img* 变量用闭包封闭起来,便能解决请求丢失的问题,如下:
+
+```js
+const report = (function () {
+ var imgs = [];
+ return function (src) {
+ var img = new Image();
+ imgs.push(img);
+ img.src = src;
+ }
+})();
+```
+
+
+
+在有些时候,闭包还会引起一些奇怪的问题,比如下面这个例子:
+
+```js
+for (var i = 1; i <= 3; i++) {
+ setTimeout(function () {
+ console.log(i);
+ }, 1000);
+}
+```
+
+我们预期的结果是过 *1* 秒后分别输出 *i* 变量的值为 *1,2,3*。但是,执行的结果是:*4,4,4*。
+
+实际上,问题就出在闭包身上。你看,循环中的 *setTimeout* 访问了它的外部变量 *i*,形成闭包。
+
+而 *i* 变量只有 *1* 个,所以循环 *3* 次的 *setTimeout* 中都访问的是同一个变量。循环到第 *4* 次,*i* 变量增加到 *4*,不满足循环条件,循环结束,代码执行完后上下文结束。但是,那 *3* 个 *setTimeout* 等 *1* 秒钟后才执行,由于闭包的原因,所以它们仍然能访问到变量 *i*,不过此时 *i* 变量值已经是 *4* 了。
+
+既然是闭包引起的问题,那么解决的方法就是去掉闭包。
+
+我们知道形成闭包有两个条件,只要不满足其一,那就不再是闭包。
+
+条件之一,内部函数被外部引用,这个我们没办法去掉。条件二,内部函数访问外部变量。这个条件我们有办法去掉,比如:
+
+```js
+for (var i = 1; i <= 3; i++) {
+ (function (index) {
+ setTimeout(function () {
+ console.log(index);
+ }, 1000);
+ })(i)
+}
+```
+
+这样 *setTimeout* 中就可以不用访问 *for* 循环声明的变量 *i* 了。而是采用调用函数传参的方式把变量 *i* 的值传给了 *setTimeout*,这样它们就不再形成闭包。也就是说 *setTimeout* 中访问的已经不是外部的变量 *i*,所以即使 *i* 的值增长到 *4*,跟它内部也没关系,最后达到了我们想要的效果。
+
+当然,解决这个问题还有个更简单的方法,就是使用 *ES6* 中的 *let* 关键字。
+
+它声明的变量有块作用域,如果将它放在循环中,那么每次循环都会有一个新的变量 *i*,这样即使有闭包也没问题,因为每个闭包保存的都是不同的 *i* 变量,那么刚才的问题也就迎刃而解。
+
+```js
+for (let i = 1; i <= 3; i++) {
+ setTimeout(function () {
+ console.log(i);
+ }, 1000);
+}
+```
+
+
+
+另外,使用闭包还可以模拟出面向对象中的私有方法。
+
+过程与数据的结合是形容面向对象中的“对象”时经常使用的表达。
+
+对象以属性的形式包含了数据,以方法的形式包含了过程。
+
+而闭包则是在过程中以环境的形式包含了数据。通常用面向对象思想能实现的功能,用闭包也能够实现,反之亦然。
+
+在 *JavaScript* 语言的祖先 *Scheme* 语言中,甚至都没有提供面向对象的原生设计,但却可以使用闭包来实现一个完整的面向对象的系统。
+
+下面我们来看看这段跟闭包相关的代码:
+
+```js
+function Test(){
+ var value = 0; // 相当于是对象的属性
+ return {
+ call : function(){
+ value++;
+ console.log(value);
+ }
+ }
+}
+const test = new Test();
+test.call(); // 1
+test.call(); // 2
+test.call(); // 3
+```
+
+如果换成面向对象的写法,那就是如下:
+
+```js
+const test = {
+ value: 0,
+ call: function () {
+ this.value++;
+ console.log(this.value);
+ }
+}
+test.call(); // 1
+test.call(); // 2
+test.call(); // 3
+```
+
+或者
+
+```js
+function Test() {
+ this.value = 0;
+}
+Test.prototype.call = function () {
+ this.value++;
+ console.log(this.value);
+}
+const test = new Test();
+test.call(); // 1
+test.call(); // 2
+test.call(); // 3
+```
+
+
+
+## 真题解答
+
+
+
+- 闭包是什么?闭包的应用场景有哪些?怎么销毁闭包?
+
+> 参考答案:
+>
+> 闭包是指有权访问另外一个函数作用域中的变量的函数。
+>
+> 因为闭包引用着另一个函数的变量,导致另一个函数已经不使用了也无法销毁,所以**闭包使用过多,会占用较多的内存,这也是一个副作用,内存泄漏。**
+>
+> 如果要销毁一个闭包,可以 把被引用的变量设置为 *null*,即手动清除变量,这样下次 *JS* 垃圾回收机制回收时,就会把设为 *null* 的量给回收了。
+>
+> 闭包的应用场景:
+>
+> 1. 匿名自执行函数
+> 2. 结果缓存
+> 3. 封装
+> 4. 实现类和继承
+> 5. 解决全局污染
+
+
+
+-*EOF*-
\ No newline at end of file
diff --git a/11. DOM事件的注册和移除/DOM 事件的注册和移除.html b/11. DOM事件的注册和移除/DOM 事件的注册和移除.html
new file mode 100644
index 0000000..666c1ee
--- /dev/null
+++ b/11. DOM事件的注册和移除/DOM 事件的注册和移除.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+好在两家公司的浏览器开发团队在看待浏览器事件方面还是一致的。
+
+如果单击了某个按钮,他们都认为单击事件不仅仅发生在按钮上,甚至也单击了整个页面。
+
+但有意思的是,*IE* 和 *Netscape* 开发团队居然提出了差不多是完全相反的事件流的概念。
+
+*IE* 的事件流是事件冒泡流,而 *Netscape* 的事件流是事件捕获流。
+
+
+
+### 事件冒泡流
+
+
+
+*IE* 的事件流叫做事件冒泡(*event bubbling*),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。
+
+以下列 *HTML* 结构为例,来说明事件冒泡。如下:
+
+```html
+
+
+
+
+
+
+
+
+### 事件捕获流
+
+
+
+*Netscape Communicator* 团队提出的另一种事件流叫做事件捕获(*event captruing*)。
+
+事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。
+
+事件捕获的思想是在事件到达预定目标之前就捕获它。
+
+以同样的 *HTML* 结构为例来说明事件捕获,如下:
+
+```html
+
+
+
+
+
+
+
+
+### 标准 *DOM* 事件流
+
+
+
+*DOM* 标准采用的是**捕获 + 冒泡**的方式。
+
+两种事件流都会触发 *DOM* 的所有对象,从 *document* 对象开始,也在 *document* 对象结束。
+
+换句话说,起点和终点都是 *document* 对象(很多浏览器可以一直捕获 + 冒泡到 *window* 对象)
+
+*DOM* 事件流示意图:
+
+
+
+
+
+*DOM* 标准规定事件流包括三个阶段:**事件捕获阶段**、**处于目标阶段**和**事件冒泡阶段**。
+
+- **事件捕获阶段:**实际目标 *div* 在捕获阶段不会触发事件。捕获阶段从 *window* 开始,然后到 *document、html*,最后到 *body* 意味着捕获阶段结束。
+
+
+
+- **处于目标阶段:**事件在 *div* 上发生并处理,但是本次事件处理会被看成是冒泡阶段的一部分。
+
+
+
+- **冒泡阶段:**事件又传播回文档。
+
+
+
+## 事件委托
+
+
+
+上面介绍了事件冒泡流,事件冒泡一个最大的好处就是可以实现事件委托。
+
+事件委托,又被称之为事件代理。在 *JavaScript* 中,添加到页面上的事件处理程序数量将直接关系到页面整体的运行性能。导致这一问题的原因是多方面的。
+
+首先,每个函数都是对象,都会占用内存。内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致的 *DOM* 访问次数,会延迟整个页面的交互就绪时间。
+
+对事件处理程序过多问题的解决方案就是事件委托。
+
+事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
+
+例如,*click* 事件会一直冒泡到 *document* 层次。也就是说,我们可以为整个页面指定一个 *onclick* 事件处理程序,而不必给每个可单击的元素分别添加事件处理程序。
+
+举一个具体的例子,例如现在我的列表项有如下内容:
+
+```html
+
+
+
+
+下面是一个递归的示例:
+
+```java
+function neverEnd() {
+ console.log("This is the method that never ends!");
+ neverEnd();
+}
+```
+
+*method* 会先输出 *This is the method that never ends!* 然后再调用自己,导致无限递归(*infinite recursion*)。当然这一般是我们需要避免的状况。
+
+
+
+在进行递归操作的时候,我们需要满足以下几个条件:
+
+- 递归调用必须有结束条件
+- 每次调用的时候都需要根据需求改变传递的参数内容
+
+
+
+下面是递归的一个示例,求某个数的阶乘。
+
+```java
+function factorial(x) {
+ if (x === 1) {
+ return 1;
+ } else {
+ return x * factorial(x - 1);
+ }
+}
+console.log(factorial(5)); // 120
+```
+
+
+
+整个递归的计算过程如下:
+
+```
+===> factorial(5)
+===> 5 * factorial(4)
+===> 5 * (4 * factorial(3))
+===> 5 * (4 * (3 * factorial(2)))
+===> 5 * (4 * (3 * (2 * factorial(1))))
+===> 5 * (4 * (3 * (2 * 1)))
+===> 5 * (4 * (3 * 2))
+===> 5 * (4 * 6)
+===> 5 * 24
+===> 120
+```
+
+
+
+
+
+使用递归时需要注意如下事项:
+
+
+
+- 递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以用循环的方式来实现。
+
+
+
+- 使用递归时需要注意防止栈溢出。在计算机中,函数调用是通过栈(*stack*)这种数据结构实现的,每当一个函数调用,栈就会加一层,每当函数返回,栈就会减一层。由于栈的大小不是无限的,所以递归调用的次数过多,会导致栈溢出。
+
+
+
+下面再来看几个递归的示例:
+
+
+
+示例 *1*:使用递归来计算从 *x* 加到 *y* 的结果
+
+```go
+function calc(i, j) {
+ if (i == j) {
+ return i;
+ }
+ return calc(i, j - 1) + j;
+}
+console.log(calc(1, 100)); // 5050
+```
+
+示例 *2*:使用递归来计算斐波那契数列
+
+```go
+function calc(i) {
+ if (i == 1) {
+ return 1;
+ } else if (i == 2) {
+ return 2;
+ } else {
+ return calc(i - 1) + calc(i - 2);
+ }
+}
+console.log(calc(7)); // 21
+```
+
+
+
+## 真题解答
+
+
+
+- 使用递归完成 *1* 到 *100* 的累加
+
+> 参考答案:
+>
+> ```js
+> function calc(i, j) {
+> if (i == j) {
+> return i;
+> }
+> return calc(i, j - 1) + j;
+> }
+> console.log(calc(1, 100)); // 5050
+> ```
+
+
+
+-*EOF*-
\ No newline at end of file
diff --git a/15. 属性描述符/属性描述符.js b/15. 属性描述符/属性描述符.js
new file mode 100644
index 0000000..c7b49c5
--- /dev/null
+++ b/15. 属性描述符/属性描述符.js
@@ -0,0 +1,154 @@
+// var obj = {};
+// obj.name = "xiejie";
+// obj.age = 18;
+// obj.age = "aaaaa";
+
+
+// console.log(obj.name);
+
+
+// var obj = {};
+// obj.x = 100;
+
+// 接下来我们通过属性描述符的形式来添加属性
+// 属性描述符是一个对象,作为第三个参数传入
+// Object.defineProperty(obj, 'x', {
+// value : 100,
+// writable : false
+// })
+// console.log(obj.x);
+// obj.x = 200;
+// console.log(obj.x);
+
+// 获取某一个对象的属性的属性描述符
+// console.log(Object.getOwnPropertyDescriptor(obj, 'x'));
+
+// var obj = Object.defineProperty({}, 'x', {
+// value : 100,
+// configurable: true // 禁止配置
+// });
+// obj.x = 5; //试图修改其值
+// console.log(obj.x); //修改失败,返回undefined
+// delete obj.x;
+// console.log(obj.x);
+
+// // 包括如果想要重新定义属性描述符也是不可以的
+// Object.defineProperty(obj,'x',{
+// value : 10
+// })
+
+// getter 和 setter 示例
+
+// var obj = Object.create(Object.prototype, {
+// // 私有属性,不对外
+// _x: { //数据属性
+// value: 1, //初始值
+// writable: true
+// },
+// // 对外的,外部可以访问和修改
+// x: { //访问器属性
+// // 通过 getter 和 setter 访问器来访问和设置属性值,可以做一些限制。
+// get: function () { //getter
+// return this._x; //返回_x属性值
+// },
+// set: function (value) { //setter
+// if (typeof value != "number") {
+// throw new Error('请输入数字');
+// }
+// this._x = value; //赋值
+// }
+// }
+// });
+// console.log(obj.x); // 1
+// obj.x = 100;
+// console.log(obj.x); // 100
+
+// obj.x = "2"; //抛出异常
+
+// var obj = {
+// _x: 1, // 定义 _x 属性
+// get x() {
+// return this._x
+// }, //定义 x 属性的 getter
+// set x(value) { //定义 x 属性的 setter
+// if (typeof value != "number") {
+// throw new Error('请输入数字');
+// }
+// this._x = value; // 赋值
+// }
+// };
+// console.log(obj.x); //1
+// obj.x = 2;
+// console.log(obj.x); //2
+
+
+// var obj = {};
+// obj.name = "xiejie";
+// obj.age = 18;
+
+// // console.log(Object.getOwnPropertyNames(obj));
+
+// console.log(obj.propertyIsEnumerable('name'));
+
+// var obj = Object.create(Object.prototype, {
+// _x: { //数据属性
+// value: 1, //初始值
+// writable: true
+// },
+// x: { //访问器属性
+// configurable: true, //允许修改配置
+// get: function () { //getter
+// return this._x; //返回_x属性值
+// },
+// set: function (value) {
+// if (typeof value != "number") {
+// throw new Error('请输入数字');
+// }
+// this._x = value; //赋值
+// }
+// }
+// });
+// var des = Object.getOwnPropertyDescriptor(obj, "x"); //获取属性x的属性描述符
+// console.log(des);
+// des.set = function (value) {
+// //修改属性x的属性描述符set函数
+// //允许非数值型的数字,也可以进行赋值
+// if (typeof value != "number" && isNaN(value * 1)) {
+// throw new Error('请输入数字');
+// }
+// this._x = value;
+// }
+// obj = Object.defineProperty(obj, "x", des);
+// console.log(obj.x); //1
+// obj.x = "2"; //把一个给数值型数字赋值给属性x
+// console.log(obj.x); //2
+
+
+function extend(toObj, fromObj) { //扩展对象
+ for (var property in fromObj) { //遍历对象属性
+ if (!fromObj.hasOwnProperty(property)) continue; //过滤掉继承属性
+ Object.defineProperty( //复制完整的属性信息
+ toObj, //目标对象
+ property, //私有属性
+ Object.getOwnPropertyDescriptor(fromObj, property) //获取属性描述符
+ );
+ }
+ return toObj; //返回目标对象
+}
+
+var obj = {
+ name : "xiejie",
+ age : 18
+}
+
+var obj2 = {};
+Object.defineProperty(obj2,'x',{
+ value : 100,
+ writable : false,
+ enumerable : true
+})
+
+extend(obj,obj2);
+console.log(obj);
+obj.x = 200;
+console.log(obj.x);
\ No newline at end of file
diff --git a/15. 属性描述符/属性描述符.md b/15. 属性描述符/属性描述符.md
new file mode 100644
index 0000000..d8a3f01
--- /dev/null
+++ b/15. 属性描述符/属性描述符.md
@@ -0,0 +1,315 @@
+# 属性描述符
+
+
+
+## 经典真题
+
+
+
+- *JavaScript* 中对象的属性描述符有哪些?分别有什么作用?
+
+
+
+## 属性描述符详解
+
+
+
+在 *JavaScript* 中,对象的属性可以分为两种:
+
+
+
+- 数据属性:它的本质就是一个数据
+
+- 存取器属性:它的本质是一个函数,但是可以将它当作普通属性来使用,当给该属性赋值时,会运行相应的 *setter* 函数,当获取该属性的值时,会运行相应的 *getter* 函数。除了存取器,还有一些其他的关键字,用以表示当前属性是否可写、是否有默认值、是否可枚举等,这些关键字就是属性描述符。
+
+
+
+属性描述符是 *ECMAScript* 5 新增的语法,它其实就是一个内部对象,用来描述对象的属性的特性。
+
+
+
+### 属性描述符的结构
+
+
+
+在定义对象、定义属性时,我们曾经介绍过属性描述符,属性描述符实际上就是一个对象。
+
+属性描述符一共有 *6* 个,可以选择使用。
+
+- *value*:设置属性值,默认值为 *undefined*。
+- *writable*:设置属性值是否可写,默认值为 *true*。
+- *enumerable*:设置属性是否可枚举,即是否允许使用 *for/in* 语句或 *Object.keys( )* 函数遍历访问,默认为 *true*。
+- *configurable*:设置是否可设置属性特性,默认为 *true*。如果为 *false*,将无法删除该属性,不能够修改属性值,也不能修改属性的属性描述符。
+- *get*:取值函数,默认为 *undefined*。
+- *set*:存值函数,默认为 *undefined*。
+
+
+
+注意这几个属性不是都可以一起设置,具体如下图:
+
+
+
+
+
+**示例 1**
+
+下面示例演示了使用 *value* 读写属性值的基本用法。
+
+```js
+var obj = {}; //定义空对象
+Object.defineProperty(obj, 'x', {value : 100}); //添加属性x,值为100
+console.log(Object.getOwnPropertyDescriptor(obj, 'x').value); //返回100
+```
+
+
+
+**示例 2**
+
+下面示例演示了使用 *writable* 属性禁止修改属性 *x*。
+
+```js
+var obj = {};
+Object.defineProperty(obj, 'x', {
+ value : 1, //设置属性默认值为1
+ writable : false //禁止修改属性值
+});
+obj.x = 2; //修改属性x的值
+console.log(obj.x); // 1 说明修改失败
+```
+
+在正常模式下,如果 *writable* 为 *false*,重写属性值不会报错,但是操作失败,而在严格模式下则会抛出异常。
+
+
+
+**示例 3**
+
+*configurable* 可以禁止修改属性描述符,当其值为 *false* 时,*value、writable、enumerable* 和 *configurable* 禁止修改,同时禁止删除属性。
+
+在下面示例中,当设置属性 *x* 禁止修改配置后,下面操作都是不允许的,其中 *obj.x=5;* 若操作失败,则后面 *4* 个操作方法都将抛出异常。
+
+```js
+var obj = Object.defineProperty({}, 'x', {
+ configurable : false // 禁止配置
+});
+obj.x = 5; //试图修改其值
+console.log(obj.x); //修改失败,返回undefined
+Object.defineProperty(obj, 'x', {value : 2}); //抛出异常
+Object.defineProperty(obj, 'x', {writable: true}); //抛出异常
+Object.defineProperty(obj, 'x', {enumerable: true}); //抛出异常
+Object.defineProperty(obj, 'x', {configurable: true}); //抛出异常
+```
+
+当 *configurable* 为 *false* 时,如果把 *writable=true* 改为 *false* 是允许的。只要 *writable* 或 *configurable* 有一个为 *true*,则 *value* 也允许修改。
+
+
+
+### *get* 和 *set* 函数
+
+
+
+除了使用点语法或中括号语法访问属性的 *value* 外,还可以使用访问器,包括 *set* 和 *get* 两个函数。
+
+其中,*set( )* 函数可以设置 *value* 属性值,而 *get( )* 函数可以读取 *value* 属性值。
+
+借助访问器,可以为属性的 *value* 设计高级功能,如禁用部分特性、设计访问条件、利用内部变量或属性进行数据处理等。
+
+
+
+**示例 1**
+
+下面示例设计对象 *obj* 的 *x* 属性值必须为数字。为属性 *x* 定义了 *get* 和 *set* 特性,*obj.x* 取值时,就会调用 *get*;赋值时,就会调用 *set*。
+
+```js
+var obj = Object.create(Object.prototype, {
+ _x : { //数据属性
+ value : 1, //初始值
+ writable : true
+ },
+ x : { //访问器属性
+ get : function () { //getter
+ return this._x; //返回_x属性值
+ },
+ set : function (value) { //setter
+ if (typeof value != "number"){
+ throw new Error('请输入数字');
+ }
+ this._x = value; //赋值
+ }
+ }
+});
+console.log(obj.x); //1
+obj.x = "2"; //抛出异常
+```
+
+
+
+**示例 2**
+
+*JavaScript* 也支持一种简写方法。针对示例 *1*,通过以下方式可以快速定义属性。
+
+```js
+var obj = {
+ _x : 1, // 定义 _x 属性
+ get x() { return this._x }, //定义 x 属性的 getter
+ set x(value) { //定义 x 属性的 setter
+ if (typeof value != "number"){
+ throw new Error('请输入数字');
+ }
+ this._x = value; // 赋值
+ }
+};
+console.log(obj.x); //1
+obj.x = 2;
+console.log(obj.x); //2
+```
+
+取值函数 *get( )* 不能接收参数,存值函数 *set( )* 只能接收一个参数,用于设置属性的值。
+
+
+
+### 操作属性描述符
+
+属性描述符是一个内部对象,无法直接读写,可以通过下面几个函数进行操作。
+
+- *Object.getOwnPropertyDescriptor( )*:可以读出指定对象私有属性的属性描述符。
+- *Object.defineProperty( )*:通过定义属性描述符来定义或修改一个属性,然后返回修改后的描述符。
+- *Object.defineProperties( )*:可以同时定义多个属性描述符。
+- *Object.getOwnPropertyNames( )*:获取对象的所有私有属性。
+- *Object.keys( )*:获取对象的所有本地可枚举的属性。
+- *propertyIsEnumerable( )*:对象实例方法,直接调用,判断指定的属性是否可枚举。
+
+
+
+**示例 1**
+
+在下面示例中,定义 *obj* 的 *x* 属性允许配置特性,然后使用 *Object.getOwnPropertyDescriptor( )* 函数获取对象 *obj* 的 *x* 属性的属性描述符。修改属性描述符的 *set* 函数,重设检测条件,允许非数值型数字赋值。
+
+```js
+var obj = Object.create(Object.prototype, {
+ _x: { //数据属性
+ value: 1, //初始值
+ writable: true
+ },
+ x: { //访问器属性
+ configurable: true, //允许修改配置
+ get: function () { //getter
+ return this._x; //返回_x属性值
+ },
+ set: function (value) {
+ if (typeof value != "number") {
+ throw new Error('请输入数字');
+ }
+ this._x = value; //赋值
+ }
+ }
+});
+var des = Object.getOwnPropertyDescriptor(obj, "x"); //获取属性x的属性描述符
+des.set = function (value) {
+ //修改属性x的属性描述符set函数
+ //允许非数值型的数字,也可以进行赋值
+ if (typeof value != "number" && isNaN(value * 1)) {
+ throw new Error('请输入数字');
+ }
+ this._x = value;
+}
+obj = Object.defineProperty(obj, "x", des);
+console.log(obj.x); //1
+obj.x = "2"; //把一个给数值型数字赋值给属性x
+console.log(obj.x); //2
+```
+
+
+
+**示例 2**
+
+下面示例先定义一个扩展函数,使用它可以把一个对象包含的属性以及丰富的信息复制给另一个对象。
+
+【实现代码】
+
+```js
+function extend (toObj, fromObj) { //扩展对象
+ for (var property in fromObj) { //遍历对象属性
+ if (!fromObj.hasOwnProperty(property)) continue; //过滤掉继承属性
+ Object.defineProperty( //复制完整的属性信息
+ toObj, //目标对象
+ property, //私有属性
+ Object.getOwnPropertyDescriptor(fromObj, property) //获取属性描述符
+ );
+ }
+ return toObj; //返回目标对象
+}
+```
+
+【应用代码】
+
+```js
+var obj = {}; //新建对象
+obj.x = 1; //定义对象属性
+extend(obj, { get y() { return 2} }) //定义读取器对象
+console.log(obj.y); //2
+```
+
+
+
+### 控制对象状态
+
+
+
+*JavaScript* 提供了 *3* 种方法,用来精确控制一个对象的读写状态,防止对象被改变。
+
+- *Object.preventExtensions*:阻止为对象添加新的属性。
+- *Object.seal*:阻止为对象添加新的属性,同时也无法删除旧属性。等价于属性描述符的 *configurable* 属性设为 *false*。注意,该方法不影响修改某个属性的值。
+- *Object.freeze*:阻止为一个对象添加新属性、删除旧属性、修改属性值。
+
+
+同时提供了 *3* 个对应的辅助检查函数,简单说明如下:
+
+- *Object.isExtensible*:检查一个对象是否允许添加新的属性。
+- *Object.isSealed*:检查一个对象是否使用了 *Object.seal* 方法。
+- *Object.isFrozen*:检查一个对象是否使用了 *Object.freeze* 方法。
+
+
+
+**示例**
+
+下面代码分别使用 *Object.preventExtensions、Object.seal* 和 *Object.freeze* 函数控制对象的状态,然后再使用 *Object.isExtensible、Object.isSealed* 和 *Object.isFrozen* 函数检测对象的状态。
+
+```js
+var obj1 = {};
+console.log(Object.isExtensible(obj1)); //true
+Object.preventExtensions(obj1);
+console.log(Object.isExtensible(obj1)); //false
+var obj2 = {};
+console.log(Object.isSealed(obj2)); //true
+Object.seal(obj2);
+console.log(Object.isSealed(obj2)); //false
+var obj3 = {};
+console.log(Object.isFrozen(obj3)); //true
+Object.freeze(obj3);
+console.log(Object.isFrozen(obj3)); //false
+```
+
+
+
+## 真题解答
+
+
+
+- *JavaScript* 中对象的属性描述符有哪些?分别有什么作用?
+
+> 参考答案:
+>
+> 属性描述符一共有 *6* 个,可以选择使用。
+>
+> - *value*:设置属性值,默认值为 *undefined*。
+> - *writable*:设置属性值是否可写,默认值为 *true*。
+> - *enumerable*:设置属性是否可枚举,即是否允许使用 *for/in* 语句或 *Object.keys( )* 函数遍历访问,默认为 *true*。
+> - *configurable*:设置是否可设置属性特性,默认为 *true*。如果为 *false*,将无法删除该属性,不能够修改属性值,也不能修改属性的属性描述符。
+> - *get*:取值函数,默认为 *undefined*。
+> - *set*:存值函数,默认为 *undefined*。
+>
+> 使用属性描述符的时候,*get* 和 *set* 以及 *value* 和 *writable* 这两组是互斥的,设置了 *get* 和 *set* 就不能设置 *value* 和 *writable*,反之设置了 *value* 和 *writable* 也就不可以设置 *get* 和 *set*。
+
+
+
+-*EOF*-
\ No newline at end of file
diff --git a/16. Class和普通构造器的区别/class 和构造函数区别.js b/16. Class和普通构造器的区别/class 和构造函数区别.js
new file mode 100644
index 0000000..df2ae88
--- /dev/null
+++ b/16. Class和普通构造器的区别/class 和构造函数区别.js
@@ -0,0 +1,121 @@
+// // 电脑类
+// // 通过 ES6 的 class 语法来创建一个类
+// class Computer1{
+// // 构造器
+// constructor(name, price){
+// // 实例属性
+// this.name = name;
+// this.price = price;
+// }
+// // 实例方法
+// showPrice(){
+// console.log(`这台${this.name}电脑的价格为${this.price}元。`);
+// }
+// // 静态方法
+// static staticFunc(){
+// console.log("这是 Computer1 类的静态方法");
+// }
+// }
+// // var apple = new Computer("苹果", 15000);
+// // console.log(apple.name); // 苹果
+// // console.log(apple.price); // 15000
+// // apple.showPrice();
+// // Computer.staticFunc();
+
+
+// // 使用 ES5 的构造函数的方法来创建
+// function Computer2(name, price){
+// this.name = name;
+// this.price = price;
+// }
+// Computer2.prototype.showPrice = function(){
+// console.log(`这台${this.name}电脑的价格为${this.price}元。`);
+// }
+// Computer2.staticFunc = function(){
+// console.log("这是 Computer2 类的静态方法");
+// }
+
+
+
+// var apple = new Computer1("苹果", 15000);
+// // new apple.showPrice()
+
+// var huawei = new Computer2("华为", 12000);
+// console.log(new huawei.showPrice());
+
+
+"use strict";
+// 核对 class 类的调用方法,如果是以普通函数的形式调用的,就会抛出错误
+function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+}
+
+// 对原型和静态方法做特殊处理,设置其特性
+function _defineProperties(target, props) {
+
+ // console.log("target:::",target);
+ // console.log("props:::",props);
+ // target::: {}
+ // props::: [ { key: 'showSth', value: [Function: showSth] } ]
+ // target::: [Function: Computer]
+ // props::: [ { key: 'comStruct', value: [Function: comStruct] } ]
+
+ // 遍历原型方法和静态方法
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor)
+ descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+}
+
+// 调用前面的函数,对原型方法和静态方法进行特性设置
+function _createClass(Constructor, protoProps, staticProps) {
+
+ console.log("Constructor:::",Constructor);
+ console.log("protoProps:::",protoProps);
+ console.log("staticProps:::",staticProps);
+ // Constructor::: [Function: Computer]
+ // protoProps::: [ { key: 'showSth', value: [Function: showSth] } ]
+ // staticProps::: [ { key: 'comStruct', value: [Function: comStruct] } ]
+
+ if (protoProps)
+ _defineProperties(Constructor.prototype, protoProps);
+ if (staticProps)
+ _defineProperties(Constructor, staticProps);
+ return Constructor;
+}
+
+var Computer = /*#__PURE__*/function () {
+ // 构造器
+ function Computer(name, price) {
+ // 1. 核对你是如何进行调用的
+ _classCallCheck(this, Computer);
+
+ this.name = name;
+ this.price = price;
+ }
+
+
+ _createClass(Computer, [{
+ key: "showSth",
+ value: function showSth() {
+ console.log("\u8FD9\u662F\u4E00\u53F0".concat(this.name, "\u7535\u8111"));
+ } // 原型方法
+
+ }], [{
+ key: "comStruct",
+ value: function comStruct() {
+ console.log("电脑由显示器,主机,键鼠组成");
+ } // 静态方法
+ }]);
+
+ return Computer;
+}();
+
+
+var apple = new Computer("苹果",15000);
\ No newline at end of file
diff --git a/16. Class和普通构造器的区别/class 和构造函数区别.md b/16. Class和普通构造器的区别/class 和构造函数区别.md
new file mode 100644
index 0000000..50f1d2e
--- /dev/null
+++ b/16. Class和普通构造器的区别/class 和构造函数区别.md
@@ -0,0 +1,578 @@
+# *class* 和构造函数区别
+
+
+
+## 经典真题
+
+
+
+- 根据下面 *ES6* 构造函数的书写方式,要求写出 *ES5* 的
+
+```js
+class Example {
+ constructor(name) {
+ this.name = name;
+ }
+ init() {
+ const fun = () => { console.log(this.name) }
+ fun();
+ }
+}
+const e = new Example('Hello');
+e.init();
+```
+
+
+
+## 回顾 *class* 的写法
+
+
+
+上面的这道面试题,典型的就是考察 *ES6* 中新增的 *class* 和以前构造函数上面的区别是什么,以及如果通过 *ES5* 去模拟的话,具体如何实现。
+
+
+
+那么在此之前,我们就先来回顾一下 *ES6* 中的 *class* 写法。
+
+
+
+代码如下:
+
+```js
+class Computer {
+ // 构造器
+ constructor(name, price) {
+ this.name = name;
+ this.price = price;
+ }
+ // 原型方法
+ showSth() {
+ console.log(`这是一台${this.name}电脑`);
+ }
+ // 静态方法
+ static comStruct() {
+ console.log("电脑由显示器,主机,键鼠组成");
+ }
+}
+```
+
+
+
+上面的代码非常的简单,我们定义了一个名为 Computer 的类,该类存在 *name、price* 这两个实例属性,一个 *showSth* 的原型方法以及一个 *comStruct* 的静态方法。
+
+
+
+我们可以简单的实例化一个对象出来,例如:
+
+```js
+var apple = new Computer("苹果", 15000);
+console.log(apple.name); // 苹果
+console.log(apple.price); // 15000
+apple.showSth(); // 这是一台苹果电脑
+Computer.comStruct(); // 电脑由显示器,主机,键鼠组成
+```
+
+在上面的代码中,我们从 *Computer* 类中实例化出来了一个 *apple* 的实例对象,然后简单访问了该对象的属性和方法。
+
+
+
+## 回顾构造函数的写法
+
+
+
+那么,在 *ES6* 出现之前,我们是如何实现类似于其他语言中的“类”的呢?
+
+没错,我们是通过的构造函数,然后将方法挂在原型上面。例如:
+
+```js
+function Computer(name, price){
+ this.name = name;
+ this.price = price;
+}
+Computer.prototype.showSth = function(){
+ console.log(`这是一台${this.name}电脑`);
+}
+Computer.comStruct = function(){
+ console.log("电脑由显示器,主机,键鼠组成");
+}
+
+var apple = new Computer("苹果", 15000);
+console.log(apple.name); // 苹果
+console.log(apple.price); // 15000
+apple.showSth(); // 这是一台苹果电脑
+Computer.comStruct(); // 电脑由显示器,主机,键鼠组成
+```
+
+上面的代码就是我们经常在 *ES5* 中所书写的代码,通过构造函数来模拟类,实例方法挂在原型上面,静态方法就挂在构造函数上。
+
+仿佛 *ES6* 的 *class* 写法就是上面构造函数写法的一种语法糖,但是事实真的如此么?
+
+
+
+## *class* 和构造函数区别上的细则
+
+
+
+接下来我们来详细比较一下两种写法在细节上面的一些差异。
+
+首先我们书写两个“类”,一个用 *ES5* 的构造函数书写,一个用 *ES6* 的类的写法来书写,如下:
+
+```js
+class Computer1 {
+ // 构造器
+ constructor(name, price) {
+ this.name = name;
+ this.price = price;
+ }
+ // 原型方法
+ showSth() {
+ console.log(`这是一台${this.name}电脑`);
+ }
+ // 静态方法
+ static comStruct() {
+ console.log("电脑由显示器,主机,键鼠组成");
+ }
+}
+
+function Computer2(name, price){
+ this.name = name;
+ this.price = price;
+}
+Computer2.prototype.showSth = function(){
+ console.log(`这是一台${this.name}电脑`);
+}
+Computer2.comStruct = function(){
+ console.log("电脑由显示器,主机,键鼠组成");
+}
+```
+
+
+
+我们知道,构造函数也是函数,既然是函数,那么就可以通过函数调用的形式来调用该函数,例如:
+
+```js
+var i = Computer2();
+console.log(i); // undefined
+```
+
+运行上面的代码,代码不会报错,因为没有使用 *new* 的方式来调用,所以不会生成一个对象,返回值就为 *undefined*。
+
+
+
+但是如果我们这样来调用 *ES6* 书写的类,会直接报错:
+
+```js
+Computer1();
+// TypeError: Class constructor Computer1 cannot be invoked without 'new'
+```
+
+可以看到,*ES6* 所书写的 *class* ,虽然我们认为背后就是构造函数实现的,但是明显是做了特殊处理的,必须通过 *new* 关键字来调用。
+
+
+
+接下来,我们来针对两种写法,各自实例化一个对象,代码如下:
+
+```js
+var apple = new Computer2("苹果", 15000);
+for(var i in apple){
+ console.log(i);
+}
+console.log('-------');
+var huawei = new Computer1("华为", 12000);
+for(var i in huawei){
+ console.log(i);
+}
+```
+
+在上面的代码中, *apple* 对象是 *ES5* 构造函数的形式创建的实例,*huawei* 是 *ES6* 类的形式创建的实例。有了这两个对象后,我们遍历这两个对象的键,结果如下:
+
+```js
+name
+price
+showSth
+-------
+name
+price
+```
+
+可以看到,*ES6* 中的原型方法是不可被枚举的,说明 *ES6* 对此也是做了特殊处理的。
+
+
+
+另外,*ES6* 的 *class* 中的所有代码均处于严格模式之下,这里我们也可以进行一个简单的验证。例如,对两种方式的 *showSth* 原型方法稍作修改,如下:
+
+```js
+class Computer1 {
+ ...
+ // 原型方法
+ showSth(i,i) {
+ console.log(`这是一台${this.name}电脑`);
+ }
+ ...
+}
+function Computer2(name, price){
+ ...
+}
+Computer2.prototype.showSth = function(j,j){
+ i = 10;
+ console.log(`这是一台${this.name}电脑`);
+}
+...
+```
+
+在上面的代码中,我们为各自的 *showSth* 方法添加了重复的形式参数。我们知道,在严格模式中方法书写重复形参是不被允许的。
+
+所以在运行代码时,*ES6* 的 *class* 声明方式会报错,错误信息如下:
+
+```js
+// SyntaxError: Duplicate parameter name not allowed in this context
+```
+
+
+
+还有就是,如果是 *ES6* 形式所声明的类,原型上的方法是不允许通过 *new* 来调用的。
+
+这里我们也可以做一个简单的测试,如下:
+
+```js
+function Computer2(name, price){
+ this.name = name;
+ this.price = price;
+}
+Computer2.prototype.showSth = function(){
+ i = 10;
+ console.log(`这是一台${this.name}电脑`);
+}
+Computer2.comStruct = function(){
+ console.log("电脑由显示器,主机,键鼠组成");
+}
+
+var apple = new Computer2("苹果", 15000);
+var i = new apple.showSth(); // 这是一台undefined电脑
+console.log(i); // {}
+```
+
+在上面的代码中,我们首先实例化了一个 *apple* 对象,在该对象的原型上面拥有一个 *showSth* 的实例方法,然后我们对其进行了 *new* 操作,可以看到返回了一个对象。
+
+
+
+但是如果是 *ES6* 形式所声明的类,上面的做法将不被允许。示例如下:
+
+```js
+class Computer1 {
+ // 构造器
+ constructor(name, price) {
+ this.name = name;
+ this.price = price;
+ }
+ // 原型方法
+ showSth() {
+ console.log(`这是一台${this.name}电脑`);
+ }
+ // 静态方法
+ static comStruct() {
+ console.log("电脑由显示器,主机,键鼠组成");
+ }
+}
+var huawei = new Computer1("华为", 12000);
+var i = new huawei.showSth(); // TypeError: huawei.showSth is not a constructor
+console.log(i);
+```
+
+在上面的代码中,我们企图对 *Computer1* 实例对象 *huawei* 的原型方法 *showSth* 进行 *new* 操作,可以看到,这里报出了 *TypeError*。
+
+
+
+## *Babel* 中具体的实现
+
+
+
+通过上面的各种例子,我们可以知道 *ES6* 中的 *class* 实现并不是我们单纯所想象的就是之前 *ES5* 写构造函数的写法,虽然本质上是构造函数,但是内部是做了各种处理的。
+
+
+
+这里,我们就来使用 *Babel* 对下面的代码进行转义,转义之前的代码如下:
+
+```js
+class Computer {
+ // 构造器
+ constructor(name, price) {
+ this.name = name;
+ this.price = price;
+ }
+ // 原型方法
+ showSth() {
+ console.log(`这是一台${this.name}电脑`);
+ }
+ // 静态方法
+ static comStruct() {
+ console.log("电脑由显示器,主机,键鼠组成");
+ }
+}
+```
+
+转义后的代码如下:
+
+```js
+"use strict";
+function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+}
+
+function _defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor)
+ descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+}
+
+function _createClass(Constructor, protoProps, staticProps) {
+ if (protoProps)
+ _defineProperties(Constructor.prototype, protoProps);
+ if (staticProps)
+ _defineProperties(Constructor, staticProps);
+ return Constructor;
+}
+
+var Computer = /*#__PURE__*/function () {
+ // 构造器
+ function Computer(name, price) {
+ _classCallCheck(this, Computer);
+
+ this.name = name;
+ this.price = price;
+ } // 原型方法
+
+
+ _createClass(Computer, [{
+ key: "showSth",
+ value: function showSth() {
+ console.log("\u8FD9\u662F\u4E00\u53F0".concat(this.name, "\u7535\u8111"));
+ } // 静态方法
+
+ }], [{
+ key: "comStruct",
+ value: function comStruct() {
+ console.log("电脑由显示器,主机,键鼠组成");
+ }
+ }]);
+
+ return Computer;
+}();
+var apple = new Computer("苹果", 15000);
+console.log(apple.name); // 苹果
+console.log(apple.price); // 15000
+apple.showSth(); // 这是一台苹果电脑
+Computer.comStruct(); // 电脑由显示器,主机,键鼠组成
+```
+
+可以看到,果然没有我们想象的那么简单,接下来我们就来一点一点剖析转义的结果。
+
+首先整体来讲分为下面几块:
+
+```js
+"use strict";
+function _classCallCheck(instance, Constructor) { ... }
+
+function _defineProperties(target, props) { ... }
+
+function _createClass(Constructor, protoProps, staticProps) { ... }
+
+var Computer = /*#__PURE__*/function () { ... }();
+```
+
+我们一块一块的来看。
+
+```js
+function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+}
+```
+
+第一个方法叫做 *classCallCheck*,从名字上面我们也可以看出,这个方法是核对构造方法的调用形式的,接收两个参数,一个是实例对象,另一个是构造函数,通过 *instanceof* 来看参数 *instance* 是否是 *Constructor* 的实例,如果不是就抛出错误。
+
+
+
+接下来是 *_defineProperties* 方法,我们对此方法稍作了修改,打印 *target* 和 *props* 的值
+
+```js
+function _defineProperties(target, props) {
+ console.log("target:::",target);
+ console.log("props:::",props);
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor)
+ descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+}
+```
+
+结果如下:
+
+```js
+target::: {}
+props::: [ { key: 'showSth', value: [Function: showSth] } ]
+target::: [Function: Computer]
+props::: [ { key: 'comStruct', value: [Function: comStruct] } ]
+```
+
+可以看出,该方法就是设置对象方法的属性描述符,包含是否可遍历呀,是否可写呀等信息,设置完成后将方法挂在 *target* 对象上面。
+
+
+
+下一个是 *_createClass* 函数,我们仍然将三个参数打印出来
+
+```js
+function _createClass(Constructor, protoProps, staticProps) {
+ console.log("Constructor::",Constructor);
+ console.log("protoProps::",protoProps);
+ console.log("staticProps::",staticProps);
+ if (protoProps)
+ _defineProperties(Constructor.prototype, protoProps);
+ if (staticProps)
+ _defineProperties(Constructor, staticProps);
+ return Constructor;
+}
+```
+
+结果如下:
+
+```js
+Constructor:: [Function: Computer]
+protoProps:: [ { key: 'showSth', value: [Function: showSth] } ]
+staticProps:: [ { key: 'comStruct', value: [Function: comStruct] } ]
+```
+
+可以看出,接收的三个参数一次为构造函数、原型上的方法,静态方法。接下来在该方法里面所做的事情也就非常清晰了。
+
+
+
+最后就是我们的构造函数了:
+
+```js
+var Computer = /*#__PURE__*/function () {
+ // 构造器
+ function Computer(name, price) {
+ // 进行调用确认
+ _classCallCheck(this, Computer);
+ // 添加实例属性
+ this.name = name;
+ this.price = price;
+ } // 原型方法
+
+ // 将实例方法和静态方法添加到构造函数上面
+ _createClass(Computer, [{
+ key: "showSth",
+ value: function showSth() {
+ console.log("\u8FD9\u662F\u4E00\u53F0".concat(this.name, "\u7535\u8111"));
+ } // 静态方法
+
+ }], [{
+ key: "comStruct",
+ value: function comStruct() {
+ console.log("电脑由显示器,主机,键鼠组成");
+ }
+ }]);
+
+ return Computer;
+}();
+```
+
+明白了 *_createClass* 方法的作用后,该方法的代码也就非常的清晰了。
+
+
+
+## 真题解答
+
+
+
+- 根据下面 *ES6* 构造函数的书写方式,要求写出 *ES5* 的
+
+```js
+class Example {
+ constructor(name) {
+ this.name = name;
+ }
+ init() {
+ const fun = () => { console.log(this.name) }
+ fun();
+ }
+}
+const e = new Example('Hello');
+e.init();
+```
+
+> 参考答案:
+>
+> ```js
+> "use strict";
+>
+> function _classCallCheck(instance, Constructor) {
+> if (!(instance instanceof Constructor)) {
+> throw new TypeError("Cannot call a class as a function");
+> }
+> }
+>
+> function _defineProperties(target, props) {
+> for (var i = 0; i < props.length; i++) {
+> var descriptor = props[i];
+> descriptor.enumerable = descriptor.enumerable || false;
+> descriptor.configurable = true;
+> if ("value" in descriptor)
+> descriptor.writable = true;
+> Object.defineProperty(target, descriptor.key, descriptor);
+> }
+> }
+>
+> function _createClass(Constructor, protoProps, staticProps) {
+> if (protoProps)
+> _defineProperties(Constructor.prototype, protoProps);
+> if (staticProps)
+> _defineProperties(Constructor, staticProps);
+> return Constructor;
+> }
+>
+> var Example = /*#__PURE__*/function () {
+> function Example(name) {
+> _classCallCheck(this, Example);
+>
+> this.name = name;
+> }
+>
+> _createClass(Example, [{
+> key: "init",
+> value: function init() {
+> var _this = this;
+>
+> var fun = function fun() {
+> console.log(_this.name);
+> };
+>
+> fun();
+> }
+> }]);
+>
+> return Example;
+> }();
+>
+> var e = new Example('Hello');
+> e.init();
+> ```
+>
+> 这里可以解释出 *_classCallCheck、_defineProperties、_createClass* 这几个方法各自的作用是什么。
+
+
+
+-*EOF*-
+
diff --git a/17. 浮点数精度问题/浮点数精度问题.js b/17. 浮点数精度问题/浮点数精度问题.js
new file mode 100644
index 0000000..4695e27
--- /dev/null
+++ b/17. 浮点数精度问题/浮点数精度问题.js
@@ -0,0 +1,29 @@
+// console.log(0.1 + 0.2 === 0.3);
+// console.log(1.00);
+
+// // 加法
+// console.log(0.1 + 0.2); // 0.30000000000000004
+// console.log(0.7 + 0.1); // 0.7999999999999999
+// console.log(0.2 + 0.4); // 0.6000000000000001
+// console.log(2.22 + 0.1); // 2.3200000000000003
+
+// // 减法
+// console.log(1.5 - 1.2); // 0.30000000000000004
+// console.log(0.3 - 0.2); // 0.09999999999999998
+
+// // 乘法
+// console.log(19.9 * 100); // 1989.9999999999998
+// console.log(19.9 * 10 * 10); // 1990
+// console.log(9.7 * 100); // 969.9999999999999
+// console.log(39.7 * 100); // 3970.0000000000005
+
+// // 除法
+// console.log(0.3 / 0.1); // 2.9999999999999996
+// console.log(0.69 / 10); // 0.06899999999999999
+
+// console.log(parseInt(0.58 * 100, 10));
+
+// console.log((1.335).toFixed(2));
+
+console.log(19571992547450991); // 19571992547450990
+console.log(19571992547450991===19571992547450992); // true
\ No newline at end of file
diff --git a/17. 浮点数精度问题/浮点数精度问题.md b/17. 浮点数精度问题/浮点数精度问题.md
new file mode 100644
index 0000000..0275c21
--- /dev/null
+++ b/17. 浮点数精度问题/浮点数精度问题.md
@@ -0,0 +1,241 @@
+# 浮点数精度问题
+
+
+
+## 经典真题
+
+
+
+- 为什么 *console.log(0.2+0.1==0.3)* 得到的值为 *false*
+
+
+
+## 浮点数精度常见问题
+
+
+
+在 *JavaScript* 中整数和浮点数都属于 *number* 数据类型,所有数字都是以 *64* 位浮点数形式储存,即便整数也是如此。 所以我们在打印 *1.00* 这样的浮点数的结果是 *1* 而非 *1.00* 。
+
+在一些特殊的数值表示中,例如金额,这样看上去有点别扭,但是至少值是正确了。
+
+然而要命的是,当浮点数做数学运算的时候,你经常会发现一些问题,举几个例子:
+
+**场景一**:进行浮点值运算结果的判断
+
+```js
+// 加法
+console.log(0.1 + 0.2); // 0.30000000000000004
+console.log(0.7 + 0.1); // 0.7999999999999999
+console.log(0.2 + 0.4); // 0.6000000000000001
+console.log(2.22 + 0.1); // 2.3200000000000003
+
+// 减法
+console.log(1.5 - 1.2); // 0.30000000000000004
+console.log(0.3 - 0.2); // 0.09999999999999998
+
+// 乘法
+console.log(19.9 * 100); // 1989.9999999999998
+console.log(19.9 * 10 * 10); // 1990
+console.log(9.7 * 100); // 969.9999999999999
+console.log(39.7 * 100); // 3970.0000000000005
+
+// 除法
+console.log(0.3 / 0.1); // 2.9999999999999996
+console.log(0.69 / 10); // 0.06899999999999999
+```
+
+
+
+**场景二**:将小数乘以 *10* 的 *n* 次方取整
+
+比如将钱币的单位,从元转化成分,经常写出来的是 *parseInt(yuan\*100, 10)*
+
+```js
+console.log(parseInt(0.58 * 100, 10)); // 57
+```
+
+
+
+**场景三**:四舍五入保留 *n* 位小数
+
+例如我们会写出 *(number).toFixed(2)*,但是看下面的例子:
+
+```js
+console.log((1.335).toFixed(2)); // 1.33
+```
+
+在上面的例子中,我们得出的结果是 *1.33*,而不是预期结果 *1.34*。
+
+
+
+## 为什么会有这样的问题
+
+
+
+似乎是不可思议。小学生都会算的题目,*JavaScript* 不会?
+
+我们来看看其真正的原因,到底为什么会产生精度丢失的问题呢?
+
+
+
+计算机底层只有 *0* 和 *1*, 所以所有的运算最后实际上都是二进制运算。
+
+十进制整数利用辗转相除的方法可以准确地转换为二进制数,但浮点数呢?
+
+
+
+
+
+
+
+上面的效果,我没有点击搜索按钮,也没有按回车键,只是写了一些搜索的文字而已。
+
+可是如何来实现上面的场景呢?
+
+如果文本框的文字每次被改变(键盘按下事件),我都要把数据发送到服务器,得到搜索结果,这是非常恐怖的!
+
+想想看,我搜索 *“google”* 这样的单词,至少需要按 *6* 次按键,就这一个词,我需要向服务器请求 *6* 次,并让服务器去搜索 *6* 次,但我只需要最后一次的结果就可以了。如果考虑用户按错的情况,发送请求的次数更加恐怖。这样就造成了大量的带宽被占用,浪费了很多资源。
+
+
+
+如何避免这样的问题呢?
+
+
+
+仔细观察,你会发现,真正的搜索行为,并不是每次按键都会触发的,只有当用户停止按键一段事件后才会触发。
+
+于是,为了满足这种类型场景,我们可以开发一个通用的函数,这个函数要满足以下功能:
+
+1. 调用该函数后,不立即做事,而是一段时间后去做事
+2. 如果在等待时间内调用了该函数,重新计时
+
+
+
+这样的功能,就叫做函数防抖,其实就是防止函数短时间内被调用多次。
+
+要完成该函数,需要给予两个条件:
+
+1. 告诉我一段时间后要做什么事(这里应该是一个回调函数,即函数作为参数)
+2. 告诉我要等待多长时间(毫秒)
+
+
+
+下面我们就来封装这么一个函数防抖的通用函数:
+
+```js
+/**
+ * 函数防抖
+ * @param {function} func 一段时间后,要调用的函数
+ * @param {number} wait 等待的时间,单位毫秒
+ */
+function debounce(func, wait) {
+ // 设置变量,记录 setTimeout 得到的 id
+ var timerId = null;
+ return function (...args) {
+ if (timerId) {
+ // 如果有值,说明目前正在等待中,清除它
+ clearTimeout(timerId);
+ }
+ // 重新开始计时
+ timerId = setTimeout(() => {
+ func(...args);
+ }, wait);
+ }
+}
+```
+
+
+
+下面来进行一个测试,测试如下:
+
+```html
+
+```
+
+```js
+var txt = document.getElementById("txt");
+// 调用 debounce 函数来将事件处理函数变为一个防抖函数
+var debounceHandle = debounce(function(event){
+ console.log(event.target.value);
+}, 500)
+txt.onkeyup = (event)=>{
+ debounceHandle(event);
+}
+```
+
+
+
+效果如下:
+
+
+
+
+
+## 函数节流
+
+
+
+函数节流的目的,也是为了防止一个函数短时间内被频繁的触发。
+
+和函数防抖的原理不同,函数节流的核心思想是让连续的函数执行,变为固定时间段间断地执行。
+
+这里做一个形象的的比喻:
+
+前面我们所介绍的函数防抖,是指频繁触发的情况下,只有足够的空闲时间,才执行代码一次。比如生活中的坐公交,就是一定时间内,如果有人陆续刷卡上车,司机就不会开车。只有别人没刷卡了,司机才开车。
+
+而这里我们要介绍的函数节流,指一定时间内函数只执行一次。比如人的眨眼睛,就是一定时间内眨一次。这是函数节流最形象的解释。
+
+
+
+关于节流的实现,有 *2* 种主流的实现方式,一种是**使用时间戳**,一种是**设置定时器**。
+
+
+
+**(1)使用时间戳**
+
+触发事件时,取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 *0*),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。
+
+下面是封装使用时间戳的函数节流的通用函数:
+
+```js
+/**
+ *
+ * @param {要进行节流的函数} func
+ * @param {间隔时间} wait
+ * @returns
+ */
+function throttle(func, wait) {
+ var args; // 存储函数参数
+ var previous = 0; // 一开始的默认时间
+ return function () {
+ var now = new Date(); // 获取最新的时间戳
+ args = arguments; // 获取参数
+ // 进行时间戳的判断,如果超出规定时间,则执行
+ if (now - previous > wait) {
+ func.apply(null, args);
+ previous = now;
+ }
+ }
+}
+```
+
+下面来实际使用测试一下:
+
+```html
+
+```
+
+```js
+var txt = document.getElementById("txt");
+// 调用 throttle 函数来将事件处理函数变为一个节流函数
+var throttleHandle = throttle(function (event) {
+ console.log(event.target.value);
+}, 1000)
+txt.onkeyup = (event) => {
+ throttleHandle(event);
+}
+```
+
+
+
+效果如下:
+
+
+
+
+
+**(2)设置定时器**
+
+第二种方式是设置定时器,触发事件时设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。
+
+下面是封装使用定时器的函数节流的通用函数:
+
+```js
+/**
+ *
+ * @param {要节流执行的函数} func
+ * @param {节流的时间间隔} wait
+ * @returns
+ */
+function throttle(func, wait) {
+ // timeout 存储计时器返回值
+ // args 存储参数
+ var timeout, args;
+ return function () {
+ args = arguments;
+ // 如果 timeout 有值,说明上一次的执行间隔时间还没过
+ if (!timeout) {
+ // 进入此 if 说明时间间隔已经过了
+ // 先执行一次要执行的函数
+ func.apply(null, args)
+ // 然后重新设置时间间隔
+ timeout = setTimeout(function () {
+ timeout = null;
+ }, wait);
+ }
+ }
+}
+```
+
+
+
+## 真题解答
+
+
+
+- 防抖,节流是什么,如何实现 (字节)
+
+> 参考答案:
+>
+> 我们在平时开发的时候,会有很多场景会频繁触发事件,比如说搜索框实时发请求,*onmousemove、resize、onscroll* 等,有些时候,我们并不能或者不想频繁触发事件,这时候就应该用到函数防抖和函数节流。
+>
+> 函数防抖(*debounce*),指的是短时间内多次触发同一事件,只执行最后一次,或者只执行最开始的一次,中间的不执行。
+>
+> 具体实现如下:
+>
+> ```js
+> /**
+> * 函数防抖
+> * @param {function} func 一段时间后,要调用的函数
+> * @param {number} wait 等待的时间,单位毫秒
+> */
+> function debounce(func, wait) {
+> // 设置变量,记录 setTimeout 得到的 id
+> var timerId = null;
+> return function (...args) {
+> if (timerId) {
+> // 如果有值,说明目前正在等待中,清除它
+> clearTimeout(timerId);
+> }
+> // 重新开始计时
+> timerId = setTimeout(() => {
+> func(...args);
+> }, wait);
+> }
+> }
+> ```
+>
+> 函数节流(*throttle*),指连续触发事件但是在 *n* 秒中只执行一次函数。即 *2n* 秒内执行 *2* 次... 。
+>
+> 节流如字面意思,会稀释函数的执行频率。
+>
+> 下面是使用时间戳方式的具体实现:
+>
+> ```js
+> /**
+> *
+> * @param {要进行节流的函数} func
+> * @param {间隔时间} wait
+> * @returns
+> */
+> function throttle(func, wait) {
+> var args; // 存储函数参数
+> var previous = 0; // 一开始的默认时间
+> return function () {
+> var now = new Date(); // 获取最新的时间戳
+> args = arguments; // 获取参数
+> // 进行时间戳的判断,如果超出规定时间,则执行
+> if (now - previous > wait) {
+> func.apply(null, args);
+> previous = now;
+> }
+> }
+> }
+> ```
+
+
+
+-*EOF*-
+
diff --git a/20. 垃圾回收与内存泄漏/垃圾回收与内存泄漏.md b/20. 垃圾回收与内存泄漏/垃圾回收与内存泄漏.md
new file mode 100644
index 0000000..f2acbf3
--- /dev/null
+++ b/20. 垃圾回收与内存泄漏/垃圾回收与内存泄漏.md
@@ -0,0 +1,182 @@
+# 垃圾回收与内存泄漏
+
+
+
+## 经典真题
+
+
+
+- 请介绍一下 *JavaScript* 中的垃圾回收站机制
+
+
+
+## 什么是内存泄露
+
+
+
+程序的运行需要内存。只要程序提出要求,操作系统或者运行时(*runtime*)就必须供给内存。
+
+对于持续运行的服务进程(*daemon*),必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。
+
+也就是说,不再用到的内存,如果没有及时释放,就叫做内存泄漏(*memory leak*)。
+
+
+
+## *JavaScript* 中的垃圾回收
+
+
+
+浏览器的 *Javascript* 具有自动垃圾回收机制(*GC*:*Garbage Collecation*),也就是说,执行环境会负责管理代码执行过程中使用的内存。其原理是:**垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存**。
+
+但是这个过程不是实时的,因为其开销比较大并且 *GC* 时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。
+
+不再使用的变量也就是生命周期结束的变量,当然只可能是局部变量,全局变量的生命周期直至浏览器卸载页面才会结束。局部变量只在函数的执行过程中存在,而在这个过程中会为局部变量在栈或堆上分配相应的空间,以存储它们的值,然后在函数中使用这些变量,直至函数结束,而闭包中由于内部函数的原因,外部函数并不能算是结束。
+
+
+
+下面是一段示例代码:
+
+```js
+function fn1() {
+ var obj = {name: 'zhangsan', age: 10};
+}
+function fn2() {
+ var obj = {name:'zhangsan', age: 10};
+ return obj;
+}
+
+var a = fn1();
+var b = fn2();
+```
+
+在上面的代码中,我们首先声明了两个函数,分别叫做 *fn1* 和 *fn2*。
+
+当 *fn1* 被调用时,进入 *fn1* 的环境,会开辟一块内存存放对象 *{name: 'zhangsan', age: 10}*。而当调用结束后,出了 *fn1* 的环境,那么该块内存会被 *JavaScript* 引擎中的垃圾回收器自动释放;
+
+在 *fn2* 被调用的过程中,返回的对象被全局变量 *b* 所指向,所以该块内存并不会被释放。
+
+这里问题就出现了:到底哪个变量是没有用的?
+
+所以垃圾收集器必须跟踪到底哪个变量没用,对于不再有用的变量打上标记,以备将来收回其内存。
+
+用于标记的无用变量的策略可能因实现而有所区别,通常情况下有两种实现方式:**标记清除**和**引用计数**。
+
+引用计数不太常用,标记清除较为常用。
+
+
+
+## 标记清除
+
+
+
+*JavaScript* 中最常用的垃圾回收方式就是标记清除。
+
+当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。
+
+从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。
+
+而当变量离开环境时,则将其标记为“离开环境”。
+
+```js
+function test(){
+ var a = 10 ; // 被标记 ,进入环境
+ var b = 20 ; // 被标记 ,进入环境
+}
+test(); // 执行完毕 之后 a、b 又被标离开环境,被回收。
+```
+
+垃圾回收器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。
+
+然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记(闭包)。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。
+
+最后,垃圾回收器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。
+
+到目前为止,*IE9+、Firefox、Opera、Chrome、Safari* 的 *JS* 实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。
+
+
+
+## 引用计数
+
+
+
+引用计数的含义是跟踪记录每个值被引用的次数。
+
+当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是 *1*。如果同一个值又被赋给另一个变量,则该值的引用次数加 *1*。
+
+相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减 *1*。当这个值的引用次数变成 *0* 时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。
+
+这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为 *0* 的值所占用的内存。
+
+```js
+function test() {
+ var a = {}; // a 指向对象的引用次数为 1
+ var b = a; // a 指向对象的引用次数加 1,为 2
+ var c = a; // a 指向对象的引用次数再加 1,为 3
+ var b = {}; // a 指向对象的引用次数减 1,为 2
+}
+```
+
+*Netscape Navigator3* 是最早使用引用计数策略的浏览器,但很快它就遇到一个严重的问题:**循环引用**。
+
+循环引用指的是对象 *A* 中包含一个指向对象B的指针,而对象 *B* 中也包含一个指向对象 *A* 的引用。
+
+```js
+function fn() {
+ var a = {};
+ var b = {};
+ a.pro = b;
+ b.pro = a;
+}
+fn();
+```
+
+以上代码 *a* 和 *b* 的引用次数都是 *2*,*fn* 执行完毕后,两个对象都已经离开环境,在标记清除方式下是没有问题的,但是在引用计数策略下,因为 *a* 和 *b* 的引用次数不为 *0*,所以不会被垃圾回收器回收内存,如果 *fn* 函数被大量调用,就会造成内存泄露。在 *IE7* 与 *IE8* 上,内存直线上升。
+
+
+
+## 真题解答
+
+
+
+- 请介绍一下 *JavaScript* 中的垃圾回收站机制
+
+> 参考答案:
+>
+> *JavaScript* 具有自动垃圾回收机制。垃圾收集器会按照固定的时间间隔周期性的执行。
+>
+> *JavaScript* 常见的垃圾回收方式:**标记清除**、**引用计数**方式。
+>
+> 1、标记清除方式:
+>
+> - 工作原理:当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。
+>
+> - 工作流程:
+>
+> - 垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记;
+>
+> - 去掉环境中的变量以及被环境中的变量引用的变量的标记;
+>
+> - 被加上标记的会被视为准备删除的变量;
+>
+> - 垃圾回收器完成内存清理工作,销毁那些带标记的值并回收他们所占用的内存空间。
+>
+> 2、引用计数方式:
+>
+> - 工作原理:跟踪记录每个值被引用的次数。
+>
+> - 工作流程:
+>
+> - 声明了一个变量并将一个引用类型的值赋值给这个变量,这个引用类型值的引用次数就是 *1*;
+>
+> - 同一个值又被赋值给另一个变量,这个引用类型值的引用次数加 *1*;
+>
+> - 当包含这个引用类型值的变量又被赋值成另一个值了,那么这个引用类型值的引用次数减 *1*;
+>
+> - 当引用次数变成 *0* 时,说明没办法访问这个值了;
+>
+> - 当垃圾收集器下一次运行时,它就会释放引用次数是 *0* 的值所占的内存。
+
+
+
+-*EOF*-
+
diff --git a/21. WeakSet和WeakMap/WeakSet 和 WeakMap.js b/21. WeakSet和WeakMap/WeakSet 和 WeakMap.js
new file mode 100644
index 0000000..7b12f7f
--- /dev/null
+++ b/21. WeakSet和WeakMap/WeakSet 和 WeakMap.js
@@ -0,0 +1,101 @@
+// 创建普通对象,添加属性
+// var obj = {}; // ===> new Object();
+// obj.name = "zhangsan";
+
+// var m = new Map();
+// console.log(m);
+// 添加键
+// m.set("name", "xiejie");
+// console.log(m);
+// m.set("name","zhangsan");
+// console.log(m);
+
+// 通过一个二维数组,可以快速的初始化一个 map
+// var arr = [
+// [true,"zhangsan"],
+// [1,18],
+// [function(){},"male"],
+// ];
+// var m2 = new Map(arr);
+// console.log(m2);
+// console.log(m2.size);
+// console.log(m2.get('name'));
+
+// 使用 has 方法查询某个键是否存在
+// console.log(m.has("name"));
+
+// var obj = {
+// name : "zhangsan",
+// age : 18,
+// gender : "male"
+// }
+
+// for(var i in obj){
+// console.log(obj[i]);
+// }
+
+// console.log(m2);
+// for(var i of m2){
+// console.log(i);
+// }
+
+
+// var wm = new WeakMap();
+// wm.set({"name":"xiejie"},2);
+// // console.log(wm);
+// wm.forEach((item)=>{
+// console.log(item);
+// })
+
+// const map = new Map();
+// map.set('name', 'john');
+// map.set('phone', 'iPhone');
+// map.forEach(item=>{
+// console.log(item);
+// })
+// // john
+// // iPhone
+
+
+// set 基本用法
+// var s = new Set();
+// console.log(s);
+// s.add(123);
+// console.log(s);
+
+// set 不允许添加相同的值
+
+// s.add(123);
+// console.log(s);
+
+// var arr = [1,2,3,4,5,6,7];
+// var s = new Set(arr);
+// console.log(s);
+
+// 利用 set 快速来为数组去重
+// var arr = [1,2,2,4,3,3,5,2,1,4,5,2,6]
+// arr = [...new Set(arr)];
+
+// var arr = [1,2,3,4,5,6,7];
+// var s = new Set(arr);
+// console.log(s.delete(1));
+// console.log(s);
+// s.clear();
+// console.log(s);
+
+// 并集
+// var arr1 = [1, 2, 3]
+// var arr2 = [2, 3, 4]
+// var newArr = [...new Set([...arr1, ...arr2])]
+// console.log(newArr);
+
+// 交集
+var arr1 = [1, 2, 3]
+var arr2 = [2, 3, 4]
+var set1 = new Set(arr1)
+var set2 = new Set(arr2)
+var newArr = []
+set1.forEach(item => {
+ set2.has(item) ? newArr.push(item) : ''
+})
+console.log(newArr)
\ No newline at end of file
diff --git a/21. WeakSet和WeakMap/WeakSet 和 WeakMap.md b/21. WeakSet和WeakMap/WeakSet 和 WeakMap.md
new file mode 100644
index 0000000..3bcedc2
--- /dev/null
+++ b/21. WeakSet和WeakMap/WeakSet 和 WeakMap.md
@@ -0,0 +1,552 @@
+# *WeakSet* 和 *WeakMap*
+
+
+
+## 经典真题
+
+
+
+- 是否了解 *WeakMap、WeakSet*(美团 *19* 年)
+
+
+
+## 从对象开始说起
+
+
+
+首先我们从大家都熟悉的对象开始说起。
+
+对于对象的使用,大家其实是非常熟悉的,所以我们这里仅简单的过一遍。
+
+```js
+const algorithm = { site: "leetcode" };
+console.log(algorithm.site); // leetcode
+
+for (const key in algorithm) {
+ console.log(key, algorithm[key]);
+}
+
+// site leetcode
+delete algorithm.site;
+console.log(algorithm.site); // undefined
+```
+
+在上面的代码中,我们有一个 *algorithm* 对象,它的 *key* 和 *value* 是一个字符串类型的值,之后通过点( . )进行值的访问。
+
+另外,*for-in* 循环也很适合在对象中循环。可以使用中括号( [ ] )访问其键对应的值。但是不能使用 *for-of* 循环,因为对象是不可迭代的。
+
+对象的属性可以用 *delete* 关键字来删除。
+
+
+
+好的,我们已经快速讨论了有关对象的一些事项:
+
+- 如何添加属性
+- 如何遍历对象
+- 如何删除属性
+
+
+
+关于对象的讨论暂时就到这儿。
+
+
+
+## *Map*
+
+
+
+*Map* 是 *JavaScript* 中新的集合对象,其功能类似于对象。但是,与常规对象相比,存在一些主要差异。
+
+首先,让我们看一个创建 *Map* 对象的简单示例。
+
+
+
+### 添加属性
+
+
+
+首先,通过 *Map* 构造函数,我们可以创建一个 *Map* 实例对象出来,如下:
+
+```js
+const map = new Map();
+// Map(0) {}
+```
+
+*Map* 有一种特殊的方法可在其中添加称为 *set* 的属性。它有两个参数:键是第一个参数,值是第二个参数。
+
+```js
+map.set('name', 'john');
+// Map(1) {"name" => "john"}
+```
+
+但是,它不允许你在其中添加现有数据。如果 *Map* 对象中已经存在与新数据的键对应的值,则不会添加新数据。
+
+```js
+map.set('phone', 'iPhone');
+// Map(2) {"name" => "john", "phone" => "iPhone"}
+map.set('phone', 'iPhone');
+// Map(2) {"name" => "john", "phone" => "iPhone"}
+```
+
+但是可以用其他值覆盖现有数据。
+
+```js
+map.set('phone', 'Galaxy');
+// Map(2) {"name" => "john", "phone" => "Galaxy"}
+```
+
+二维数组和 *Map* 对象之间可以很方便的相互转换。例如:
+
+```js
+var arr = [
+ [1, 2],
+ [3, 4],
+ [5, 6],
+];
+
+var map = new Map(arr);
+console.log(map); //Map { 1 => 2, 3 => 4, 5 => 6 }
+console.log(Array.from(map)); //[ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ]
+```
+
+
+
+### 获取属性和长度
+
+
+
+可以通过 *get* 方法或者 *Map* 对象某一条属性的值:
+
+```js
+const map = new Map();
+map.set('name', 'john');
+map.set('phone', 'iPhone');
+console.log(map.get('phone')); // iPhone
+```
+
+
+
+可以通过 *has* 方法来查询是否具有某一条属性:
+
+```js
+const map = new Map();
+map.set('name', 'john');
+map.set('phone', 'iPhone');
+console.log(map.has('phone')); // true
+```
+
+
+
+可以通过 *size* 属性获取 *Map* 对象的长度:
+
+```js
+const map = new Map();
+map.set('name', 'john');
+map.set('phone', 'iPhone');
+console.log(map.size); // 2
+```
+
+
+
+### 遍历 *Map* 对象
+
+
+
+*Map* 是一个可迭代的对象,这意味着可以使用 *for-of* 语句将其映射。
+
+*Map* 以数组形式提供数据,要获取键或值则需要解构数组或以索引的方式来进行访问。
+
+```js
+for (const item of map) {
+ console.dir(item);
+}
+// Array(2) ["name", "john"]
+// Array(2) ["phone", "Galaxy"]
+```
+
+
+
+要仅获取键或值,还有一些方法可供使用。
+
+```js
+map.keys();
+// MapIterator {"name", "phone"}
+map.values();
+// MapIterator {"john", "Galaxy"}
+map.entries();
+// MapIterator {"name" => "john", "phone" => "Galaxy"}
+```
+
+
+
+也可以使用 *forEach* 方法,例如:
+
+```js
+const map = new Map();
+map.set('name', 'john');
+map.set('phone', 'iPhone');
+map.forEach(item=>{
+ console.log(item);
+})
+// john
+// iPhone
+```
+
+
+
+可以使用展开操作符( ... )来获取 *Map* 的全部数据,因为展开操作符还可以在幕后与可迭代对象一起工作。
+
+```js
+const simpleSpreadedMap = [...map];
+// [Array(2), Array(2)]
+```
+
+
+
+### 删除属性
+
+
+
+从 *Map* 对象中删除数据也很容易,你所需要做的就是调用 *delete*。
+
+```js
+map.delete('phone');
+// true
+map.delete('fake');
+// false
+```
+
+*delete* 返回布尔值,该布尔值指示 *delete* 函数是否成功删除了数据。如果是,则返回 *true*,否则返回 *false*。
+
+
+
+如果要清空整个 *Map* 对象,可以使用 *clear* 方法,如下:
+
+```js
+const map = new Map();
+map.set('name', 'john');
+map.set('phone', 'iPhone');
+console.log(map); // Map(2) { 'name' => 'john', 'phone' => 'iPhone' }
+map.clear();
+console.log(map); // Map(0) {}
+```
+
+
+
+### *Map* 和 *Object* 的区别
+
+
+
+关于 *Map* 和 *Object* 的区别,可以参阅下表:
+
+
+
+
+
+## *WeakMap*
+
+
+
+*WeakMap* 起源于 *Map*,因此它们彼此非常相似。但是,*WeakMap* 具有很大的不同。
+
+*WeakMap* 的名字是怎么来的呢?
+
+嗯,是因为它与它的引用链接所指向的数据对象的连接或关系没有 *Map* 的连接或关系那么强,所以它是弱的。
+
+那么,这到底是什么意思呢?
+
+
+
+**差异 *1*:*key* 必须是对象**
+
+可以将任何值作为键传入 *Map* 对象,但 *WeakMap* 不同,它只接受一个对象作为键,否则,它将返回一个错误。
+
+```js
+const John = { name: 'John' };
+const weakMap = new WeakMap();
+weakMap.set(John, 'student');
+// WeakMap {{...} => "student"}
+weakMap.set('john', 'student');
+// Uncaught TypeError: Invalid value used as weak map key
+```
+
+
+
+**差异 *2*:并非 *Map* 中的所有方法都支持**
+
+*WeakMap* 可以使用的方法如下:
+
+- *delete*
+- *get*
+- *has*
+- *set*
+
+还有一个最大的不同是 *WeakMap* 不支持迭代对象的方法。
+
+
+
+**差异 *3*:当 *GC* 清理引用时,数据会被删除**
+
+这是和 *Map* 相比最大的不同。
+
+例如:
+
+```js
+let John = { major: "math" };
+
+const map = new Map();
+const weakMap = new WeakMap();
+
+map.set(John, 'John');
+weakMap.set(John, 'John');
+
+John = null;
+/* John 被垃圾收集 */
+```
+
+当 *John* 对象被垃圾回收时,*Map* 对象将保持引用链接,而 *WeakMap* 对象将丢失链接。
+
+所以当你使用 *WeakMap* 时,你应该考虑这个特点。
+
+
+
+## *Set*
+
+
+
+*Set* 也非常类似于 *Map*,但是 *Set* 对于单个值更有用。
+
+
+
+### 添加属性
+
+
+
+使用 *add* 方法可以添加属性。
+
+```js
+const set = new Set();
+
+set.add(1);
+set.add('john');
+set.add(BigInt(10));
+// Set(4) {1, "john", 10n}
+```
+
+与 *Map* 一样,*Set* 也不允许添加相同的值。
+
+```js
+set.add(5);
+// Set(1) {5}
+
+set.add(5);
+// Set(1) {5}
+```
+
+对于原始数据类型(*boolean、number、string、null、undefined*),如果储存相同值则只保存一个,对于引用类型,引用地址完全相同则只会存一个。
+
+- *+0* 与 *-0* 在存储判断唯一性的时候是恒等的,所以不可以重复。
+- *undefined* 和 *undefined* 是恒等的,所以不可以重复。
+- *NaN* 与 *NaN* 是不恒等的,但是在 *Set* 中只能存一个不能重复。
+
+
+
+### 遍历对象
+
+
+
+由于 *Set* 是一个可迭代的对象,因此可以使用 *for-of* 或 *forEach* 语句。
+
+```js
+for (const val of set) {
+ console.dir(val);
+}
+// 1
+// 'John'
+// 10n
+// 5
+
+set.forEach(val => console.dir(val));
+// 1
+// 'John'
+// 10n
+// 5
+```
+
+
+
+### 删除属性
+
+
+
+这一部分和 *Map* 的删除完全一样。如果数据被成功删除,它返回 *true*,否则返回 *false*。
+
+当然也可以使用 clear 方法清空 *Set* 集合。
+
+```js
+set.delete(5);
+// true
+set.delete(function(){});
+// false;
+
+set.clear();
+```
+
+如果你不想将相同的值添加到数组表单中,则 *Set* 可能会非常有用。
+
+```js
+/* With Set */
+const set = new Set();
+set.add(1);
+set.add(2);
+set.add(2);
+set.add(3);
+set.add(3);
+// Set {1, 2, 3}
+
+// Converting to Array
+const arr = [ ...set ];
+// [1, 2, 3]
+
+Object.prototype.toString.call(arr);
+// [object Array]
+
+/* Without Set */
+const hasSameVal = val => ar.some(v === val);
+const ar = [];
+
+if (!hasSameVal(1)) ar.push(1);
+if (!hasSameVal(2)) ar.push(2);
+if (!hasSameVal(3)) ar.push(3);
+```
+
+
+
+### 应用场景
+
+
+
+接下来来看一下 *Set* 常见的应用场景:
+
+```js
+//数组去重
+...new Set([1,1,2,2,3])
+
+//并集
+var arr1 = [1, 2, 3]
+var arr2 = [2, 3, 4]
+var newArr = [...new Set([...arr1, ...arr2])]
+//交集
+var arr1 = [1, 2, 3]
+var arr2 = [2, 3, 4]
+var set1 = new Set(arr1)
+var set2 = new Set(arr2)
+var newArr = []
+set1.forEach(item => {
+ set2.has(item) ? newArr.push(item) : ''
+})
+console.log(newArr)
+//差集
+var arr1 = [1, 2, 3]
+var arr2 = [2, 3, 4]
+var set1 = new Set(arr1)
+var set2 = new Set(arr2)
+var newArr = []
+set1.forEach(item => {
+ set2.has(item) ? '' : newArr.push(item)
+})
+set2.forEach(item => {
+ set1.has(item) ? '' : newArr.push(item)
+})
+console.log(newArr)
+```
+
+
+
+## *WeakSet*
+
+
+
+*WeakSet* 和 *Set* 区别如下:
+
+- *WeakSet* 只能储存对象引用,不能存放值,而 *Set* 对象都可以
+- *WeakSet* 对象中储存的对象值都是被弱引用的,即垃圾回收机制不考虑 *WeakSet* 对该对象的引用,如果没有其他的变量或者属性引用这个对象值,则这个对象将会被垃圾回收掉。(不考虑该对象还存在与 *WeakSet* 中),所以 *WeakSet* 对象里有多少个成员元素,取决于垃圾回收机制有没有运行,运行前后成员个数可能不一致,遍历结束之后,有的成员可能取不到,被垃圾回收了。因此 *ES6* 规定,*WeakSet* 对象是无法被遍历的,也没有办法拿到它包含的所有元素。
+
+
+
+*WeakSet* 能够使用的方法如下:
+
+- *add(value)* 方法:在 *WeakSet* 中添加一个元素。如果添加的元素已存在,则不会进行操作。
+- *delete(value)* 方法:删除元素 *value*
+- *has(value)* 方法:判断 *WeakSet* 对象中是否包含 *value*
+- *clear( )* 方法:清空所有元素
+
+
+
+下面来看一下 *WeakSet* 的代码示例,与 *WeakMap* 一样,*WeakSet* 也将丢失对内部数据的访问链接(如果内部数据已被垃圾收集)。
+
+```js
+let John = { major: "math" };
+
+const set = new Set();
+const weakSet = new WeakSet();
+
+set.add(John);
+// Set {{...}}
+weakSet.add(John);
+// WeakSet {{...}}
+
+John = null;
+/* John 被垃圾收集 */
+```
+
+一旦对象 *John* 被垃圾回收,*WeakSet* 就无法访问其引用 *John* 的数据。而且 *WeakSet* 不支持 *for-of* 或 *forEach*,因为它不可迭代。
+
+
+
+## 比较总结
+
+
+
+- *Map*
+ - 键名唯一不可重复
+ - 类似于集合,键值对的集合,任何值都可以作为一个键或者一个值
+ - 可以遍历,可以转换各种数据格式,方法 *get、set、has、delete*
+- *WeakMap*
+ - 只接受对象为键名,不接受其他类型的值作为键名,键值可以是任意
+ - 键名是拖引用,键名所指向的对象,会被垃圾回收机制回收
+ - 不能遍历,方法 *get、set、has、delete*
+
+- *Set*
+ - 成员唯一,无序且不会重复
+ - 类似于数组集合,键值和键名是一致的(只有键值。没有键名)
+ - 可以遍历,方法有 *add、delete、has*
+- *WeakSet*
+ - 只能存储对应引用,不能存放值
+ - 成员都是弱引用,会被垃圾回收机制回收
+ - 不能遍历,方法有 *add、delete、has*
+
+
+
+## 真题解答
+
+
+
+- 是否了解 *WeakMap、WeakSet*(美团 *19* 年)
+
+> 参考答案:
+>
+> *WeakSet* 对象是一些对象值的集合, 并且其中的每个对象值都只能出现一次。在 *WeakSet* 的集合中是唯一的
+>
+> 它和 *Set* 对象的区别有两点:
+>
+> - 与 *Set* 相比,*WeakSet* 只能是**对象的集合**,而不能是任何类型的任意值。
+> - *WeakSet* 持弱引用:集合中对象的引用为弱引用。 如果没有其他的对 *WeakSet* 中对象的引用,那么这些对象会被当成垃圾回收掉。 这也意味着 *WeakSet* 中没有存储当前对象的列表。 正因为这样,*WeakSet* 是不可枚举的。
+>
+> *WeakMap* 对象也是键值对的集合。它的**键必须是对象类型**,值可以是任意类型。它的键被弱保持,也就是说,当其键所指对象没有其他地方引用的时候,它会被 *GC* 回收掉。*WeakMap* 提供的接口与 *Map* 相同。
+>
+> 与 *Map* 对象不同的是,*WeakMap* 的键是不可枚举的。不提供列出其键的方法。列表是否存在取决于垃圾回收器的状态,是不可预知的。
+
+
+
+-*EOF*-
\ No newline at end of file
diff --git a/22. 深浅拷贝/深浅拷贝.html b/22. 深浅拷贝/深浅拷贝.html
new file mode 100644
index 0000000..def37c3
--- /dev/null
+++ b/22. 深浅拷贝/深浅拷贝.html
@@ -0,0 +1,231 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+## 深拷贝方法
+
+
+
+说完了浅拷贝,接下来我们来看如何实现深拷贝。
+
+总结一下,大致有如下的方式。
+
+
+
+**1. *JSON.parse(JSON.stringify)***
+
+这是一个广为流传的深拷贝方式,用 *JSON.stringify* 将对象转成 *JSON* 字符串,再用 *JSON.parse* 方法把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
+
+示例如下:
+
+```js
+const stu = {
+ name: 'xiejie',
+ age: 18,
+ stuInfo: {
+ No: 1,
+ score: 100
+ }
+}
+const stu2 = JSON.parse(JSON.stringify(stu));
+stu2.name = 'zhangsan';
+stu2.stuInfo.score = 90;
+console.log(stu); // { name: 'xiejie', age: 18, stuInfo: { No: 1, score: 100 } }
+console.log(stu2); // { name: 'zhangsan', age: 18, stuInfo: { No: 1, score: 90 } }
+```
+
+
+
+这种方式看似能够解决问题,但是这种方法也有一个缺点,那就是不能处理函数。
+
+这是因为 *JSON.stringify* 方法是将一个 *javascript* 值(对象或者数组)转换为一个 *JSON* 字符串,而 *JSON* 字符串是不能够接受函数的。同样,正则对象也一样,在 *JSON.parse* 解析时会发生错误。
+
+例如:
+
+```js
+const stu = {
+ name: 'xiejie',
+ age: 18,
+ stuInfo: {
+ No: 1,
+ score: 100,
+ saySth: function () {
+ console.log('我是一个学生');
+ }
+ }
+}
+const stu2 = JSON.parse(JSON.stringify(stu));
+stu2.name = 'zhangsan';
+stu2.stuInfo.score = 90;
+console.log(stu); // { name: 'xiejie', age: 18, stuInfo: { No: 1, score: 100, saySth: [Function: saySth] }}
+console.log(stu2); // { name: 'zhangsan', age: 18, stuInfo: { No: 1, score: 90 } }
+```
+
+可以看到,在原对象中有方法,拷贝之后,新对象中没有方法了。
+
+
+
+**2. *$.extend(deep,target,object1,objectN)***
+
+前面在介绍浅拷贝时提到了 *jQuery* 的这个方法,该方法既能实现浅拷贝,也能实现深拷贝。要实现深拷贝,只需要将第一个参数设置为 *true* 即可。例如:
+
+```js
+
+
+
+
+```
+
+效果:
+
+
+
+
+
+
+
+
+
+**3. 手写递归方法**
+
+最终,还是只有靠我们自己手写递归方法来实现深拷贝。
+
+示例如下:
+
+```js
+function deepClone(target) {
+ var result;
+ // 判断是否是对象类型
+ if (typeof target === 'object') {
+ // 判断是否是数组类型
+ if (Array.isArray(target)) {
+ result = []; // 如果是数组,创建一个空数组
+ // 遍历数组的键
+ for (var i in target) {
+ // 递归调用
+ result.push(deepClone(target[i]))
+ }
+ } else if (target === null) {
+ // 再判断是否是 null
+ // 如果是,直接等于 null
+ result = null;
+ } else if (target.constructor === RegExp) {
+ // 判断是否是正则对象
+ // 如果是,直接赋值拷贝
+ result = target;
+ } else if (target.constructor === Date) {
+ // 判断是否是日期对象
+ // 如果是,直接赋值拷贝
+ result = target;
+ } else {
+ // 则是对象
+ // 创建一个空对象
+ result = {};
+ // 遍历该对象的每一个键
+ for (var i in target) {
+ // 递归调用
+ result[i] = deepClone(target[i]);
+ }
+ }
+ } else {
+ // 表示不是对象类型,则是简单数据类型 直接赋值
+ result = target;
+ }
+ // 返回结果
+ return result;
+}
+```
+
+在上面的代码中,我们封装了一个名为 *deepClone* 的方法,在该方法中,通过递归调用的形式来深度拷贝一个对象。
+
+下面是 *2* 段测试代码:
+
+```js
+// 测试1
+const stu = {
+ name: 'xiejie',
+ age: 18,
+ stuInfo: {
+ No: 1,
+ score: 100,
+ saySth: function () {
+ console.log('我是一个学生');
+ }
+ }
+}
+const stu2 = deepClone(stu)
+stu2.name = 'zhangsan';
+stu2.stuInfo.score = 90;
+console.log(stu); // { name: 'xiejie', age: 18, stuInfo: { No: 1, score: 100, saySth: [Function: saySth] }}
+console.log(stu2); // { name: 'xiejie', age: 18, stuInfo: { No: 1, score: 90, saySth: [Function: saySth] }}
+```
+
+```js
+// 测试2
+var arr1 = [1, true, 'Hello', { name: 'xiejie', age: 18 }];
+var arr2 = deepClone(arr1)
+console.log(arr1); // [ 1, true, 'Hello', { name: 'xiejie', age: 18 } ]
+console.log(arr2); // [ 1, true, 'Hello', { name: 'xiejie', age: 18 } ]
+
+arr2[0] = 2;
+arr2[3].age = 19;
+console.log(arr1); // [ 1, true, 'Hello', { name: 'xiejie', age: 18 } ]
+console.log(arr2); // [ 2, true, 'Hello', { name: 'xiejie', age: 19 } ]
+```
+
+
+
+## 真题解答
+
+
+
+- 深拷贝和浅拷贝的区别?如何实现
+
+> 参考答案:
+>
+> - **浅拷贝**:只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用,我们把这种拷贝叫做浅拷贝(浅复制)
+>
+> 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
+>
+> - **深拷贝**:在堆中重新分配内存,并且把源对象所有属性都进行新建拷贝,以保证深拷贝的对象的引用图不包含任何原有对象或对象图上的任何对象,拷贝后的对象与原来的对象是完全隔离,互不影响。
+>
+> **浅拷贝方法**
+>
+> 1. 直接赋值
+> 2. *Object.assign* 方法:可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。当拷贝的 *object* 只有一层的时候,是深拷贝,但是当拷贝的对象属性值又是一个引用时,换句话说有多层时,就是一个浅拷贝。
+> 3. *ES6* 扩展运算符,当 *object* 只有一层的时候,也是深拷贝。有多层时是浅拷贝。
+> 4. *Array.prototype.concat* 方法
+> 5. *Array.prototype.slice* 方法
+> 6. *jQuery* 中的 *$.extend*:在 *jQuery* 中,*$.extend(deep,target,object1,objectN)* 方法可以进行深浅拷贝。*deep* 如过设为 *true* 为深拷贝,默认是 *false* 浅拷贝。
+>
+> **深拷贝方法**
+>
+> 1. *$.extend(deep,target,object1,objectN)*,将 *deep* 设置为 *true*
+> 2. *JSON.parse(JSON.stringify)*:用 *JSON.stringify* 将对象转成 *JSON* 字符串,再用 *JSON.parse* 方法把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。这种方法虽然可以实现数组或对象深拷贝,但不能处理函数。
+> 3. 手写递归
+>
+> 示例代码如下:
+>
+> ```js
+> function deepCopy(oldObj, newobj) {
+> for (var key in oldObj) {
+> var item = oldObj[key];
+> // 判断是否是对象
+> if (item instanceof Object) {
+> if (item instanceof Function) {
+> newobj[key] = oldObj[key];
+> } else {
+> newobj[key] = {}; //定义一个空的对象来接收拷贝的内容
+> deepCopy(item, newobj[key]); //递归调用
+> }
+>
+> // 判断是否是数组
+> } else if (item instanceof Array) {
+> newobj[key] = []; //定义一个空的数组来接收拷贝的内容
+> deepCopy(item, newobj[key]); //递归调用
+> } else {
+> newobj[key] = oldObj[key];
+> }
+> }
+> }
+> ```
+
+
+
+-*EOF*-
\ No newline at end of file
diff --git a/23. 函数柯里化/函数柯里化.js b/23. 函数柯里化/函数柯里化.js
new file mode 100644
index 0000000..5ed962a
--- /dev/null
+++ b/23. 函数柯里化/函数柯里化.js
@@ -0,0 +1,116 @@
+// 该函数就是接受不了多个参数
+// function add(x, y) {
+// return x + y;
+// }
+// console.log(add(1, 2)); // 3
+// console.log(add(5, 7)); // 12
+
+
+// 接下来我们要将其进行柯里化
+
+// function add(x){
+// return function(y){
+// return x + y;
+// }
+// }
+
+// console.log(add(1)(2));
+
+// 固定参数
+
+// function check(reg, txt) {
+// return reg.test(txt)
+// }
+
+// // 即使是相同的正则表达式,也需要重新传递一次
+// console.log(check(/\d+/g, 'test1')); // true
+// console.log(check(/\d+/g, 'testtest')); // false
+
+// function check(reg){
+// return function(str){
+// return reg.test(str);
+// }
+// }
+
+// var func = check(/\d+/g);
+// console.log(func('test1'));
+// console.log(func('testtest'));
+// console.log(func('abc'));
+
+
+// console.log(check(/[a-z]+/g)('test'));
+
+
+// 封装一个通用的柯里化函数
+
+// function curry() {
+// var fn = arguments[0]; // 拿到要执行的函数
+// var args = Array.prototype.slice.call(arguments, 1);
+// // 接下来,我们就需要判断这个参数是否足够
+// if(args.length === fn.length){
+// // 进入此 if,说明第一次参数就是传够了的
+// // 直接执行 fn 函数
+// return fn.apply(this, args);
+// }
+// // 下面是处理参数不够的情况
+// function _curry(){
+// args.push(...arguments);
+// if(args.length === fn.length){
+// return fn.apply(this, args);
+// }
+// return _curry;
+// }
+// return _curry;
+// }
+
+// // 测试 1
+// function add(a, b, c) {
+// return a + b + c;
+// }
+
+// console.log(curry(add)(1)(2)(3)); // 6
+// console.log(curry(add, 1)(2)(3)); // 6
+// console.log(curry(add, 1, 2, 3)); // 6
+// console.log(curry(add, 1)(3, 4)); // 8
+
+// var addCurrying = curry(add)(2);
+// console.log(addCurrying(7)(8)); // 17
+
+// // 测试 2
+// function check(reg, txt) {
+// return reg.test(txt)
+// }
+// var hasNumber = curry(check)(/\d+/g);
+// console.log(hasNumber('test1'));// true
+
+
+// 一道面试题
+
+// add(1)(2)(3) = 6;
+// add(1, 2, 3)(4) = 10;
+// add(1)(2)(3)(4)(5) = 15;
+
+
+function add(){
+ // 拿到第一次调用的所有的参数
+ var args = Array.prototype.slice.call(arguments);
+
+ // 该函数会被返回,该函数的作用是继续收集参数
+ function _adder(){
+ args.push(...arguments);
+ return _adder;
+ }
+
+ // 当调用 toString 方法的时候,说明我不要再接收参数了
+ // 执行计算操作
+ _adder.toString = function(){
+ return args.reduce((a,b)=>a+b);
+ }
+
+ return _adder;
+}
+
+console.log(add(1)(2)(3).toString())
+console.log(add(1, 2, 3)(4).toString())
+console.log(add(1)(2)(3,4,5)(6,7).toString())
+
diff --git a/23. 函数柯里化/函数柯里化.md b/23. 函数柯里化/函数柯里化.md
new file mode 100644
index 0000000..f57ebe4
--- /dev/null
+++ b/23. 函数柯里化/函数柯里化.md
@@ -0,0 +1,318 @@
+# 函数柯里化
+
+
+
+## 经典真题
+
+
+
+- 什么是函数柯里化?
+
+
+
+## 什么是函数柯里化
+
+
+
+在计算机科学中,柯里化(英语:*Currying*),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
+
+这个技术由克里斯托弗·斯特雷奇以逻辑学家哈斯凯尔·加里命名的,尽管它是 *Moses Schönfinkel* 和戈特洛布·弗雷格发明的。
+
+
+
+在直觉上,柯里化声称如果你固定某些参数,你将得到接受余下参数的一个函数。
+
+我们姑且叫它返回函数,在调用返回函数的时候,它将判断当前的参数和之前被柯里化函数固定的参数拼起来之后,是否达到了原本函数的参数个数。
+
+如果是,则执行原本的函数,得到结果;如果没有达到,则要继续调用柯里化函数来固定目前的参数。
+
+
+
+在理论计算机科学中,柯里化提供了在简单的理论模型中,比如:只接受一个单一参数的 *lambda* 演算中,研究带有多个参数的函数的方式。
+
+函数柯里化的对偶是*Uncurrying*,一种使用匿名单参数函数来实现多参数函数的方法。
+
+
+
+## 柯里化快速入门
+
+
+
+接下来,我们来通过一个简单的示例,让大家快速体会函数柯里化。
+
+假设我们有一个求取两个数之和的函数:
+
+```js
+function add(x, y) {
+ return x + y;
+}
+console.log(add(1, 2)); // 3
+console.log(add(5, 7)); // 12
+```
+
+在上面的示例中,我们有一个 *add* 函数,接收两个形参,返回两形参的和。
+
+在调用的时候,我们每次也需要传递两个参数。
+
+
+
+现在,我们对其进行柯里化,如下:
+
+```js
+function add(x) {
+ return function (y) {
+ return x + y;
+ }
+}
+console.log(add(1)(2)); // 3
+console.log(add(5)(7)); // 3
+```
+
+在上面的代码中,我们对 *add* 函数进行了柯里化改造,只接受一个参数,但是返回的也不是值了,而是返回一个函数,这个函数也接收一个参数,然后利用闭包的特性,可以访问到最开始传入的 *x* 的值,最终返回 *x* 和 *y* 的和。
+
+
+
+所以,通过上面的这个示例,我们能够体会到前面所说的柯里化函数的特点:
+
+一个柯里化的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
+
+
+
+## 函数柯里化实际应用
+
+
+
+通过上面的例子,我们体验到了什么是柯里化函数。
+
+但是问题来了,费这么大劲封装一层,到底有什么用处呢?
+
+没有好处想让我们程序员多干事情是不可能滴,这辈子都不可能。
+
+
+
+所以接下来我们就来看一下函数柯里化的一个实际应用。
+
+
+
+**参数复用**
+
+就是将相同的参数固定下来。
+
+```js
+// 正常正则验证字符串 reg.test(txt)
+
+// 函数封装后
+function check(reg, txt) {
+ return reg.test(txt)
+}
+
+// 即使是相同的正则表达式,也需要重新传递一次
+console.log(check(/\d+/g, 'test1')); // true
+console.log(check(/\d+/g, 'testtest')); // false
+console.log(check(/[a-z]+/g, 'test')); // true
+
+// Currying后
+function curryingCheck(reg) {
+ return function (txt) {
+ return reg.test(txt)
+ }
+}
+
+// 正则表达式通过闭包保存了起来
+var hasNumber = curryingCheck(/\d+/g)
+var hasLetter = curryingCheck(/[a-z]+/g)
+
+console.log(hasNumber('test1')); // true
+console.log(hasNumber('testtest')); // false
+console.log(hasLetter('21212')); // false
+```
+
+上面的示例是一个正则的校验,正常来说直接调用 *check* 函数就可以了,但是如果我有很多地方都要校验是否有数字,其实就是需要将第一个参数 *reg* 进行复用,这样别的地方就能够直接调用 *hasNumber、hasLetter* 等函数,让参数能够复用,调用起来也更方便。
+
+
+
+**提前确认**
+
+```js
+/**
+ *
+ * @param {要绑定事件的 DOM 元素} element
+ * @param {绑定什么事件} event
+ * @param {事件处理函数} handler
+ */
+var on = function (element, event, handler) {
+ if (document.addEventListener) {
+ if (element && event && handler) {
+ element.addEventListener(event, handler, false);
+ }
+ } else {
+ if (element && event && handler) {
+ element.attachEvent('on' + event, handler);
+ }
+ }
+}
+
+on(div, 'click', function(){})
+
+
+var on = (function () {
+ if (document.addEventListener) {
+ return function (element, event, handler) {
+ if (element && event && handler) {
+ element.addEventListener(event, handler, false);
+ }
+ };
+ } else {
+ return function (element, event, handler) {
+ if (element && event && handler) {
+ element.attachEvent('on' + event, handler);
+ }
+ };
+ }
+})();
+
+on(div, 'click', function(){})
+
+//换一种写法可能比较好理解一点,上面就是把 isSupport 这个参数给先确定下来了
+var on = function (isSupport, element, event, handler) {
+ isSupport = isSupport || document.addEventListener;
+ if (isSupport) {
+ return element.addEventListener(event, handler, false);
+ } else {
+ return element.attachEvent('on' + event, handler);
+ }
+}
+on(true, div, 'click', function(){})
+on(true, div, 'click', function(){})
+on(true, div, 'click', function(){})
+```
+
+我们在做项目的过程中,封装一些 *DOM* 操作可以说再常见不过,上面第一种写法也是比较常见,但是我们看看第二种写法,它相对于第一种写法就是自执行然后返回一个新的函数,这样其实就是提前确定了会走哪一个方法,避免每次都进行判断。
+
+
+
+## 封装通用柯里化函数
+
+
+
+接下来我们来封装一个通用的柯里化函数。
+
+```js
+function curry() {
+ var fn = arguments[0]; // 获取要执行的函数
+ var args = [].slice.call(arguments, 1); // 获取传递的参数,构成一个参数数组
+ // 如果传递的参数已经等于执行函数所需的参数数量
+ if (args.length === fn.length) {
+ return fn.apply(this, args)
+ }
+ // 参数不够向外界返回的函数
+ function _curry(){
+ // 推入之前判断
+ // 将新接收到的参数推入到参数数组中
+ args.push(...arguments);
+ if(args.length === fn.length){
+ return fn.apply(this, args)
+ }
+ return _curry;
+ }
+ return _curry;
+}
+```
+
+对上面的代码进行测试:
+
+```js
+// 测试 1
+function add(a, b, c) {
+ return a + b + c;
+}
+
+console.log(curry(add)(1)(2)(3)); // 6
+console.log(curry(add, 1)(2)(3)); // 6
+console.log(curry(add, 1, 2, 3)); // 6
+console.log(curry(add, 1)(3, 4)); // 8
+
+var addCurrying = curry(add)(2);
+console.log(addCurrying(7)(8)); // 17
+
+// 测试 2
+function check(reg, txt) {
+ return reg.test(txt)
+}
+var hasNumber = curry(check)(/\d+/g);
+console.log(hasNumber('test1'));// true
+```
+
+
+
+## 一道经典的柯里化面试题
+
+
+
+实现一个 *add* 方法,使计算结果能够满足如下预期:
+
+```js
+add(1)(2)(3) = 6;
+add(1, 2, 3)(4) = 10;
+add(1)(2)(3)(4)(5) = 15;
+```
+
+
+
+要完成上面的需求,我们就可以使用柯里化函数:
+
+```js
+function add() {
+ // 第一次执行时,定义一个数组专门用来存储所有的参数
+ var _args = Array.prototype.slice.call(arguments);
+
+ // 在内部声明一个函数,利用闭包的特性保存 _args 并收集所有的参数值
+ var _adder = function () {
+ _args.push(...arguments);
+ return _adder;
+ };
+
+ // 这个是最后输出的时候被调用的,return 后面如果是函数体,
+ // 为了输出函数体字符串会自动调用 toString 方法
+ // 利用 toString 隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
+ _adder.toString = function () {
+ return _args.reduce(function (a, b) {
+ return a + b;
+ });
+ }
+
+ // 这个 return 是第一次调用的时候返回上面的函数体,
+ // 这样后面所有的括号再执行的时候就是执行 _adder 函数体
+ return _adder;
+}
+console.log(add(1)(2)(3).toString()); // 6
+console.log(add(1, 2, 3)(4).toString()); // 10
+console.log(add(1)(2)(3)(4)(5).toString()); // 15
+console.log(add(2, 6)(1).toString()); // 9
+```
+
+
+
+## 真题详解
+
+
+
+- 什么是函数柯里化?
+
+> 参考答案:
+>
+> 柯里化(*currying*)又称部分求值。一个柯里化的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
+>
+> 举个例子,就是把原本:
+>
+> *function(arg1,arg2)* 变成 *function(arg1)(arg2)*
+> *function(arg1,arg2,arg3)* 变成 *function(arg1)(arg2)(arg3)*
+> *function(arg1,arg2,arg3,arg4)* 变成 *function(arg1)(arg2)(arg3)(arg4)*
+>
+> 总而言之,就是将:
+>
+> *function(arg1,arg2,…,argn)* 变成 *function(arg1)(arg2)…(argn)*
+
+
+
+-*EOF*-
\ No newline at end of file
diff --git a/24. Node的事件循环/Node的事件循环.html b/24. Node的事件循环/Node的事件循环.html
new file mode 100644
index 0000000..72fa70d
--- /dev/null
+++ b/24. Node的事件循环/Node的事件循环.html
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+- 进程好比图中的工厂,有单独的专属自己的工厂资源。
+
+- 线程好比图中的工人,多个工人在一个工厂中协作工作,工厂与工人是 *1:n* 的关系。也就是说**一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线**。
+
+- 工厂的空间是工人们共享的,这象征**一个进程的内存空间是共享的,每个线程都可用这些共享内存**。
+
+- 多个工厂之间独立存在。
+
+
+
+接下来我们回过头来看多进程和多线程的概念:
+
+- 多进程:在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态。多进程带来的好处是明显的,比如你可以听歌的同时,打开编辑器敲代码,编辑器和听歌软件的进程之间丝毫不会相互干扰。
+
+
+
+- 多线程:程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
+
+
+
+以 *Chrome* 浏览器中为例,当你打开一个 *Tab* 页时,其实就是创建了一个进程。
+
+
+
+
+
+
+
+一个进程中可以有多个线程,比如渲染线程、*JS* 引擎线程、*HTTP* 请求线程等等。当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。
+
+
+
+### 浏览器内核
+
+
+
+简单来说浏览器内核是通过取得页面内容、整理信息(应用 *CSS* )、计算和组合最终输出可视化的图像结果,通常也被称为渲染引擎。
+
+浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:
+
+- *GUI* 渲染线程
+- *JavaScript* 引擎线程
+- 定时触发器线程
+- 事件触发线程
+- 异步 *http* 请求线程
+
+
+
+#### *GUI* 渲染线程
+
+
+
+- 主要负责页面的渲染,解析 *HTML*、*CSS*,构建 *DOM* 树,布局和绘制等。
+- 当界面需要重绘或者由于某种操作引发回流时,将执行该线程。
+- 该线程与 *JS* 引擎线程互斥,当执行 *JS* 引擎线程时,*GUI* 渲染会被挂起,当任务队列空闲时,主线程才会去执行 *GUI* 渲染。
+
+
+
+#### *JavaScript* 引擎线程
+
+
+
+- 该线程当然是主要负责处理 *JavaScript* 脚本,执行代码。
+- 也是主要负责执行准备好待执行的事件,即定时器计数结束,或者异步请求成功并正确返回时,将依次进入任务队列,等待 *JS* 引擎线程的执行。
+- 当然,该线程与 *GUI* 渲染线程互斥,当 *JS* 引擎线程执行 *JavaScript* 脚本时间过长,将导致页面渲染的阻塞。
+
+
+
+#### 定时触发器线程
+
+
+
+- 负责执行异步定时器一类的函数的线程,如:*setTimeout、setInterval*。
+- 主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理,当计数完毕后,事件触发线程会将计数完毕后的事件加入到任务队列的尾部,等待 *JS* 引擎线程执行。
+
+
+
+#### 事件触发线程
+
+
+
+- 主要负责将准备好的事件交给 *JS* 引擎线程执行。
+
+
+
+比如 *setTimeout* 定时器计数结束, *ajax* 等异步请求成功并触发回调函数,或者用户触发点击事件时,该线程会将整装待发的事件依次加入到任务队列的队尾,等待 *JS* 引擎线程的执行。
+
+
+
+#### 异步 *http* 请求线程
+
+
+
+- 负责执行异步请求一类的函数的线程,如:*Promise、fetch、ajax* 等。
+- 主线程依次执行代码时,遇到异步请求,会将函数交给该线程处理,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待 *JS* 引擎线程执行。
+
+
+
+### 浏览器中的事件循环
+
+
+
+#### 宏任务和微任务
+
+
+
+事件循环中的异步队列有两种:宏任务( *macro* )队列和微任务( *micro* )队列。
+
+**宏任务队列有一个,微任务队列只有一个**。
+
+- 常见的宏任务有:*setTimeout、setInterval、requestAnimationFrame、script*等。
+- 常见的微任务有:*new Promise( ).then(回调)、MutationObserver* 等。
+
+
+
+#### 事件循环流程
+
+
+
+一个完整的事件循环过程,可以概括为以下阶段:
+
+
+
+
+
+- 一开始执行栈空,我们可以把**执行栈认为是一个存储函数调用的栈结构,遵循先进后出的原则**。微任务队列空,宏任务队列里有且只有一个 *script* 脚本(整体代码)。
+
+
+
+- 全局上下文( *script* 标签)被推入执行栈,同步代码执行。在执行的过程中,会判断是同步任务还是异步任务,通过对一些接口的调用,可以产生新的宏任务与微任务,它们会分别被推入各自的任务队列里。同步代码执行完了,*script* 脚本会被移出宏任务队列,这个过程本质上是队列的宏任务的执行和出队的过程。
+
+
+
+- 上一步我们出队的是一个宏任务,这一步我们处理的是微任务。但需要注意的是:当一个宏任务执行完毕后,会执行所有的微任务,也就是将整个微任务队列清空。
+
+
+
+- 执行渲染操作,更新界面
+
+
+
+- 检查是否存在 *Web worker* 任务,如果有,则对其进行处理
+
+
+
+- 上述过程循环往复,直到两个队列都清空
+
+
+
+宏任务和微任务的执行流程,总结起来就是:
+
+**当某个宏任务执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务,依次类推。**
+
+执行流程如下图所示:
+
+
+
+
+
+
+
+这里我们可以来看两道具体的代码题目加深理解:
+
+```js
+console.log('script start');
+setTimeout(function() {
+ console.log('setTimeout');
+}, 0);
+
+Promise.resolve().then(function() {
+ console.log('promise1');
+}).then(function() {
+ console.log('promise2');
+});
+
+console.log('script end');
+```
+
+上面的代码输出的结果为:
+
+```js
+script start
+script end
+promise1
+promise2
+setTimeout
+```
+
+原因很简单,首先会执行同步的任务,输出 *script start* 以及 *script end*。接下来是处理异步任务,异步任务分为宏任务队列和微任务队列,在执行宏任务队列中的每个宏任务之前先把微任务清空一遍,由于 *promise* 是微任务,所以会先被执行,而 *setTimeout* 由于是一个宏任务,会在微任务队列被清空后再执行。
+
+
+
+```js
+Promise.resolve().then(()=>{
+ console.log('Promise1')
+ setTimeout(()=>{
+ console.log('setTimeout2')
+ },0)
+})
+setTimeout(()=>{
+ console.log('setTimeout1')
+ Promise.resolve().then(()=>{
+ console.log('Promise2')
+ })
+},0)
+```
+
+上面的代码输出的结果为:
+
+```js
+Promise1
+setTimeout1
+Promise2
+setTimeout2
+```
+
+一开始执行栈的同步任务(这属于宏任务)执行完毕,会去查看是否有微任务队列,上题中存在(有且只有一个),然后执行微任务队列中的所有任务输出 *Promise1*,同时会生成一个宏任务 *setTimeout2*。
+
+然后去查看宏任务队列,宏任务 *setTimeout1* 在 *setTimeout2* 之前,先执行宏任务 *setTimeout1*,输出 *setTimeout1*。在执行宏任务 *setTimeout1* 时会生成微任务 *Promise2* ,放入微任务队列中,接着先去清空微任务队列中的所有任务,输出 *Promise2*。
+
+清空完微任务队列中的所有任务后,就又会去宏任务队列取一个,这回执行的是 *setTimeout2*。
+
+
+
+### *Node.js* 中的事件循环
+
+
+
+#### *Node.js* 事件循环介绍
+
+
+
+*Node.js* 中的事件循环和浏览器中的是完全不相同的东西。
+
+*Node.js* 采用 *V8* 作为 *JS* 的解析引擎,而 *I/O* 处理方面使用了自己设计的 *libuv*,*libuv* 是一个基于事件驱动的跨平台抽象层,封装了不同操作系统一些底层特性,对外提供统一的 *API*,事件循环机制也是它里面的实现。
+
+
+
+
+
+可以看出 *Node.JS* 的事件循环比浏览器端复杂很多。*Node.js* 的运行机制如下:
+
+- *V8* 引擎解析 *JavaScript* 脚本。
+- 解析后的代码,调用 *Node API*。
+- *libuv* 库负责 *Node API* 的执行。它将不同的任务分配给不同的线程,形成一个事件循环,以异步的方式将任务的执行结果返回给 *V8* 引擎。
+- *V8* 引擎再将结果返回给用户。
+
+
+
+整个架构图如下所示:
+
+
+
+
+
+
+
+#### 事件循环的 *6* 个阶段
+
+
+
+其中 *libuv* 引擎中的事件循环分为 *6* 个阶段,它们会按照顺序反复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段。
+
+
+
+
+
+从上图中,大致看出 *Node.js* 中的事件循环的顺序:
+
+外部输入数据 –-> 轮询阶段( *poll* )-–> 检查阶段( *check* )-–> 关闭事件回调阶段( *close callback* )–-> 定时器检测阶段( *timer* )–-> *I/O* 事件回调阶段( *I/O callbacks* )-–>闲置阶段( *idle、prepare* )–->轮询阶段(按照该顺序反复运行)...
+
+以上 *6* 个阶段所做的事情如下:
+
+- *timers* 阶段:这个阶段执行 *timer*( *setTimeout、setInterval* )的回调
+- *I/O callbacks* 阶段:处理一些上一轮循环中的少数未执行的 *I/O* 回调
+- *idle、prepare* 阶段:仅 *Node.js* 内部使用
+- *poll* 阶段:获取新的 *I/O* 事件, 适当的条件下 *Node.js* 将阻塞在这里
+- *check* 阶段:执行 *setImmediate( )* 的回调
+- *close callbacks* 阶段:执行 *socket* 的 *close* 事件回调
+
+注意:**上面六个阶段都不包括 *process.nextTick( )***
+
+接下去我们详细介绍 *timers、poll、check* 这 *3* 个阶段,因为日常开发中的绝大部分异步任务都是在这 *3* 个阶段处理的。
+
+
+
+***timer* 阶段**
+
+
+
+*timers* 阶段会执行 *setTimeout* 和 *setInterval* 回调,并且是由 *poll* 阶段控制的。同样,**在 *Node.js* 中定时器指定的时间也不是准确时间,只能是尽快执行**。
+
+
+
+***poll* 阶段**
+
+
+
+*poll* 是一个至关重要的阶段,这一阶段中,系统会做两件事情:
+
+- 回到 *timer* 阶段执行回调
+- 执行 *I/O* 回调
+
+并且在进入该阶段时如果没有设定了 *timer* 的话,会发生以下两件事情:
+
+- 如果 *poll* 队列不为空,会遍历回调队列并同步执行,直到队列为空或者达到系统限制
+- 如果 *poll* 队列为空时,会有两件事发生:
+ - 如果有 *setImmediate* 回调需要执行,*poll* 阶段会停止并且进入到 *check* 阶段执行回调
+ - 如果没有 *setImmediate* 回调需要执行,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去
+
+当然设定了 *timer* 的话且 *poll* 队列为空,则会判断是否有 *timer* 超时,如果有的话会回到 *timer* 阶段执行回调。
+
+假设 *poll* 被堵塞,那么即使 *timer* 已经到时间了也只能等着,这也是为什么上面说定时器指定的时间并不是准确的时间。例如:
+
+```js
+const start = Date.now();
+setTimeout(function f1() {
+ console.log("setTimeout", Date.now() - start);
+}, 200);
+
+const fs = require('fs');
+
+fs.readFile('./index.js', 'utf-8', function f2() {
+ console.log('readFile');
+ const start = Date.now();
+ // 强行延时 500 毫秒
+ while (Date.now() - start < 500) { }
+})
+```
+
+
+
+***check* 阶段**
+
+
+
+*setImmediate( )* 的回调会被加入 *check* 队列中,从事件循环的阶段图可以知道,*check* 阶段的执行顺序在 *poll* 阶段之后。
+
+我们先来看个例子:
+
+```js
+console.log('start')
+setTimeout(() => {
+ console.log('timer1')
+ Promise.resolve().then(function() {
+ console.log('promise1')
+ })
+}, 0)
+setTimeout(() => {
+ console.log('timer2')
+ Promise.resolve().then(function() {
+ console.log('promise2')
+ })
+}, 0)
+Promise.resolve().then(function() {
+ console.log('promise3')
+})
+console.log('end')
+// 输出结果:start => end => promise3 => timer1 => promise1 => timer2 => promise2
+```
+
+一开始执行同步任务,依次打印出 *start end*,并将 *2* 个 *timer* 依次放入 *timer* 队列,之后会立即执行微任务队列,所以打印出 *promise3*。
+
+然后进入 *timers* 阶段,执行 *timer1* 的回调函数,打印 *timer1*,发现有一个 *promise.then* 回调将其加入到微任务队列并且立即执行,之后同样的步骤执行 *timer2*,打印 *timer2* 以及 *promise2*。
+
+
+
+#### 一些注意点
+
+
+
+***setTimeout* 和 *setImmediate* 区别**
+
+
+
+二者非常相似,区别主要在于调用时机不同。
+
+- *setImmediate* 设计在 *poll* 阶段完成时执行,即 *check* 阶段
+- *setTimeout* 设计在 *poll* 阶段为空闲时,且设定时间到达后执行,但它在 *timer* 阶段执行
+
+来看一个具体的示例:
+
+```js
+setTimeout(function timeout () {
+ console.log('timeout');
+},0);
+setImmediate(function immediate () {
+ console.log('immediate');
+});
+```
+
+对于以上代码来说,*setTimeout* 可能执行在前,也可能执行在后。首先 *setTimeout(fn, 0) === setTimeout(fn, 1)*,这是由源码决定的,进入事件循环也是需要成本的,如果在准备时候花费了大于 *1ms* 的时间,那么在 *timer* 阶段就会直接执行 *setTimeout* 回调。如果准备时间花费小于 *1ms*,那么就是 *setImmediate* 回调先执行了。
+
+
+
+但当二者在异步 *I/O callback* 内部调用时,总是先执行 *setImmediate*,再执行 *setTimeout*,例如:
+
+```js
+const fs = require('fs')
+fs.readFile(__filename, () => {
+ setTimeout(() => {
+ console.log('timeout');
+ }, 0)
+ setImmediate(() => {
+ console.log('immediate')
+ })
+})
+// immediate
+// timeout
+```
+
+在上述代码中,*setImmediate* 永远先执行。因为两个代码写在 *I/O* 回调中,*I/O* 回调是在 *poll* 阶段执行,当回调执行完毕后队列为空,发现存在 *setImmediate* 回调,所以就直接跳转到 *check* 阶段去执行回调了。
+
+
+
+***process.nextTick***
+
+
+
+这个函数其实是独立于事件循环之外的,它有一个自己的队列。当每个阶段完成后,如果存在 *nextTick* 队列,就会清空队列中的所有回调函数,并且优先于其他 *microtask* 执行。
+
+
+
+```js
+setTimeout(() => {
+ console.log('timer1')
+ Promise.resolve().then(function() {
+ console.log('promise1')
+ })
+}, 0)
+process.nextTick(() => {
+ console.log('nextTick')
+ process.nextTick(() => {
+ console.log('nextTick')
+ process.nextTick(() => {
+ console.log('nextTick')
+ process.nextTick(() => {
+ console.log('nextTick')
+ })
+ })
+ })
+})
+// nextTick => nextTick => nextTick => nextTick => timer1 => promise1
+```
+
+
+
+***Promise.then***
+
+
+
+*Promise.then* 也是独立于事件循环之外的,有一个自己的队列,但是优先级要比 *process.nextTick* 要低,所以当微任务中同时存在 *process.nextTick* 和 *Promise.then* 时,会优先执行前者。
+
+
+
+```js
+setTimeout(()=>{
+ console.log('timer1')
+ Promise.resolve().then(function() {
+ console.log('promise1')
+ })
+ process.nextTick(() => {
+ console.log('nexttick');
+ })
+}, 0)
+setTimeout(()=>{
+ console.log('timer2')
+ Promise.resolve().then(function() {
+ console.log('promise2')
+ })
+}, 0)
+// timer1、nexttick、promise1、timer2、promise2
+```
+
+
+
+#### *Node.js* 与浏览器的事件队列的差异
+
+
+
+浏览器环境下,就两个队列,一个宏任务队列,一个微任务队列。微任务的任务队列是每个宏任务执行完之后执行。
+
+在 *Node.js* 中,每个任务队列的每个任务执行完毕之后,就会清空这个微任务队列。
+
+
+
+
+
+
+
+## 真题解答
+
+
+
+- 请简述一下 *Node.js* 中的事件循环,和浏览器环境的事件循环有何不同?
+
+> 参考答案:
+>
+> *Node.JS* 的事件循环分为 *6* 个阶段:
+>
+> - *timers* 阶段:这个阶段执行 *timer*( *setTimeout、setInterval* )的回调
+> - *I/O callbacks* 阶段:处理一些上一轮循环中的少数未执行的 *I/O* 回调
+> - *idle、prepare* 阶段:仅 *Node.js* 内部使用
+> - *poll* 阶段:获取新的 *I/O* 事件, 适当的条件下 *Node.js* 将阻塞在这里
+> - *check* 阶段:执行 *setImmediate( )* 的回调
+> - *close callbacks* 阶段:执行 *socket* 的 *close* 事件回调
+>
+> 事件循环的执行顺序为:
+>
+> 外部输入数据 –-> 轮询阶段( *poll* )-–> 检查阶段( *check* )-–> 关闭事件回调阶段( *close callback* )–-> 定时器检测阶段( *timer* )–-> *I/O* 事件回调阶段( *I/O callbacks* )-–>闲置阶段( *idle、prepare* )–->轮询阶段(按照该顺序反复运行)...
+>
+> 浏览器和 *Node.js* 环境下,微任务任务队列的执行时机不同
+>
+> - 在 *Node.js* 中,每个任务队列的每个任务执行完毕之后,就会清空这个微任务队列。
+> - 浏览器环境下,就两个队列,一个宏任务队列,一个微任务队列。微任务的任务队列是每个宏任务执行完之后执行。
+
+
+
+-*EOF*-
\ No newline at end of file
diff --git a/25. eval/eval.md b/25. eval/eval.md
new file mode 100644
index 0000000..598887c
--- /dev/null
+++ b/25. eval/eval.md
@@ -0,0 +1,45 @@
+# *eval*
+
+
+
+## 经典真题
+
+
+
+- *JavaScript* 中的 *eval* 方法是啥?一般什么场景下使用?
+
+
+
+## 关于 *eval* 你所需要知道的内容
+
+
+
+### *eval* 的基本用法
+
+
+
+
+
+
+
+### *eval* 作用域
+
+
+
+### *eval* 应用场景
+
+
+
+
+
+
+
+## 真题解答
+
+
+
+- *JavaScript* 中的 *eval* 方法是啥?一般什么场景下使用?
+
+> 参考答案:
+>
+>
\ No newline at end of file
diff --git a/javascript 面试题汇总.md b/javascript 面试题汇总.md
new file mode 100644
index 0000000..34d8c2a
--- /dev/null
+++ b/javascript 面试题汇总.md
@@ -0,0 +1,8026 @@
+# *JavaScript* 面试题汇总
+
+### 1. 根据下面 *ES6* 构造函数的书写方式,要求写出 *ES5* 的
+
+```js
+class Example {
+ constructor(name) {
+ this.name = name;
+ }
+ init() {
+ const fun = () => { console.log(this.name) }
+ fun();
+ }
+}
+const e = new Example('Hello');
+e.init();
+```
+
+> 参考答案:
+>
+> ```js
+> function Example(name) {
+> 'use strict';
+> if (!new.target) {
+> throw new TypeError('Class constructor cannot be invoked without new');
+> }
+> this.name = name;
+> }
+>
+> Object.defineProperty(Example.prototype, 'init', {
+> enumerable: false,
+> value: function () {
+> 'use strict';
+> if (new.target) {
+> throw new TypeError('init is not a constructor');
+> }
+> var fun = function () {
+> console.log(this.name);
+> }
+> fun.call(this);
+> }
+> })
+> ```
+>
+
+> 解析:
+>
+> 此题的关键在于是否清楚 *ES6* 的 *class* 和普通构造函数的区别,记住它们有以下区别,就不会有遗漏:
+>
+> 1. *ES6* 中的 *class* 必须通过 *new* 来调用,不能当做普通函数调用,否则报错
+>
+> 因此,在答案中,加入了 *new.target* 来判断调用方式
+>
+> 2. *ES6* 的 *class* 中的所有代码均处于严格模式之下
+>
+> 因此,在答案中,无论是构造函数本身,还是原型方法,都使用了严格模式
+>
+> 3. *ES6* 中的原型方法是不可被枚举的
+>
+> 因此,在答案中,定义原型方法使用了属性描述符,让其不可枚举
+>
+> 4. 原型上的方法不允许通过 *new* 来调用
+>
+> 因此,在答案中,原型方法中加入了 *new.target* 来判断调用方式
+
+
+
+### 2. 数组去重有哪些方法?(美团 *19* 年)
+
+> 参考答案:
+>
+> ```js
+> // 数字或字符串数组去重,效率高
+> function unique(arr) {
+> var result = {}; // 利用对象属性名的唯一性来保证不重复
+> for (var i = 0; i < arr.length; i++) {
+> if (!result[arr[i]]) {
+> result[arr[i]] = true;
+> }
+> }
+> return Object.keys(result); // 获取对象所有属性名的数组
+> }
+>
+> // 任意数组去重,适配范围光,效率低
+> function unique(arr) {
+> var result = []; // 结果数组
+> for (var i = 0; i < arr.length; i++) {
+> if (!result.includes(arr[i])) {
+> result.push(arr[i]);
+> }
+> }
+> return result;
+> }
+>
+> // 利用ES6的Set去重,适配范围广,效率一般,书写简单
+> function unique(arr) {
+> return [...new Set(arr)]
+> }
+> ```
+
+
+
+### 3. 描述下列代码的执行结果
+
+```js
+foo(typeof a);
+function foo(p) {
+ console.log(this);
+ console.log(p);
+ console.log(typeof b);
+ let b = 0;
+}
+```
+
+> 参考答案:
+>
+> 报错,报错的位置在 `console.log(typeof b);`
+>
+> 报错原因:*ReferenceError: Cannot access 'b' before initialization*
+
+> 解析:
+>
+> 这道题考查的是 *ES6* 新增的声明变量关键字 *let* 以及暂时性死区的知识。*let* 和以前的 *var* 关键字不一样,无法在 *let* 声明变量之前访问到该变量,所以在 *typeof b* 的地方就会报错。
+
+
+
+### 4. 描述下列代码的执行结果
+
+```js
+class Foo {
+ constructor(arr) {
+ this.arr = arr;
+ }
+ bar(n) {
+ return this.arr.slice(0, n);
+ }
+}
+var f = new Foo([0, 1, 2, 3]);
+console.log(f.bar(1));
+console.log(f.bar(2).splice(1, 1));
+console.log(f.arr);
+```
+
+> 参考答案:
+>
+> [ 0 ]
+> [ 1 ]
+> [ 0, 1, 2, 3 ]
+
+> 解析:
+>
+> 主要考察的是数组相关的知识。 *f* 对象上面有一个属性 *arr*,*arr* 的值在初始化的时候会被初始化为 *[0, 1, 2, 3]*,之后就完全是考察数组以及数组方法的使用了。
+
+
+
+### 5. 描述下列代码的执行结果
+
+```js
+01 function f(count) {
+02 console.log(`foo${count}`);
+03 setTimeout(() => { console.log(`bar${count}`); });
+04 }
+05 f(1);
+06 f(2);
+07 setTimeout(() => { f(3); });
+```
+
+> 参考答案:
+>
+> foo1
+> foo2
+> bar1
+> bar2
+> foo3
+> bar3
+
+> 解析:
+>
+> 这个完全是考察的异步的知识。调用 *f(1)* 的时候,会执行同步代码,打印出 *foo1*,然后 *03* 行的 *setTimeout* 被放入到异步执行队列,接下来调用 *f(2)* 的时候,打印出 *foo2*,后面 *03* 行的 *setTimeout* 又被放入到异步执行队列。然后执行 *07* 行的语句,被放入到异步执行队列。至此,所有同步代码就都执行完毕了。
+>
+> 接下来开始执行异步代码,那么大家时间没写,就都是相同的,所以谁先被放入到异步队列,谁就先执行,所以先打印出 *bar1*、然后是 *bar2*,接下来执行之前 *07* 行放入到异步队列里面的 *setTimeout*,先执行 *f* 函数里面的同步代码,打印出 *foo3*,然后是最后一个异步,打印出 *bar3*
+
+
+
+### 6. 描述下列代码的执行结果
+
+```js
+var a = 2;
+var b = 5;
+console.log(a === 2 || 1 && b === 3 || 4);
+```
+
+> 参考答案:
+>
+> *true*
+>
+> 考察的是逻辑运算符。在 || 里面,只要有一个为真,后面的直接短路,都不用去计算。所以 *a === 2* 得到 *true* 之后直接短路了,返回 *true*。
+
+
+
+### 7. 描述下列代码的执行结果
+
+```js
+export class ButtonWrapper {
+ constructor(domBtnEl, hash) {
+ this.domBtnEl = domBtnEl;
+ this.hash = hash;
+ this.bindEvent();
+ }
+ bindEvent() {
+ this.domBtnEl.addEventListener('click', this.clickEvent, false);
+ }
+ detachEvent() {
+ this.domBtnEl.removeEventListener('click', this.clickEvent);
+ }
+ clickEvent() {
+ console.log(`The hash of the button is: ${this.hash}`);
+ }
+}
+```
+
+> 参考答案:
+>
+> 上面的代码导出了一个 *ButtonWrapper* 类,该类在被实例化的时候,实例化对象上面有两个属性,分别是 *domBtnEl* 和 *hash*,*domBtnEl* 是一个 *DOM* 节点,之后为这个 *domBtnEl* 绑定了点击事件,点击后打印出 *The hash of the button is: hash* 那句话。*detachEvent* 是移除点击事件,当调用实例化对象的 *detachEvent* 方法时,点击事件就会被移除。
+
+
+
+### 8. 箭头函数有哪些特点
+
+> 参考答案:
+>
+> 1. 更简洁的语法,例如
+> - 只有一个形参就不需要用括号括起来
+> - 如果函数体只有一行,就不需要放到一个块中
+> - 如果 *return* 语句是函数体内唯一的语句,就不需要 *return* 关键字
+> 2. 箭头函数没有自己的 *this*,*arguments*,*super*
+> 3. 箭头函数 *this* 只会从自己的作用域链的上一层继承 *this*。
+
+
+
+### 9. 说一说类的继承
+
+> 参考答案:
+>
+> 继承是面向对象编程中的三大特性之一。
+>
+> *JavaScript* 中的继承经过不断的发展,从最初的对象冒充慢慢发展到了今天的圣杯模式继承。
+>
+> 其中最需要掌握的就是**伪经典继承**和**圣杯模式**的继承。
+>
+> 很长一段时间,JS 继承使用的都是**组合继承**。这种继承也被称之为伪经典继承,该继承方式综合了原型链和盗用构造函数的方式,将两者的优点集中了起来。
+>
+> 组合继承弥补了之前原型链和盗用构造函数这两种方式各自的不足,是 *JavaScript* 中使用最多的继承方式。
+>
+> 组合继承最大的问题就是效率问题。最主要就是父类的构造函数始终会被调用两次:一次是在创建子类原型时调用,另一次是在子类构造函数中调用。
+>
+> 本质上,子类原型最终是要包含超类对象的所有实例属性,子类构造函数只要在执行时重写自己的原型就行了。
+>
+> 圣杯模式的继承解决了这一问题,其基本思路就是不通过调用父类构造函数来给子类原型赋值,而是取得父类原型的一个副本,然后将返回的新对象赋值给子类原型。
+
+> 解析:该题主要考察就是对 *js* 中的继承是否了解,以及常见的继承的形式有哪些。最常用的继承就是**组合继承**(伪经典继承)和圣杯模式继承。下面附上 *js* 中这两种继承模式的详细解析。
+>
+> 下面是一个组合继承的例子:
+>
+> ```js
+> // 基类
+> var Person = function (name, age) {
+> this.name = name;
+> this.age = age;
+> }
+> Person.prototype.test = "this is a test";
+> Person.prototype.testFunc = function () {
+> console.log('this is a testFunc');
+> }
+>
+> // 子类
+> var Student = function (name, age, gender, score) {
+> Person.apply(this, [name, age]); // 盗用构造函数
+> this.gender = gender;
+> this.score = score;
+> }
+> Student.prototype = new Person(); // 改变 Student 构造函数的原型对象
+> Student.prototype.testStuFunc = function () {
+> console.log('this is a testStuFunc');
+> }
+>
+> // 测试
+> var zhangsan = new Student("张三", 18, "男", 100);
+> console.log(zhangsan.name); // 张三
+> console.log(zhangsan.age); // 18
+> console.log(zhangsan.gender); // 男
+> console.log(zhangsan.score); // 100
+> console.log(zhangsan.test); // this is a test
+> zhangsan.testFunc(); // this is a testFunc
+> zhangsan.testStuFunc(); // this is a testStuFunc
+> ```
+>
+>
+>
+> 在上面的例子中,我们使用了组合继承的方式来实现继承,可以看到无论是基类上面的属性和方法,还是子类自己的属性和方法,都得到了很好的实现。
+>
+>
+>
+> 但是在组合继承中存在效率问题,比如在上面的代码中,我们其实调用了两次 *Person*,产生了两组 *name* 和 *age* 属性,一组在原型上,一组在实例上。
+>
+>
+>
+> 也就是说,我们在执行 *Student.prototype = new Person( )* 的时候,我们是想要 *Person* 原型上面的方法,属性是不需要的,因为属性之后可以通过 *Person.apply(this, [name, age])* 拿到,但是当你 *new Person( )* 的时候,会实例化一个 *Person* 对象出来,这个对象上面,属性和方法都有。
+>
+>
+>
+> 圣杯模式的继承解决了这一问题,其基本思路就是不通过调用父类构造函数来给子类原型赋值,而是取得父类原型的一个副本,然后将返回的新对象赋值给子类原型。
+>
+>
+>
+> 下面是一个圣杯模式的示例:
+>
+>
+>
+> ```js
+> // target 是子类,origin 是基类
+> // target ---> Student, origin ---> Person
+> function inherit(target, origin) {
+> function F() { }; // 没有任何多余的属性
+>
+> // origin.prototype === Person.prototype, origin.prototype.constructor === Person 构造函数
+> F.prototype = origin.prototype;
+>
+> // 假设 new F() 出来的对象叫小 f
+> // 那么这个 f 的原型对象 === F.prototype === Person.prototype
+> // 那么 f.constructor === Person.prototype.constructor === Person 的构造函数
+> target.prototype = new F();
+>
+> // 而 f 这个对象又是 target 对象的原型对象
+> // 这意味着 target.prototype.constructor === f.constructor
+> // 所以 target 的 constructor 会指向 Person 构造函数
+>
+> // 我们要让子类的 constructor 重新指向自己
+> // 若不修改则会发现 constructor 指向的是父类的构造函数
+> target.prototype.constructor = target;
+> }
+>
+>
+> // 基类
+> var Person = function (name, age) {
+> this.name = name;
+> this.age = age;
+> }
+> Person.prototype.test = "this is a test";
+> Person.prototype.testFunc = function () {
+> console.log('this is a testFunc');
+> }
+>
+>
+> // 子类
+> var Student = function (name, age, gender, score) {
+> Person.apply(this, [name, age]);
+> this.gender = gender;
+> this.score = score;
+> }
+> inherit(Student, Person); // 使用圣杯模式实现继承
+> // 在子类上面添加方法
+> Student.prototype.testStuFunc = function () {
+> console.log('this is a testStuFunc');
+> }
+>
+> // 测试
+> var zhangsan = new Student("张三", 18, "男", 100);
+>
+> console.log(zhangsan.name); // 张三
+> console.log(zhangsan.age); // 18
+> console.log(zhangsan.gender); // 男
+> console.log(zhangsan.score); // 100
+> console.log(zhangsan.test); // this is a test
+> zhangsan.testFunc(); // this is a testFunc
+> zhangsan.testStuFunc(); // this is a testStuFunc
+> ```
+>
+> 在上面的代码中,我们在 *inherit* 方法中创建了一个中间层,之后让 *F* 的原型和父类的原型指向同一地址,再让子类的原型指向这个 *F* 的实例化对象来实现了继承。
+>
+>
+>
+> 这样我们的继承,属性就不会像之前那样实例对象上一份,原型对象上一份,拥有两份。圣杯模式继承是目前 *js* 继承的最优解。
+>
+>
+>
+> 最后我再画个图帮助大家理解,如下图:
+>
+>
+>
+> 组合模式(伪经典模式)下的继承示意图:
+>
+>
+>
+> 圣杯模式下的继承示意图:
+>
+>
+>
+>
+>
+>
+
+
+
+### 10. *new* 操作符都做了哪些事?
+
+> 参考答案:
+>
+> *new* 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
+>
+> *new* 关键字会进行如下的操作:
+> 步骤 *1*:创建一个空的简单 *JavaScript* 对象,即 { } ;
+> 步骤 *2*:链接该对象到另一个对象(即设置该对象的原型对象);
+> 步骤 *3*:将步骤 *1* 新创建的对象作为 *this* 的上下文;
+> 步骤 *4*:如果该函数没有返回对象,则返回 *this*。
+
+
+
+### 11. *call、apply、bind* 的区别 ?
+
+> 参考答案:
+>
+> *call* 和 *apply* 的功能相同,区别在于传参的方式不一样:
+>
+> - *fn.call(obj, arg1, arg2, ...)* 调用一个函数, 具有一个指定的 *this* 值和分别地提供的参数(参数的列表)。
+> - *fn.apply(obj, [argsArray])* 调用一个函数,具有一个指定的 *this* 值,以及作为一个数组(或类数组对象)提供的参数。
+>
+> *bind* 和 *call/apply* 有一个很重要的区别,一个函数被 *call/apply* 的时候,会直接调用,但是 *bind* 会创建一个新函数。当这个新函数被调用时,*bind( )* 的第一个参数将作为它运行时的 *this*,之后的一序列参数将会在传递的实参前传入作为它的参数。
+
+
+
+### 12. 事件循环机制(宏任务、微任务)
+
+> 参考答案:
+>
+> 在 *js* 中任务会分为同步任务和异步任务。
+>
+> 如果是同步任务,则会在主线程(也就是 *js* 引擎线程)上进行执行,形成一个执行栈。但是一旦遇到异步任务,则会将这些异步任务交给异步模块去处理,然后主线程继续执行后面的同步代码。
+>
+> 当异步任务有了运行结果以后,就会在任务队列里面放置一个事件,这个任务队列由事件触发线程来进行管理。
+>
+> 一旦执行栈中所有的同步任务执行完毕,就代表着当前的主线程(*js* 引擎线程)空闲了,系统就会读取任务队列,将可以运行的异步任务添加到执行栈中,开始执行。
+>
+> 在 *js* 中,任务队列中的任务又可以被分为 *2* 种类型:宏任务(*macrotask*)与微任务(*microtask*)
+>
+> 宏任务可以理解为每次执行栈所执行的代码就是一个宏任务,包括每次从事件队列中获取一个事件回调并放到执行栈中所执行的任务。
+>
+> 微任务可以理解为当前宏任务执行结束后立即执行的任务。
+
+
+
+### 13. 你了解 *node* 中的事件循环机制吗?*node11* 版本以后有什么改变
+
+> 参考答案:
+>
+> *Node.js* 在主线程里维护了一个**事件队列,**当接到请求后,就将该请求作为一个事件放入这个队列中,然后继续接收其他请求。当主线程空闲时(没有请求接入时),就开始循环事件队列,检查队列中是否有要处理的事件,这时要分两种情况:如果是非 *I/O* 任务,就亲自处理,并通过回调函数返回到上层调用;如果是 *I/O* 任务,就从**线程池**中拿出一个线程来处理这个事件,并指定回调函数,然后继续循环队列中的其他事件。
+>
+> 当线程中的 *I/O* 任务完成以后,就执行指定的回调函数,并把这个完成的事件放到事件队列的尾部,等待事件循环,当主线程再次循环到该事件时,就直接处理并返回给上层调用。 这个过程就叫 **事件循环** (*Event Loop*)。
+>
+> 无论是 *Linux* 平台还是 *Windows* 平台,*Node.js* 内部都是通过**线程池**来完成异步 *I/O* 操作的,而 *LIBUV* 针对不同平台的差异性实现了统一调用。因此,***Node.js* 的单线程仅仅是指 *JavaScript* 运行在单线程中,而并非 *Node.js* 是单线程。**
+>
+> *Node.JS* 的事件循环分为 *6* 个阶段:
+>
+> - *timers* 阶段:这个阶段执行 *timer*( *setTimeout、setInterval* )的回调
+> - *I/O callbacks* 阶段:处理一些上一轮循环中的少数未执行的 *I/O* 回调
+> - *idle、prepare* 阶段:仅 *Node.js* 内部使用
+> - *poll* 阶段:获取新的 *I/O* 事件, 适当的条件下 *Node.js* 将阻塞在这里
+> - *check* 阶段:执行 *setImmediate( )* 的回调
+> - *close callbacks* 阶段:执行 *socket* 的 *close* 事件回调
+>
+> 事件循环的执行顺序为:
+>
+> 外部输入数据 –-> 轮询阶段( *poll* )-–> 检查阶段( *check* )-–> 关闭事件回调阶段( *close callback* )–-> 定时器检测阶段( *timer* )–-> *I/O* 事件回调阶段( *I/O callbacks* )-–>闲置阶段( *idle、prepare* )–->轮询阶段(按照该顺序反复运行)...
+>
+> 浏览器和 *Node.js* 环境下,微任务任务队列的执行时机不同
+>
+> - *Node.js* 端,微任务在事件循环的各个阶段之间执行
+> - 浏览器端,微任务在事件循环的宏任务执行完之后执行
+>
+> *Node.js v11.0.0* 版本于 *2018* 年 *10* 月,主要有以下变化:
+>
+> 1. *V8* 引擎更新至版本 *7.0*
+> 2. *http、https* 和 *tls* 模块默认使用 *WHESWG URL* 解析器。
+> 3. 隐藏子进程的控制台窗口默认改为了 *true*。
+> 4. *FreeBSD 10*不再支持。
+> 5. 增加了多线程 *Worker Threads*
+
+
+
+### 14. 什么是函数柯里化?
+
+> 参考答案:
+>
+> 柯里化(*currying*)又称部分求值。一个柯里化的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
+>
+> 举个例子,就是把原本:
+>
+> *function(arg1,arg2)* 变成 *function(arg1)(arg2)*
+> *function(arg1,arg2,arg3)* 变成 *function(arg1)(arg2)(arg3)*
+> *function(arg1,arg2,arg3,arg4)* 变成 *function(arg1)(arg2)(arg3)(arg4)*
+>
+> 总而言之,就是将:
+>
+> *function(arg1,arg2,…,argn)* 变成 *function(arg1)(arg2)…(argn)*
+
+
+
+### 15. *promise.all* 方法的使用场景?数组中必须每一项都是 *promise* 对象吗?不是 *promise* 对象会如何处理 ?
+
+> 参考答案:
+>
+> ***promise.all(promiseArray)*** 方法是 *promise* 对象上的静态方法,该方法的作用是将多个 *promise* 对象实例包装,生成并返回一个新的 *promise* 实例。
+>
+> 此方法在集合多个 *promise* 的返回结果时很有用。
+>
+> 返回值将会按照参数内的 *promise* 顺序排列,而不是由调用 *promise* 的完成顺序决定。
+>
+> ***promise.all* 的特点**
+>
+> 接收一个*Promise*实例的数组或具有*Iterator*接口的对象
+>
+> 如果元素不是*Promise*对象,则使用*Promise.resolve*转成*Promise*对象
+>
+> 如果全部成功,状态变为*resolved*,返回值将组成一个数组传给回调
+>
+> 只有有一个失败,状态就变为 *rejected*,返回值将直接传递给回调 *all( )*的返回值,也是新的 *promise* 对象
+
+
+
+### 16. *this* 的指向哪几种 ?
+
+> 参考答案:
+>
+> 总结起来,*this* 的指向规律有如下几条:
+>
+> - 在函数体中,非显式或隐式地简单调用函数时,在严格模式下,函数内的 *this* 会被绑定到 *undefined* 上,在非严格模式下则会被绑定到全局对象 *window/global* 上。
+> - 一般使用 *new* 方法调用构造函数时,构造函数内的 *this* 会被绑定到新创建的对象上。
+> - 一般通过 *call/apply/bind* 方法显式调用函数时,函数体内的 *this* 会被绑定到指定参数的对象上。
+> - 一般通过上下文对象调用函数时,函数体内的 *this* 会被绑定到该对象上。
+> - 在箭头函数中,*this* 的指向是由外层(函数或全局)作用域来决定的。
+
+
+
+### 17. *JS* 中继承实现的几种方式
+
+> 参考答案:
+>
+> *JS* 的继承随着语言的发展,从最早的对象冒充到现在的圣杯模式,涌现出了很多不同的继承方式。每一种新的继承方式都是对前一种继承方式不足的一种补充。
+>
+> 1. 原型链继承
+>
+> - 重点:让新实例的原型等于父类的实例。
+> - 特点:实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新实例不会继承父类实例的属性!)
+> - 缺点:
+> - 1、新实例无法向父类构造函数传参。
+> - 2、继承单一。
+> - 3、所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)
+>
+> 2. 借用构造函数继承
+>
+> - 重点:用 *call( )* 和 *apply( )* 将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))
+> - 特点:
+> - 1、只继承了父类构造函数的属性,没有继承父类原型的属性。
+> - 2、解决了原型链继承缺点1、2、3。
+> - 3、可以继承多个构造函数属性(call多个)。
+> - 4、在子实例中可向父实例传参。
+> - 缺点:
+> - 1、只能继承父类构造函数的属性。
+> - 2、无法实现构造函数的复用。(每次用每次都要重新调用)
+> - 3、每个新实例都有父类构造函数的副本,臃肿。
+>
+> 3. 组合模式(又被称之为伪经典模式)
+>
+> - 重点:结合了两种模式的优点,传参和复用
+> - 特点:
+> - 1、可以继承父类原型上的属性,可以传参,可复用。
+> - 2、每个新实例引入的构造函数属性是私有的。
+> - 缺点:调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。
+>
+> 4. 寄生组合式继承(圣杯模式)
+>
+> - 重点:修复了组合继承的问题
+
+
+
+### 18. 什么是事件监听
+
+> 参考答案:
+>
+> 首先需要区别清楚事件监听和事件监听器。
+>
+> 在绑定事件的时候,我们需要对应的书写一个事件处理程序,来应对事件发生时的具体行为。
+>
+> 这个事件处理程序我们也称之为事件监听器。
+>
+> 当事件绑定好后,程序就会对事件进行监听,当用户触发事件时,就会执行对应的事件处理程序。
+>
+> 关于事件监听,*W3C* 规范中定义了 *3* 个事件阶段,依次是捕获阶段、目标阶段、冒泡阶段。
+>
+> - **捕获**阶段:在事件对象到达事件目标之前,事件对象必须从 *window* 经过目标的祖先节点传播到事件目标。 这个阶段被我们称之为捕获阶段。在这个阶段注册的事件监听器在事件到达其目标前必须先处理事件。
+>
+> - **目标** 阶段:事件对象到达其事件目标。 这个阶段被我们称为目标阶段。一旦事件对象到达事件目标,该阶段的事件监听器就要对它进行处理。如果一个事件对象类型被标志为不能冒泡。那么对应的事件对象在到达此阶段时就会终止传播。
+> - **冒泡** 阶段:事件对象以一个与捕获阶段相反的方向从事件目标传播经过其祖先节点传播到 *window*。这个阶段被称之为冒泡阶段。在此阶段注册的事件监听器会对相应的冒泡事件进行处理。
+
+
+
+### 19. 什么是 *js* 的闭包?有什么作用?
+
+> 参考答案:
+>
+> 一个函数和对其周围状态(*lexical environment*,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是**闭包**(*closure*)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 *JavaScript* 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
+>
+> 闭包的用处:
+>
+> 1. 匿名自执行函数
+> 2. 结果缓存
+> 3. 封装
+> 4. 实现类和继承
+
+
+
+### 20. 事件委托以及冒泡原理
+
+> 参考答案:
+>
+> 事件委托,又被称之为事件代理。在 *JavaScript* 中,添加到页面上的事件处理程序数量将直接关系到页面整体的运行性能。导致这一问题的原因是多方面的。
+>
+> 首先,每个函数都是对象,都会占用内存。内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致的 *DOM* 访问次数,会延迟整个页面的交互就绪时间。
+>
+> 对事件处理程序过多问题的解决方案就是事件委托。
+>
+> 事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。例如,*click* 事件会一直冒泡到 *document* 层次。也就是说,我们可以为整个页面指定一个 *onclick* 事件处理程序,而不必给每个可单击的元素分别添加事件处理程序。
+>
+> 事件冒泡(*event bubbling*),是指事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。
+
+
+
+### 21. *let const var* 的区别?什么是块级作用域?如何用?
+
+> 参考答案:
+>
+> 1. *var* 定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问,有变量提升。
+> 2. *let* 定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问,无变量提升,不可以重复声明。
+> 3. *const* 用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改,无变量提升,不可以重复声明。
+>
+> 最初在 *JS* 中作用域有:全局作用域、函数作用域。没有块作用域的概念。
+>
+> *ES6* 中新增了块级作用域。块作用域由 { } 包括,*if* 语句和 *for* 语句里面的 { } 也属于块作用域。
+>
+> 在以前没有块作用域的时候,在 if 或者 for 循环中声明的变量会泄露成全局变量,其次就是 { } 中的内层变量可能会覆盖外层变量。块级作用域的出现解决了这些问题。
+
+
+
+### 22. *ES5* 的方法实现块级作用域(立即执行函数) *ES6* 呢?
+
+> 参考答案:
+>
+> *ES6* 原生支持块级作用域。块作用域由 { } 包括,*if* 语句和 *for* 语句里面的 { } 也属于块作用域。
+>
+> 使用 *let* 声明的变量或者使用 *const* 声明的常量,只能在块作用域里访问,不能跨块访问。
+
+
+
+### 23. *ES6* 箭头函数的特性
+
+> 参考答案:
+>
+> 1. 更简洁的语法,例如
+> - 只有一个形参就不需要用括号括起来
+> - 如果函数体只有一行,就不需要放到一个块中
+> - 如果 *return* 语句是函数体内唯一的语句,就不需要 *return* 关键字
+> 2. 箭头函数没有自己的 *this*,*arguments*,*super*
+> 3. 箭头函数 *this* 只会从自己的作用域链的上一层继承 *this*。
+
+
+
+### 24. 箭头函数与普通函数的区别 ?
+
+> 参考答案:
+>
+> 1. 外形不同。箭头函数使用箭头定义,普通函数中没有
+>
+> 2. 普通函数可以有匿名函数,也可以有具体名函数,但是箭头函数都是匿名函数。
+>
+> 3. **箭头函数不能用于构造函数,不能使用 *new*,**普通函数可以用于构造函数,以此创建对象实例。
+>
+> 4. **箭头函数中 *this* 的指向不同,**在普通函数中,*this* 总是指向调用它的对象,如果用作构造函数,*this* 指向创建的对象实例。
+> 箭头函数本身不创建 *this*,也可以说箭头函数本身没有 *this*,但是它在声明时可以捕获其所在上下文的 *this* 供自己使用。
+>
+> 5. 每一个普通函数调用后都具有一个 *arguments* 对象,用来存储实际传递的参数。
+>
+> 但是箭头函数并没有此对象。**取而代之用rest参数来解决**。
+>
+> 6. 箭头函数不能用于 *Generator* 函数,不能使用 *yeild* 关键字。
+>
+> 7. 箭头函数不具有 *prototype* 原型对象。而普通函数具有 *prototype* 原型对象。
+>
+> 8. 箭头函数不具有 *super*。
+>
+> 9. 箭头函数不具有 *new.target*。
+
+
+
+### 25. *JS* 的基本数据类型有哪些?基本数据类型和引用数据类型的区别
+
+> 参考答案:
+>
+> 在 *JavaScript* 中,数据类型整体上来讲可以分为两大类:**基本类型**和**引用数据类型**
+>
+> 基本数据类型,一共有 *6* 种:
+>
+> ```text
+> string,symbol,number,boolean,undefined,null
+> ```
+>
+> 其中 *symbol* 类型是在 *ES6* 里面新添加的基本数据类型。
+>
+> 引用数据类型,就只有 *1* 种:
+>
+> ```js
+> object
+> ```
+>
+> 基本数据类型的值又被称之为原始值或简单值,而引用数据类型的值又被称之为复杂值或引用值。
+>
+> 两者的区别在于:
+>
+> **原始值是表示 *JavaScript* 中可用的数据或信息的最底层形式或最简单形式。**简单类型的值被称为原始值,是因为它们是**不可细化**的。
+>
+> 也就是说,数字是数字,字符是字符,布尔值是 *true* 或 *false*,*null* 和 *undefined* 就是 *null* 和 *undefined*。这些值本身很简单,不能够再进行拆分。由于原始值的数据大小是固定的,所以**原始值的数据是存储于内存中的栈区里面的。**
+>
+> 在 *JavaScript* 中,对象就是一个引用值。因为对象可以向下拆分,拆分成多个简单值或者复杂值。**引用值在内存中的大小是未知的,因为引用值可以包含任何值,而不是一个特定的已知值,所以引用值的数据都是存储于堆区里面。**
+>
+> 最后总结一下两者的区别:
+>
+> 1. 访问方式
+> - 原始值:访问到的是值
+> - 引用值:访问到的是引用地址
+> 2. 比较方式
+> - 原始值:比较的是值
+> - 引用值:比较的是地址
+>
+> 3. 动态属性
+> - 原始值:无法添加动态属性
+> - 引用值:可以添加动态属性
+> 4. 变量赋值
+> - 原始值:赋值的是值
+> - 引用值:赋值的是地址
+
+
+
+### 26. *NaN* 是什么的缩写
+
+> 参考答案:
+>
+> *NaN* 的全称为 *Not a Number*,表示非数,或者说不是一个数。虽然 NaN 表示非数,但是它却属于 *number* 类型。
+>
+> *NaN* 有两个特点:
+>
+> 1. 任何涉及 *NaN* 的操作都会返回 *NaN*
+> 2. *NaN* 和任何值都不相等,包括它自己本身
+
+
+
+### 27. *JS* 的作用域类型
+
+> 参考答案:
+>
+> 在 *JavaScript* 里面,作用域一共有 4 种:全局作用域,局部作用域、函数作用域以及 *eval* 作用域。
+>
+> **全局作用域:**这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境。
+>
+> **局部作用域:**当使用 *let* 或者 *const* 声明变量时,这些变量在一对花括号中存在局部作用域,只能够在花括号内部进行访问使用。
+>
+> **函数作用域:**当进入到一个函数的时候,就会产生一个函数作用域。函数作用域里面所声明的变量只在函数中提供访问使用。
+>
+> ***eval* 作用域:**当调用 *eval( )* 函数的时候,就会产生一个 *eval* 作用域。
+
+
+
+### 28. *undefined==null* 返回的结果是什么?*undefined* 与 *null* 的区别在哪?
+
+> 参考答案:
+>
+> 返回 *true*。
+>
+> 这两个值都表示“无”的意思。
+>
+> 通常情况下, 当我们试图访问某个不存在的或者没有赋值的变量时,就会得到一个 *undefined* 值。*Javascript* 会自动将声明是没有进行初始化的变量设为 *undifined*。
+>
+> 而 *null* 值表示空,*null* 不能通过 *Javascript* 来自动赋值,也就是说必须要我们自己手动来给某个变量赋值为 *null*。
+
+> 解析:
+>
+> 那么为什么 *JavaScript* 要设置两个表示"无"的值呢?这其实是历史原因。
+>
+> *1995* 年 *JavaScript* 诞生时,最初像 *Java* 一样,只设置了 *null* 作为表示"无"的值。根据 *C* 语言的传统,*null* 被设计成可以自动转为*0*。
+>
+> 但是,*JavaScript* 的设计者,觉得这样做还不够,主要有以下两个原因。
+>
+> 1. *null* 像在 *Java* 里一样,被当成一个对象。但是,*JavaScript* 的数据类型分成原始类型(*primitive*)和合成类型(*complex*)两大类,作者觉得表示"无"的值最好不是对象。
+> 2. *JavaScript* 的最初版本没有包括错误处理机制,发生数据类型不匹配时,往往是自动转换类型或者默默地失败。作者觉得,如果 *null* 自动转为 *0*,很不容易发现错误。
+>
+> 因此,作者又设计了一个 *undefined*。
+>
+> **这里注意:先有 *null* 后有 *undefined* 出来,*undefined* 是为了填补之前的坑。**
+>
+> *JavaScript* 的最初版本是这样区分的:
+>
+> *null* 是一个表示"无"的对象(空对象指针),转为数值时为 *0*;
+>
+> 典型用法是:
+>
+> - 作为函数的参数,表示该函数的参数不是对象。
+>
+> - 作为对象原型链的终点。
+>
+> *undefined* 是一个表示"无"的原始值,转为数值时为 *NaN*。
+>
+> 典型用法是:
+>
+> - 变量被声明了,但没有赋值时,就等于 *undefined*。
+> - 调用函数时,应该提供的参数没有提供,该参数等于 *undefined*。
+> - 对象没有赋值的属性,该属性的值为 *undefined*。
+> - 函数没有返回值时,默认返回 *undefined*。
+
+
+
+### 29. 写一个函数判断变量类型
+
+> 参考答案:
+>
+> ```js
+> function getType(data){
+> let type = typeof data;
+> if(type !== "object"){
+> return type
+> }
+> return Object.prototype.toString.call(data).replace(/^\[object (\S+)\]$/,'$1')
+> }
+> function Person(){}
+> console.log(getType(1)); // number
+> console.log(getType(true)); // boolean
+> console.log(getType([1,2,3])); // Array
+> console.log(getType(/abc/)); // RegExp
+> console.log(getType(new Date)); // Date
+> console.log(getType(new Person)); // Object
+> console.log(getType({})); // Object
+> ```
+
+
+
+### 30. *js* 的异步处理函数
+
+> 参考答案:
+>
+> 在最早期的时候,*JavaScript* 中要实现异步操作,使用的就是 *Callback* 回调函数。
+>
+> 但是回调函数会产生回调地狱(*Callback Hell*)
+>
+> 之后 ES6 推出了 *Promise* 解决方案来解决回调地狱的问题。不过,虽然 *Promise* 作为 *ES6* 中提供的一种新的异步编程解决方案,但是它也有问题。比如,代码并没有因为新方法的出现而减少,反而变得更加复杂,同时理解难度也加大。
+>
+> 之后,就出现了基于 *Generator* 的异步解决方案。不过,这种方式需要编写外部的执行器,而执行器的代码写起来一点也不简单。当然也可以使用一些插件,比如 *co* 模块来简化执行器的编写。
+>
+> *ES7* 提出的 *async* 函数,终于让 *JavaScript* 对于异步操作有了终极解决方案。
+>
+> 实际上,*async* 只是生成器的一种语法糖而已,简化了外部执行器的代码,同时利用 *await* 替代 *yield*,*async* 替代生成器的`*`号。
+
+
+
+### 31. *defer* 与 *async* 的区别
+
+> 参考答案:
+>
+> 按照惯例,所有 *script* 元素都应该放在页面的 *head* 元素中。这种做法的目的就是把**所有外部文件(*CSS* 文件和 *JavaScript* 文件)的引用都放在相同的地方**。可是,在文档的 *head* 元素中包含所有 *JavaScript* 文件,意味着必须等到全部 *JavaScript* 代码都被下载、解析和执行完成以后,才能开始呈现页面的内容(浏览器在遇到 *body* 标签时才开始呈现内容)。
+>
+> 对于那些需要很多 *JavaScript* 代码的页面来说,这无疑会导致浏览器在呈现页面时出现明显的延迟,而延迟期间的浏览器窗口中将是一片空白。为了避免这个问题,现在 ***Web* 应用程序一般都全部 *JavaScript* 引用放在 *body* 元素中页面的内容后面**。这样一来,在解析包含的 *JavaScript* 代码之前,页面的内容将完全呈现在浏览器中。而用户也会因为浏览器窗口显示空白页面的时间缩短而感到打开页面的速度加快了。
+>
+> 有了 *defer* 和 *async* 后,这种局面得到了改善。
+>
+> ***defer* (延迟脚本)**
+>
+> 延迟脚本:*defer* 属性只适用于外部脚本文件。
+>
+> 如果给 *script* 标签定义了*defer* 属性,这个属性的作用是表明脚本在执行时不会影响页面的构造。也就是说,脚本会被延迟到整个页面都解析完毕后再运行。因此,如果 *script* 元素中设置了 *defer* 属性,相当于告诉浏览器立即下载,但延迟执行。
+>
+> ***async*(异步脚本)**
+>
+> 异步脚本:*async* 属性也只适用于外部脚本文件,并告诉浏览器立即下载文件。
+>
+> **但与 *defer* 不同的是:标记为 *async* 的脚本并不保证按照指定它们的先后顺序执行。**
+>
+> 所以总结起来,两者之间最大的差异就是在于脚本下载完之后何时执行,显然 *defer* 是最接近我们对于应用脚本加载和执行的要求的。
+>
+> *defer* 是立即下载但延迟执行,加载后续文档元素的过程将和脚本的加载并行进行(异步),但是脚本的执行要在所有元素解析完成之后,*DOMContentLoaded* 事件触发之前完成。*async* 是立即下载并执行,加载和渲染后续文档元素的过程将和 *js* 脚本的加载与执行并行进行(异步)。
+
+
+
+### 32. 浏览器事件循环和任务队列
+
+> 参考答案:
+>
+> *JavaScript* 的异步机制由事件循环和任务队列构成。
+>
+> *JavaScript* 本身是单线程语言,所谓异步依赖于浏览器或者操作系统等完成。*JavaScript* 主线程拥有一个执行栈以及一个任务队列,主线程会依次执行代码,当遇到函数时,会先将函数入栈,函数运行完毕后再将该函数出栈,直到所有代码执行完毕。
+>
+> 遇到异步操作(例如:*setTimeout、Ajax*)时,异步操作会由浏览器(*OS*)执行,浏览器会在这些任务完成后,将事先定义的回调函数推入主线程的任务队列(*task queue*)中,当主线程的执行栈清空之后会读取任务队列中的回调函数,当任务队列被读取完毕之后,主线程接着执行,从而进入一个无限的循环,这就是事件循环。
+
+
+
+### 33. 原型与原型链 (美团 19年)
+
+> 参考答案:
+>
+> - 每个对象都有一个 `__proto__ ` 属性,该属性指向自己的原型对象
+> - 每个构造函数都有一个 `prototype ` 属性,该属性指向实例对象的原型对象
+> - 原型对象里的 `constructor` 指向构造函数本身
+>
+> 如下图:
+>
+>
+>
+> 每个对象都有自己的原型对象,而原型对象本身,也有自己的原型对象,从而形成了一条原型链条。
+>
+> 当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
+
+
+
+### 34. 作用域与作用域链 (美团 19年)
+
+> 参考答案:
+>
+> 作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性。*ES6* 之前 *JavaScript* 没有块级作用域,只有全局作用域和函数作用域。*ES6* 的到来,为我们提供了块级作用域。
+>
+> 作用域链指的是作用域与作用域之间形成的链条。当我们查找一个当前作用域没有定义的变量(自由变量)的时候,就会向上一级作用域寻找,如果上一级也没有,就再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链 。
+
+
+
+### 35. 闭包及应用场景以及闭包缺点 (美团 19年)
+
+> 参考答案:
+>
+> 闭包的应用场景:
+>
+> 1. 匿名自执行函数
+> 2. 结果缓存
+> 3. 封装
+> 4. 实现类和继承
+>
+> 闭包的缺点:
+>
+> 因为闭包的作用域链会引用包含它的函数的活动对象,导致这些活动对象不会被销毁,因此会占用更多的内存。
+
+
+
+### 36. 继承方式 (美团 19年)
+
+> 参考答案:
+>
+> 参阅前面第 *9* 题以及第 *18* 题答案。
+
+
+
+### 37. 原始值与引用值 (美团 19年)
+
+> 参考答案:
+>
+> **原始值是表示 *JavaScript* 中可用的数据或信息的最底层形式或最简单形式。**简单类型的值被称为原始值,是因为它们是**不可细化**的。
+>
+> 也就是说,数字是数字,字符是字符,布尔值是 *true* 或 *false*,*null* 和 *undefined* 就是 *null* 和 *undefined*。这些值本身很简单,不能够再进行拆分。由于原始值的数据大小是固定的,所以**原始值的数据是存储于内存中的栈区里面的。**
+>
+> 在 *JavaScript* 中,对象就是一个引用值。因为对象可以向下拆分,拆分成多个简单值或者复杂值。**引用值在内存中的大小是未知的,因为引用值可以包含任何值,而不是一个特定的已知值,所以引用值的数据都是存储于堆区里面。**
+>
+> 最后总结一下两者的区别:
+>
+> 1. 访问方式
+> - 原始值:访问到的是值
+> - 引用值:访问到的是引用地址
+> 2. 比较方式
+> - 原始值:比较的是值
+> - 引用值:比较的是地址
+>
+> 3. 动态属性
+> - 原始值:无法添加动态属性
+> - 引用值:可以添加动态属性
+> 4. 变量赋值
+> - 原始值:赋值的是值
+> - 引用值:赋值的是地址
+
+
+
+### 38. 描述下列代码的执行结果
+
+```js
+const first = () => (new Promise((resolve, reject) => {
+ console.log(3);
+ let p = new Promise((resolve, reject) => {
+ console.log(7);
+ setTimeout(() => {
+ console.log(1);
+ }, 0);
+ setTimeout(() => {
+ console.log(2);
+ resolve(3);
+ }, 0)
+ resolve(4);
+ });
+ resolve(2);
+ p.then((arg) => {
+ console.log(arg, 5); // 1 bb
+ });
+ setTimeout(() => {
+ console.log(6);
+ }, 0);
+}))
+first().then((arg) => {
+ console.log(arg, 7); // 2 aa
+ setTimeout(() => {
+ console.log(8);
+ }, 0);
+});
+setTimeout(() => {
+ console.log(9);
+}, 0);
+console.log(10);
+```
+
+> 参考答案:
+>
+> 3
+> 7
+> 10
+> 4 5
+> 2 7
+> 1
+> 2
+> 6
+> 9
+> 8
+
+
+
+### 39. 如何判断数组或对象(美团 19年)
+
+> 参考答案:
+>
+> 1. 通过 *instanceof* 进行判断
+>
+> ```js
+> var arr = [1,2,3,1];
+> console.log(arr instanceof Array) // true
+> ```
+>
+> 2. 通过对象的 *constructor* 属性
+>
+> ```js
+> var arr = [1,2,3,1];
+> console.log(arr.constructor === Array) // true
+> ```
+>
+> 3. *Object.prototype.toString.call(arr)*
+>
+> ```js
+> console.log(Object.prototype.toString.call({name: "jerry"}));//[object Object]
+> console.log(Object.prototype.toString.call([]));//[object Array]
+> ```
+>
+> 4. 可以通过 *ES6* 新提供的方法 *Array.isArray( )*
+>
+> ```js
+> Array.isArray([]) //true
+> ```
+
+
+
+### 40. 对象深拷贝与浅拷贝,单独问了 *Object.assign*(美团 19年)
+
+> 参考答案:
+>
+> - **浅拷贝**:只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用,我们把这种拷贝叫做浅拷贝(浅复制)
+>
+> 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
+>
+> - **深拷贝**:在堆中重新分配内存,并且把源对象所有属性都进行新建拷贝,以保证深拷贝的对象的引用图不包含任何原有对象或对象图上的任何对象,拷贝后的对象与原来的对象是完全隔离,互不影响。
+>
+>
+>
+> *Object.assign* 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 *Object.assign* 方法进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
+
+
+
+### 42. 说说 *instanceof* 原理,并回答下面的题目(美团 19年)
+
+```js
+function A(){}
+function B(){}
+A.prototype = new B();
+let a = new A();
+console.log(a instanceof B) // true of false ?
+```
+
+> 参考答案:
+>
+> 答案为 *true*。
+>
+> *instanceof* 原理:
+>
+> *instanceof* 用于检测一个对象是否为某个构造函数的实例。
+>
+> 例如:*A instanceof B*
+> *instanceof* 用于检测对象 *A* 是不是 *B* 的实例,而检测是基于原型链进行查找的,也就是说 *B* 的 *prototype* 有没有在对象 *A* 的\__*proto*__ 原型链上,如果有就返回 *true*,否则返回 *false*
+
+###
+
+### 43. 内存泄漏(美团 19 年)
+
+> 参考答案:
+>
+> 内存泄漏(*Memory Leak*)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
+>
+> *Javascript* 是一种高级语言,它不像 *C* 语言那样要手动申请内存,然后手动释放,*Javascript* 在声明变量的时候自动会分配内存,普通的类型比如 *number*,一般放在栈内存里,对象放在堆内存里,声明一个变量,就分配一些内存,然后定时进行垃圾回收。垃圾回收的任务由 *JavaScript* 引擎中的垃圾回收器来完成,它监视所有对象,并删除那些不可访问的对象。
+>
+> 基本的垃圾回收算法称为**“标记-清除”**,定期执行以下“垃圾回收”步骤:
+>
+> - 垃圾回收器获取根并**“标记”**(记住)它们。
+> - 然后它访问并“标记”所有来自它们的引用。
+> - 然后它访问标记的对象并标记它们的引用。所有被访问的对象都被记住,以便以后不再访问同一个对象两次。
+> - 以此类推,直到有未访问的引用(可以从根访问)为止。
+> - 除标记的对象外,所有对象都被删除。
+
+
+
+### 44. *ES6* 新增哪些东西?让你自己说(美团 19 年)
+
+> 参考答案:
+>
+> *ES6* 新增内容众多,这里列举出一些关键的以及平时常用的新增内容:
+>
+> 1. 箭头函数
+> 2. 字符串模板
+> 3. 支持模块化(*import、export*)
+> 4. 类(*class、constructor、extends*)
+> 5. *let、const* 关键字
+> 6. 新增一些数组、字符串等内置构造函数方法,例如 *Array.from*、*Array.of* 、*Math.sign*、*Math.trunc* 等
+> 7. 新增一些语法,例如扩展操作符、解构、函数默认参数等
+> 8. 新增一种基本数据类型 *Symbol*
+> 9. 新增元编程相关,例如 *proxy*、*Reflect*
+> 10. *Set* 和 *Map* 数据结构
+> 11. *Promise*
+> 12. *Generator* 生成器
+
+
+
+### 45. *weakmap、weakset*(美团 *19* 年)
+
+> 参考答案:
+>
+> *WeakSet* 对象是一些对象值的集合, 并且其中的每个对象值都只能出现一次。在 *WeakSet* 的集合中是唯一的
+>
+> 它和 *Set* 对象的区别有两点:
+>
+> - 与 *Set* 相比,*WeakSet* 只能是**对象的集合**,而不能是任何类型的任意值。
+> - *WeakSet* 持弱引用:集合中对象的引用为弱引用。 如果没有其他的对 *WeakSet* 中对象的引用,那么这些对象会被当成垃圾回收掉。 这也意味着 *WeakSet* 中没有存储当前对象的列表。 正因为这样,*WeakSet* 是不可枚举的。
+>
+> *WeakMap* 对象也是键值对的集合。它的**键必须是对象类型**,值可以是任意类型。它的键被弱保持,也就是说,当其键所指对象没有其他地方引用的时候,它会被 *GC* 回收掉。*WeakMap* 提供的接口与 *Map* 相同。
+>
+> 与 *Map* 对象不同的是,*WeakMap* 的键是不可枚举的。不提供列出其键的方法。列表是否存在取决于垃圾回收器的状态,是不可预知的。
+
+
+
+### 46. 为什么 *ES6* 会新增 *Promise*(美团 19年)
+
+> 参考答案:
+>
+> 在 *ES6* 以前,解决异步的方法是回调函数。但是回调函数有一个最大的问题就是回调地狱(*callback hell*),当我们的回调函数嵌套的层数过多时,就会导致代码横向发展。
+>
+> *Promise* 的出现就是为了解决回调地狱的问题。
+
+
+
+### 47. *ES5* 实现继承?(虾皮)
+
+> 参考答案:
+>
+> 1. 借用构造函数实现继承
+>
+> ```js
+> function Parent1(){
+> this.name = "parent1"
+> }
+> function Child1(){
+> Parent1.call(this);
+> this.type = "child1";
+> }
+> ```
+>
+> 缺点:*Child1* 无法继承 *Parent1* 的原型对象,并没有真正的实现继承 (部分继承)。
+>
+> 2. 借用原型链实现继承
+>
+> ```js
+> function Parent2(){
+> this.name = "parent2";
+> this.play = [1,2,3];
+> }
+> function Child2(){
+> this.type = "child2";
+> }
+> Child2.prototype = new Parent2();
+> ```
+>
+> 缺点:原型对象的属性是共享的。
+>
+> 3. 组合式继承
+>
+> ```js
+> function Parent3(){
+> this.name = "parent3";
+> this.play = [1,2,3];
+> }
+> function Child3(){
+> Parent3.call(this);
+> this.type = "child3";
+> }
+> Child3.prototype = Object.create(Parent3.prototype);
+> Child3.prototype.constructor = Child3;
+> ```
+
+
+
+### 48. 科里化?(搜狗)
+
+> 参考答案:
+>
+> 柯里化,英语全称 *Currying*,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
+>
+> 举个例子,就是把原本:
+>
+> *function(arg1,arg2)* 变成 *function(arg1)(arg2)*
+> *function(arg1,arg2,arg3)* 变成 *function(arg1)(arg2)(arg3)*
+> *function(arg1,arg2,arg3,arg4)* 变成 *function(arg1)(arg2)(arg3)(arg4)*
+>
+> 总而言之,就是将:
+>
+> *function(arg1,arg2,…,argn)* 变成 *function(arg1)(arg2)…(argn)*
+
+
+
+### 49. 防抖和节流?(虾皮)
+
+> 参考答案:
+>
+> 我们在平时开发的时候,会有很多场景会频繁触发事件,比如说搜索框实时发请求,*onmousemove、resize、onscroll* 等,有些时候,我们并不能或者不想频繁触发事件,这时候就应该用到函数防抖和函数节流。
+>
+> 函数防抖(*debounce*),指的是短时间内多次触发同一事件,只执行最后一次,或者只执行最开始的一次,中间的不执行。
+>
+> 函数节流(*throttle*),指连续触发事件但是在 *n* 秒中只执行一次函数。即 *2n* 秒内执行 *2* 次... 。节流如字面意思,会稀释函数的执行频率。
+
+
+
+### 50. 闭包?(好未来---探讨了 *40* 分钟)
+
+> 参考答案:
+>
+> 请参阅前面第 *20* 题以及第 *36* 题答案。
+
+
+
+### 51. 原型和原型链?(字节)
+
+> 参考答案:
+>
+> 请参阅前面第 *34* 题答案。
+
+
+
+### 52. 排序算法---(时间复杂度、空间复杂度)
+
+> 参考答案:
+>
+> 算法(*Algorithm*)是指用来操作数据、解决程序问题的一组方法。对于同一个问题,使用不同的算法,也许最终得到的结果是一样的,但在过程中消耗的资源和时间却会有很大的区别。
+>
+> 主要还是从算法所占用的「时间」和「空间」两个维度去考量。
+>
+> - 时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。
+> - 空间维度:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述。
+>
+> 因此,评价一个算法的效率主要是看它的时间复杂度和空间复杂度情况。然而,有的时候时间和空间却又是「鱼和熊掌」,不可兼得的,那么我们就需要从中去取一个平衡点。
+>
+> 排序也称排序算法(*Sort Algorithm*),排序是将**一组数据**,依**指定的顺序**进行**排列的过程**。
+>
+> 排序的分类分为**内部排序**和**外部排序法**。
+>
+> - 内部排序:指将需要处理的所有数据都加载到**内部存储器(内存)**中进行排序。
+> - 外部排序:**数据量过大**,无法全部加载到内存中,需要借助**外部存储(文件等)**进行排序。
+>
+>
+
+
+
+### 53. 浏览器事件循环和 *node* 事件循环(搜狗)
+
+> 参考答案:
+>
+> 1. 浏览器中的 *Event Loop*
+>
+> 事件循环中的异步队列有两种:*macro*(宏任务)队列和 *micro*(微任务)队列。**宏任务队列可以有多个,微任务队列只有一个**。
+>
+> - 常见的 *macro-task* 比如:*setTimeout、setInterval、 setImmediate、script*(整体代码)、 *I/O* 操作、*UI* 渲染等。
+> - 常见的 *micro-task* 比如: *process.nextTick、new Promise( ).then*(回调)、*MutationObserver*(*html5* 新特性) 等。
+>
+> 当某个宏任务执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务,依次类推。
+>
+> 2. *Node* 中的事件循环
+>
+> *Node* 中的 *Event Loop* 和浏览器中的是完全不相同的东西。*Node.js* 采用 *V8* 作为 *js* 的解析引擎,而 *I/O* 处理方面使用了自己设计的 *libuv*,*libuv* 是一个基于事件驱动的跨平台抽象层,封装了不同操作系统一些底层特性,对外提供统一的 *API*,事件循环机制也是它里面的实现。
+>
+> *Node.JS* 的事件循环分为 *6* 个阶段:
+>
+> - *timers* 阶段:这个阶段执行 *timer*( *setTimeout、setInterval* )的回调
+> - *I/O callbacks* 阶段:处理一些上一轮循环中的少数未执行的 *I/O* 回调
+> - *idle、prepare* 阶段:仅 *Node.js* 内部使用
+> - *poll* 阶段:获取新的 *I/O* 事件, 适当的条件下 *Node.js* 将阻塞在这里
+> - *check* 阶段:执行 *setImmediate( )* 的回调
+> - *close callbacks* 阶段:执行 *socket* 的 *close* 事件回调
+>
+> *Node.js* 的运行机制如下:
+>
+> - *V8* 引擎解析 *JavaScript* 脚本。
+> - 解析后的代码,调用 *Node API*。
+> - *libuv* 库负责 *Node API* 的执行。它将不同的任务分配给不同的线程,形成一个 *Event Loop*(事件循环),以异步的方式将任务的执行结果返回给 *V8* 引擎。
+> - *V8* 引擎再将结果返回给用户。
+
+
+
+### 54. 闭包的好处
+
+> 参考答案:
+>
+> 请参阅前面第 *20* 题以及第 *36* 题答案。
+
+
+
+### 55. *let、const、var* 的区别
+
+> 参考答案:
+>
+> 1. *var* 定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问,有变量提升。
+> 2. *let* 定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问,无变量提升,不可以重复声明。
+> 3. *const* 用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改,无变量提升,不可以重复声明。
+
+
+
+### 56. 闭包、作用域(可以扩充到作用域链)
+
+> 参考答案:
+>
+> **什么是作业域?**
+>
+> ES5 中只存在两种作用域:全局作用域和函数作用域。在 JavaScript 中,我们将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套子作用域中根据标识符名称进行变量(变量名或者函数名)查找。
+>
+> **什么是作用域链?**
+>
+> 当访问一个变量时,编译器在执行这段代码时,会首先从当前的作用域中查找是否有这个标识符,如果没有找到,就会去父作用域查找,如果父作用域还没找到继续向上查找,直到全局作用域为止,,而作用域链,就是有当前作用域与上层作用域的一系列变量对象组成,它保证了当前执行的作用域对符合访问权限的变量和函数的有序访问。
+>
+> **闭包产生的本质**
+>
+> 当前环境中存在指向父级作用域的引用
+>
+> **什么是闭包**
+>
+> 闭包是一种特殊的对象,它由两部分组成:执行上下文(代号 A),以及在该执行上下文中创建的函数 (代号 B),当 B 执行时,如果访问了 A 中变量对象的值,那么闭包就会产生,且在 Chrome 中使用这个执行上下文 A 的函数名代指闭包。
+>
+> **一般如何产生闭包**
+>
+> - 返回函数
+> - 函数当做参数传递
+>
+> **闭包的应用场景**
+>
+> - 柯里化 bind
+> - 模块
+
+
+
+### 57. *Promise*
+
+> 参考答案:
+>
+> *Promise* 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理且更强大。它最早由社区提出并实现,*ES6*将其写进了语言标准,统一了用法,并原生提供了*Promise*对象。
+>
+> **特点**
+>
+> 1. 对象的状态不受外界影响 (*3* 种状态)
+>
+> - *Pending* 状态(进行中)
+>
+> - *Fulfilled* 状态(已成功)
+> - *Rejected* 状态(已失败)
+>
+> 2. 一旦状态改变就不会再变 (两种状态改变:成功或失败)
+>
+> - *Pending* -> *Fulfilled*
+> - *Pending* -> *Rejected*
+>
+> **用法**
+>
+> ```js
+> var promise = new Promise(function(resolve, reject){
+> // ... some code
+>
+> if (/* 异步操作成功 */) {
+> resolve(value);
+> } else {
+> reject(error);
+> }
+> })
+> ```
+
+
+
+### 58. 实现一个函数,对一个url进行请求,失败就再次请求,超过最大次数就走失败回调,任何一次成功都走成功回调
+
+> 参考答案:
+>
+> 示例代码如下:
+>
+> ```js
+> /**
+> @params url: 请求接口地址;
+> @params body: 设置的请求体;
+> @params succ: 请求成功后的回调
+> @params error: 请求失败后的回调
+> @params maxCount: 设置请求的数量
+> */
+> function request(url, body, succ, error, maxCount = 5) {
+> return fetch(url, body)
+> .then(res => succ(res))
+> .catch(err => {
+> if (maxCount <= 0) return error('请求超时');
+> return request(url, body, succ, error, --maxCount);
+> });
+> }
+>
+> // 调用请求函数
+> request('https://java.some.com/pc/reqCount', { method: 'GET', headers: {} },
+> (res) => {
+> console.log(res.data);
+> },
+> (err) => {
+> console.log(err);
+> })
+>
+> ```
+
+
+
+### 59. 冒泡排序
+
+> 参考答案:
+>
+> 冒泡排序的核心思想是:
+>
+> 1. 比较相邻的两个元素,如果前一个比后一个大或者小(取决于排序的顺序是小到大还是大到小),则交换位置。
+> 2. 比较完第一轮的时候,最后一个元素是最大或最小的元素。
+> 3. 这时候最后一个元素已经是最大或最小的了,所以下一次冒泡的时候最后一个元素不需要参与比较。
+>
+> 示例代码:
+>
+> ```js
+> function bSort(arr) {
+> var len = arr.length;
+> // 外层 for 循环控制冒泡的次数
+> for (var i = 0; i < len - 1; i++) {
+> for (var j = 0; j < len - 1 - i; j++) {
+> // 内层 for 循环控制每一次冒泡需要比较的次数
+> // 因为之后每一次冒泡的两两比较次数会越来越少,所以 -i
+> if (arr[j] > arr[j + 1]) {
+> var temp = arr[j];
+> arr[j] = arr[j + 1];
+> arr[j + 1] = temp;
+> }
+> }
+> }
+> return arr;
+> }
+>
+> //举个数组
+> myArr = [20, -1, 27, -7, 35];
+> //使用函数
+> console.log(bSort(myArr)); // [ -7, -1, 20, 27, 35 ]
+> ```
+
+
+
+### 60. 数组降维
+
+> 参考答案:
+>
+> 数组降维就是将一个嵌套多层的数组进行降维操作,也就是对数组进行扁平化。在 *ES5* 时代我们需要自己手写方法或者借助函数库来完成,但是现在可以使用 *ES6* 新提供的数组方法 *flat* 来完成数组降维操作。
+
+> 解析:使用 *flat* 方法会接收一个参数,这个参数是数值类型,是要处理扁平化数组的深度,生成后的新数组是独立存在的,不会对原数组产生影响。
+>
+> *flat* 方法的语法如下:
+>
+> ```js
+> var newArray = arr.flat([depth])
+> ```
+>
+> 其中 *depth* 指定要提取嵌套数组结构的深度,默认值为 *1*。
+>
+> 示例如下:
+>
+> ```js
+> var arr = [1, 2, [3, 4, [5, 6]]];
+> console.log(arr.flat()); // [1, 2, 3, 4, [5, 6]]
+> console.log(arr.flat(2)); // [1, 2, 3, 4, 5, 6]
+> ```
+>
+> 上面的代码定义了一个层嵌套的数组,默认情况下只会拍平一层数组,也就是把原来的三维数组降低到了二维数组。在传入的参数为 *2* 时,则会降低两维,成为一个一维数组。
+>
+> 使用 *Infinity*,可展开任意深度的嵌套数组,示例如下:
+>
+> ```js
+> var arr = [1, 2, [3, 4, [5, 6, [7, 8]]]];
+> console.log(arr.flat(Infinity)); // [1, 2, 3, 4, 5, 6, 7, 8]
+> ```
+>
+> 在数组中有空项的时候,使用 *flat* 方法会将中的空项进行移除。
+>
+> ```js
+> var arr = [1, 2, , 4, 5];
+> console.log(arr.flat()); // [1, 2, 4, 5]
+> ```
+>
+> 上面的代码中,数组中第三项是空值,在使用 *flat* 后会对空项进行移除。
+
+
+
+### 61. *call apply bind*
+
+> 参考答案:
+>
+> 请参阅前面第 *11* 题答案。
+
+
+
+### 62. promise 代码题
+
+```js
+new Promise((resolve, reject) => {
+ reject(1);
+ console.log(2);
+ resolve(3);
+ console.log(4);
+}).then((res) => { console.log(res) })
+ .catch(res => { console.log('reject1') })
+try {
+ new Promise((resolve, reject) => {
+ throw 'error'
+ }).then((res) => { console.log(res) })
+ .catch(res => { console.log('reject2') })
+} catch (err) {
+ console.log(err)
+}
+```
+
+>参考答案:
+>
+>2
+>4
+>reject1
+>reject2
+>
+>直播课或者录播课进行解析。
+
+
+
+### 63. *proxy* 是实现代理,可以改变 *js* 底层的实现方式, 然后说了一下和 *Object.defineProperty* 的区别
+
+>参考答案:
+>
+>两者的区别总结如下:
+>
+>- 代理原理:Object.defineProperty的原理是通过将数据属性转变为存取器属性的方式实现的属性读写代理。而Proxy则是因为这个内置的Proxy对象内部有一套监听机制,在传入handler对象作为参数构造代理对象后,一旦代理对象的某个操作触发,就会进入handler中对应注册的处理函数,此时我们就可以有选择的使用Reflect将操作转发被代理对象上。
+>- 代理局限性:Object.defineProperty始终还是局限于属性层面的读写代理,对于对象层面以及属性的其它操作代理它都无法实现。鉴于此,由于数组对象push、pop等方法的存在,它对于数组元素的读写代理实现的并不完全。而使用Proxy则可以很方便的监视数组操作。
+>- 自我代理:Object.defineProperty方式可以代理到自身(代理之后使用对象本身即可),也可以代理到别的对象身上(代理之后需要使用代理对象)。Proxy方式只能代理到Proxy实例对象上。这一点在其它说法中是Proxy对象不需要侵入对象就可以实现代理,实际上Object.defineProperty方式也可以不侵入。
+
+
+
+### 64. 使用 *ES5* 与 *ES6* 分别实现继承
+
+>参考答案:
+>
+>如果是使用 *ES5* 来实现继承,那么现在的最优解是使用圣杯模式。圣杯模式的核心思想就是不通过调用父类构造函数来给子类原型赋值,而是取得父类原型的一个副本,然后将返回的新对象赋值给子类原型。具体代码可以参阅前面第 *9* 题的解析。
+>
+>*ES6* 新增了 *extends* 关键字,直接使用该关键字就能够实现继承。
+
+
+
+### 65. 深拷贝
+
+> 参考答案:
+>
+> 有深拷贝就有浅拷贝。
+>
+> 浅拷贝就是只拷贝对象的引用,而不深层次的拷贝对象的值,多个对象指向堆内存中的同一对象,任何一个修改都会使得所有对象的值修改,因为它们共用一条数据。
+>
+> 深拷贝不是单纯的拷贝一份引用数据类型的引用地址,而是将引用类型的值全部拷贝一份,形成一个新的引用类型,这样就不会发生引用错乱的问题,使得我们可以多次使用同样的数据,而不用担心数据之间会起冲突。
+
+> 解析:
+>
+> 「深拷贝」就是在拷贝数据的时候,将数据的所有**引用结构**都拷贝一份。简单的说就是,在内存中存在两个数据结构完全相同又相互独立的数据,将引用型类型进行复制,而不是只复制其引用关系。
+>
+> 分析下怎么做「深拷贝」:
+>
+> 1. 首先假设深拷贝这个方法已经完成,为 deepClone
+> 2. 要拷贝一个数据,我们肯定要去遍历它的属性,如果这个对象的属性仍是对象,继续使用这个方法,如此往复
+>
+> ```
+> function deepClone(o1, o2) {
+> for (let k in o2) {
+> if (typeof o2[k] === 'object') {
+> o1[k] = {};
+> deepClone(o1[k], o2[k]);
+> } else {
+> o1[k] = o2[k];
+> }
+> }
+> }
+> // 测试用例
+> let obj = {
+> a: 1,
+> b: [1, 2, 3],
+> c: {}
+> };
+> let emptyObj = Object.create(null);
+> deepClone(emptyObj, obj);
+> console.log(emptyObj.a == obj.a);
+> console.log(emptyObj.b == obj.b);
+> ```
+>
+> 递归容易造成爆栈,尾部调用可以解决递归的这个问题,*Chrome* 的 *V8* 引擎做了尾部调用优化,我们在写代码的时候也要注意尾部调用写法。递归的爆栈问题可以通过将递归改写成枚举的方式来解决,就是通过 *for* 或者 *while* 来代替递归。
+
+
+
+### 66. *async* 与 *await* 的作用
+
+>参考答案:
+>
+> *async* 是一个修饰符,*async* 定义的函数会默认的返回一个 *Promise* 对象 *resolve* 的值,因此对 *async* 函数可以直接进行 *then* 操作,返回的值即为 *then* 方法的传入函数。
+>
+>*await* 关键字只能放在 *async* 函数内部, *await* 关键字的作用就是获取 *Promise* 中返回的内容, 获取的是 *Promise* 函数中 *resolve* 或者 *reject* 的值。
+
+
+
+### 67. 数据的基础类型(原始类型)有哪些
+
+>参考答案:
+>
+>*JavaScript* 中的基础数据类型,一共有 *6* 种:
+>
+>*string,symbol,number,boolean,undefined,null*
+>
+>其中 *symbol* 类型是在 *ES6* 里面新添加的基本数据类型。
+
+
+
+### 68. *typeof null* 返回结果
+
+> 参考答案:
+>
+> 返回 *object*
+
+> 解析:至于为什么会返回 *object*,这实际上是来源于 *JavaScript* 从第一个版本开始时的一个 *bug*,并且这个 *bug* 无法被修复。修复会破坏现有的代码。
+>
+> 原理这是这样的,不同的对象在底层都表现为二进制,在 *JavaScript* 中二进制前三位都为 *0* 的话会被判断为 *object* 类型,*null* 的二进制全部为 *0*,自然前三位也是 *0*,所以执行 *typeof* 值会返回 *object*。
+
+
+
+### 69. 对变量进行类型判断的方式有哪些
+
+> 参考答案:
+>
+> 常用的方法有 *4* 种:
+>
+> 1. *typeof*
+>
+> *typeof* 是一个操作符,其右侧跟一个一元表达式,并返回这个表达式的数据类型。返回的结果用该类型的字符串(全小写字母)形式表示,包括以下 *7* 种:*number、boolean、symbol、string、object、undefined、function* 等。
+>
+> 2. *instanceof*
+>
+> *instanceof* 是用来判断 *A* 是否为 *B* 的实例,表达式为:*A instanceof B*,如果 *A* 是 *B* 的实例,则返回 *true*,否则返回 *false*。 在这里需要特别注意的是:*instanceof* 检测的是原型。
+>
+> 3. *constructor*
+>
+> 当一个函数 *F* 被定义时,*JS* 引擎会为 *F* 添加 *prototype* 原型,然后再在 *prototype* 上添加一个 *constructor* 属性,并让其指向 *F* 的引用。
+>
+> 4. *toString*
+>
+> *toString( )* 是 *Object* 的原型方法,调用该方法,默认返回当前对象的 *[[Class]]* 。这是一个内部属性,其格式为 *[object Xxx]* ,其中 *Xxx* 就是对象的类型。
+>
+> 对于 *Object* 对象,直接调用 *toString( )* 就能返回 *[object Object]* 。而对于其他对象,则需要通过 *call / apply* 来调用才能返回正确的类型信息。例如:
+>
+> ```js
+> Object.prototype.toString.call('') ; // [object String]
+> Object.prototype.toString.call(1) ; // [object Number]
+> Object.prototype.toString.call(true) ;// [object Boolean]
+> Object.prototype.toString.call(Symbol());//[object Symbol]
+> Object.prototype.toString.call(undefined) ;// [object Undefined]
+> Object.prototype.toString.call(null) ;// [object Null]
+> ```
+
+
+
+### 70. *typeof* 与 *instanceof* 的区别? *instanceof* 是如何实现?
+
+> 参考答案:
+>
+> 1. *typeof*
+>
+> *typeof* 是一个操作符,其右侧跟一个一元表达式,并返回这个表达式的数据类型。返回的结果用该类型的字符串(全小写字母)形式表示,包括以下 *7* 种:*number、boolean、symbol、string、object、undefined、function* 等。
+>
+> 2. *instanceof*
+>
+> *instanceof* 是用来判断 *A* 是否为 *B* 的实例,表达式为:*A instanceof B*,如果 *A* 是 *B* 的实例,则返回 *true*,否则返回 *false*。 在这里需要特别注意的是:*instanceof* 检测的是原型。
+>
+> 用一段伪代码来模拟其内部执行过程:
+>
+> ```js
+> instanceof (A,B) = {
+> varL = A.__proto__;
+> varR = B.prototype;
+> if(L === R) {
+> // A的内部属性 __proto__ 指向 B 的原型对象
+> return true;
+> }
+> return false;
+> }
+> ```
+>
+> 从上述过程可以看出,当 *A* 的 \__*proto*__ 指向 *B* 的 *prototype* 时,就认为 *A* 就是 *B* 的实例。
+>
+> 需要注意的是,*instanceof* 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型。
+>
+> 例如:*[ ] instanceof Object* 返回的也会是 *true*。
+
+
+
+### 71. 引用类型有哪些,有什么特点
+
+>参考答案:
+>
+>JS 中七种内置类型(*null,undefined,boolean,number,string,symbol,object*)又分为两大类型
+>
+>两大类型:
+>
+>- 基本类型: `null`,`undefined`,`boolean`,`number`,`string`,`symbol`
+>- 引用类型Object: `Array` ,`Function`, `Date`, `RegExp`等
+>
+>
+>
+>基本类型和引用类型的主要区别有以下几点:
+>
+>**存放位置:**
+>
+>- 基本数据类型:基本类型值在内存中占据固定大小,直接存储在**栈内存**中的数据
+>- 引用数据类型:引用类型在栈中存储了指针,这个指针指向堆内存中的地址,真实的数据存放在**堆内存**里。
+>
+>
+>
+>**值的可变性:**
+>
+>- 基本数据类型: 值不可变,*javascript* 中的原始值(*undefined、null*、布尔值、数字和字符串)是不可更改的
+>
+>- 引用数据类型:引用类型是可以直接改变其值的
+>
+>**比较:**
+>
+>- 基本数据类型: 基本类型的比较是值的比较,只要它们的值相等就认为他们是相等的
+>
+>- 引用数据类型: 引用数据类型的比较是引用的比较,看其的引用是否指向同一个对象
+
+
+
+### 72. 如何得到一个变量的类型---指函数封装实现
+
+>参考答案:
+>
+>请参阅前面第 *30* 题答案。
+
+
+
+### 73. 什么是作用域、闭包
+
+>参考答案:
+>
+>请参阅前面第 *56* 题。
+
+
+
+### 74. 闭包的缺点是什么?闭包的应用场景有哪些?怎么销毁闭包?
+
+>参考答案:
+>
+>闭包是指有权访问另外一个函数作用域中的变量的函数。
+>
+>因为闭包引用着另一个函数的变量,导致另一个函数已经不使用了也无法销毁,所以**闭包使用过多,会占用较多的内存,这也是一个副作用,内存泄漏。**
+>
+>如果要销毁一个闭包,可以 把被引用的变量设置为*null*,即手动清除变量,这样下次 *js* 垃圾回收机制回收时,就会把设为 *null* 的量给回收了。
+>
+>闭包的应用场景:
+>
+>1. 匿名自执行函数
+>2. 结果缓存
+>3. 封装
+>4. 实现类和继承
+
+
+
+### 75. *JS*的垃圾回收站机制
+
+>参考答案:
+>
+>*JS* 具有自动垃圾回收机制。垃圾收集器会按照固定的时间间隔周期性的执行。
+>
+>*JS* 常见的垃圾回收方式:标记清除、引用计数方式。
+>
+>1、标记清除方式:
+>
+>- 工作原理:当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。
+>
+>- 工作流程:
+>
+> - 垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记;
+>
+> - 去掉环境中的变量以及被环境中的变量引用的变量的标记;
+>
+> - 被加上标记的会被视为准备删除的变量;
+>
+> - 垃圾回收器完成内存清理工作,销毁那些带标记的值并回收他们所占用的内存空间。
+>
+>2、引用计数方式:
+>
+>- 工作原理:跟踪记录每个值被引用的次数。
+>
+>- 工作流程:
+>
+> - 声明了一个变量并将一个引用类型的值赋值给这个变量,这个引用类型值的引用次数就是 *1*;
+>
+> - 同一个值又被赋值给另一个变量,这个引用类型值的引用次数加1;
+>
+> - 当包含这个引用类型值的变量又被赋值成另一个值了,那么这个引用类型值的引用次数减 *1*;
+>
+> - 当引用次数变成 *0* 时,说明没办法访问这个值了;
+>
+> - 当垃圾收集器下一次运行时,它就会释放引用次数是0的值所占的内存。
+
+
+
+### 76. 什么是作用域链、原型链
+
+>参考答案:
+>
+>**什么是作用域链?**
+>
+>当访问一个变量时,编译器在执行这段代码时,会首先从当前的作用域中查找是否有这个标识符,如果没有找到,就会去父作用域查找,如果父作用域还没找到继续向上查找,直到全局作用域为止,,而作用域链,就是有当前作用域与上层作用域的一系列变量对象组成,它保证了当前执行的作用域对符合访问权限的变量和函数的有序访问。
+>
+>**什么原型链?**
+>
+>每个对象都可以有一个原型\__*proto*__,这个原型还可以有它自己的原型,以此类推,形成一个原型链。查找特定属性的时候,我们先去这个对象里去找,如果没有的话就去它的原型对象里面去,如果还是没有的话再去向原型对象的原型对象里去寻找。这个操作被委托在整个原型链上,这个就是我们说的原型链。
+
+
+
+### 77. *new* 一个构造函数发生了什么
+
+>参考答案:
+>
+>*new* 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
+>
+>*new* 关键字会进行如下的操作:
+>步骤 *1*:创建一个空的简单 *JavaScript* 对象,即 { } ;
+>步骤 *2*:链接该对象到另一个对象(即设置该对象的原型对象);
+>步骤 *3*:将步骤 *1* 新创建的对象作为 *this* 的上下文;
+>步骤 *4*:如果该函数没有返回对象,则返回 *this*。
+
+
+
+### 78. 对一个构造函数实例化后. 它的原型链指向什么
+
+>参考答案:
+>
+>指向该构造函数实例化出来对象的原型对象。
+>
+>对于构造函数来讲,可以通过 *prototype* 访问到该对象。
+>
+>对于实例对象来讲,可以通过隐式属性 \__*proto*__ 来访问到。
+
+
+
+### 79. 什么是变量提升
+
+>参考答案:
+>
+>当 *JavaScript* 编译所有代码时,所有使用 *var* 的变量声明都被提升到它们的函数/局部作用域的顶部(如果在函数内部声明的话),或者提升到它们的全局作用域的顶部(如果在函数外部声明的话),而不管实际的声明是在哪里进行的。这就是我们所说的“提升”。
+>
+>请记住,这种“提升”实际上并不发生在你的代码中,而只是一种比喻,与 *JavaScript* 编译器如何读取你的代码有关。记住当我们想到“提升”的时候,我们可以想象任何被提升的东西都会被移动到顶部,但是实际上你的代码并不会被修改。
+>
+>函数声明也会被提升,但是被提升到了最顶端,所以将位于所有变量声明之上。
+>
+>在编译阶段变量和函数声明会被放入内存中,但是你在代码中编写它们的位置会保持不变。
+
+
+
+### 80. == 和 === 的区别是什么
+
+> 参考答案:
+>
+> 简单来说: == 代表相同, === 代表严格相同(数据类型和值都相等)。
+>
+> 当进行双等号比较时候,先检查两个操作数数据类型,如果相同,则进行===比较,如果不同,则愿意为你进行一次类型转换,转换成相同类型后再进行比较,而 === 比较时,如果类型不同,直接就是false。
+>
+> 从这个过程来看,大家也能发现,某些情况下我们使用 === 进行比较效率要高些,因此,没有歧义的情况下,不会影响结果的情况下,在 *JS* 中首选 === 进行逻辑比较。
+
+
+
+### 81. *Object.is* 方法比较的是什么
+
+>参考答案:
+>
+>*Object.is* 方法是 *ES6* 新增的用来比较两个值是否严格相等的方法,与 === (严格相等)的行为基本一致。不过有两处不同:
+>
+>- +0 不等于 -0。
+>- *NaN* 等于自身。
+>
+>所以可以将*Object.is* 方法看作是加强版的严格相等。
+
+
+
+### 82. 基础数据类型和引用数据类型,哪个是保存在栈内存中?哪个是在堆内存中?
+
+> 参考答案:
+>
+> 在 *ECMAScript* 规范中,共定义了 *7* 种数据类型,分为 **基本类型** 和 **引用类型** 两大类,如下所示:
+>
+> - **基本类型**:*String、Number、Boolean、Symbol、Undefined、Null*
+>
+> - **引用类型**:*Object*
+>
+> 基本类型也称为简单类型,由于其占据空间固定,是简单的数据段,为了便于提升变量查询速度,将其存储在栈中,即按值访问。
+>
+> 引用类型也称为复杂类型,由于其值的大小会改变,所以不能将其存放在栈中,否则会降低变量查询速度,因此,其值存储在堆(*heap*)中,而存储在变量处的值,是一个指针,指向存储对象的内存处,即按址访问。引用类型除 *Object* 外,还包括 *Function 、Array、RegExp、Date* 等等。
+
+
+
+### 83. 箭头函数解决了什么问题?
+
+> 参考答案:
+>
+> 箭头函数主要解决了 *this* 的指向问题。
+
+> 解析:
+>
+> 在 *ES5* 时代,一旦对象的方法里面又存在函数,则 *this* 的指向往往会让开发人员抓狂。
+>
+> 例如:
+>
+> ```js
+> //错误案例,this 指向会指向 Windows 或者 undefined
+> var obj = {
+> age: 18,
+> getAge: function () {
+> var a = this.age; // 18
+> var fn = function () {
+> return new Date().getFullYear() - this.age; // this 指向 window 或 undefined
+> };
+> return fn();
+> }
+> };
+> console.log(obj.getAge()); // NaN
+> ```
+>
+> 然而,箭头函数没有 *this*,箭头函数的 *this* 是继承父执行上下文里面的 *this*
+>
+> ```js
+> var obj = {
+> age: 18,
+> getAge: function () {
+> var a = this.age; // 18
+> var fn = () => new Date().getFullYear() - this.age; // this 指向 obj 对象
+> return fn();
+> }
+> };
+>
+> console.log(obj.getAge()); // 2003
+> ```
+
+
+
+### 84. *new* 一个箭头函数后,它的 *this* 指向什么?
+
+>参考答案:
+>
+>我不知道这道题是出题人写错了还是故意为之。
+>
+>箭头函数无法用来充当构造函数,所以是无法 *new* 一个箭头函数的。
+>
+>当然,也有可能是面试官故意挖的一个坑,等着你往里面跳。
+
+
+
+### 85. *promise* 的其他方法有用过吗?如 *all、race*。请说下这两者的区别
+
+>参考答案:
+>
+>*promise.all* 方法参数是一个 *promise* 的数组,只有当所有的 *promise* 都完成并返回成功,才会调用 *resolve*,当有一个失败,都会进*catch*,被捕获错误,*promise.all* 调用成功返回的结果是每个 *promise* 单独调用成功之后返回的结果组成的数组,如果调用失败的话,返回的则是第一个 *reject* 的结果
+>
+>*promise.race* 也会调用所有的 *promise*,返回的结果则是所有 *promise* 中最先返回的结果,不关心是成功还是失败。
+
+
+
+### 86. *class* 是如何实现的
+
+>参考答案:
+>
+>*class* 是 *ES6* 新推出的关键字,它是一个语法糖,本质上就是基于这个原型实现的。只不过在以前 *ES5* 原型实现的基础上,添加了一些 *_classCallCheck、_defineProperties、_createClass*等方法来做出了一些特殊的处理。
+>
+>例如:
+>
+>```js
+>class Hello {
+> constructor(x) {
+> this.x = x;
+> }
+> greet() {
+> console.log("Hello, " + this.x)
+> }
+>}
+>```
+>
+>```js
+>"use strict";
+>
+>function _classCallCheck(instance, Constructor) {
+> if (!(instance instanceof Constructor)) {
+> throw new TypeError("Cannot call a class as a function");
+> }
+>}
+>
+>function _defineProperties(target, props) {
+> for (var i = 0; i < props.length; i++) {
+> var descriptor = props[i];
+> descriptor.enumerable = descriptor.enumerable || false;
+> descriptor.configurable = true;
+> if ("value" in descriptor)
+> descriptor.writable = true;
+> Object.defineProperty(target, descriptor.key, descriptor);
+> }
+>}
+>
+>function _createClass(Constructor, protoProps, staticProps) {
+> console.log("Constructor::",Constructor);
+> console.log("protoProps::",protoProps);
+> console.log("staticProps::",staticProps);
+> if (protoProps)
+> _defineProperties(Constructor.prototype, protoProps);
+> if (staticProps)
+> _defineProperties(Constructor, staticProps);
+> return Constructor;
+>}
+>
+>var Hello = /*#__PURE__*/function () {
+> function Hello(x) {
+> _classCallCheck(this, Hello);
+>
+> this.x = x;
+> }
+>
+> _createClass(Hello, [{
+> key: "greet",
+> value: function greet() {
+> console.log("Hello, " + this.x);
+> }
+> }]);
+>
+> return Hello;
+>}();
+>```
+
+
+
+### 87. *let、const、var* 的区别
+
+>参考答案:
+>
+>请参阅前面第 *22* 题答案。
+
+
+
+### 88. *ES6* 中模块化导入和导出与 *common.js* 有什么区别
+
+>参考答案:
+>
+>CommonJs模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化不会影响到这个值.
+>
+>```
+>// common.js
+>var count = 1;
+>
+>var printCount = () =>{
+> return ++count;
+>}
+>
+>module.exports = {
+> printCount: printCount,
+> count: count
+>};
+>// index.js
+>let v = require('./common');
+>console.log(v.count); // 1
+>console.log(v.printCount()); // 2
+>console.log(v.count); // 1
+>```
+>
+>你可以看到明明common.js里面改变了count,但是输出的结果还是原来的。这是因为count是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动的值。将common.js里面的module.exports 改写成
+>
+>```
+>module.exports = {
+> printCount: printCount,
+> get count(){
+> return count
+> }
+>};
+>```
+>
+>这样子的输出结果是 1,2,2
+>
+>而在ES6当中,写法是这样的,是利用export 和import导入的
+>
+>```
+>// es6.js
+>export let count = 1;
+>export function printCount() {
+> ++count;
+>}
+>// main1.js
+>import { count, printCount } from './es6';
+>console.log(count)
+>console.log(printCount());
+>console.log(count)
+>```
+>
+>ES6 模块是动态引用,并且不会缓存,模块里面的变量绑定其所有的模块,而是动态地去加载值,并且不能重新赋值,
+>
+>ES6 输入的模块变量,只是一个“符号连接符”,所以这个变量是只读的,对它进行重新赋值会报错。如果是引用类型,变量指向的地址是只读的,但是可以为其添加属性或成员。
+>
+>另外还想说一个 *export default*
+>
+>```
+> let count = 1;
+> function printCount() {
+> ++count;
+>}
+>export default { count, printCount}
+>// main3.js
+>import res form './main3.js'
+>console.log(res.count)
+>```
+>
+>export与export default的区别及联系:
+>
+>1. export与export default均可用于导出常量、函数、文件、模块等
+>
+>2. 你可以在其它文件或模块中通过 import + (常量 | 函数 | 文件 | 模块)名的方式,将其导入,以便能够对其进行使用
+>
+>3. 在一个文件或模块中,export、import可以有多个,export default仅有一个
+>
+>4. 通过export方式导出,在导入时要加{ },export default则不需要。
+
+
+
+### 89. 说一下普通函数和箭头函数的区别
+
+>参考答案:
+>
+>请参阅前面第 *8、25、83* 题答案。
+
+
+
+### 90. 说一下 *promise* 和 *async* 和 *await* 什么关系
+
+>参考答案:
+>
+>*await* 表达式会造成异步函数停止执行并且等待*promise*的解决,当值被*resolved*,异步函数会恢复执行以及返回*resolved*值。如果该值不是一个*promise*,它将会被转换成一个*resolved*后的*promise*。如果*promise*被*rejected*,*await* 表达式会抛出异常值。
+
+
+
+### 91. 说一下你学习过的有关 *ES6* 的知识点
+
+>参考答案:
+>
+>这种题目是开放题,可以简单列举一下 *ES6* 的新增知识点。( *ES6* 的新增知识点参阅前面第 *44* 题)
+>
+>然后说一下自己平时开发中用得比较多的是哪些即可。
+>
+>一般面试官会针对你所说的内容进行二次提问。例如:你回答平时开发中箭头函数用得比较多,那么面试官极大可能针对箭头函数展开二次提问,询问你箭头函数有哪些特性?箭头函数 *this* 特点之类的问题。
+
+
+
+### 92. 了解过 *js* 中 *arguments* 吗?接收的是实参还是形参?
+
+>参考答案:
+>
+>*JS* 中的 *arguments* 是一个伪数组对象。这个伪数组对象将包含调用函数时传递的所有的实参。
+>
+>与之相对的,*JS* 中的函数还有一个 *length* 属性,返回的是函数形参的个数。
+
+
+
+### 93. *ES6* 相比于 *ES5* 有什么变化
+
+>参考答案:
+>
+>*ES6* 相比 *ES5* 新增了很多新特性,这里可以自己简述几个。
+>
+>具体的新增特性可以参阅前面第 *44* 题。
+
+
+
+### 94. 强制类型转换方法有哪些?
+
+>参考答案:
+>
+>JavaScript 中的数据类型转换,主要有三种方式:
+>
+>1. 转换函数
+>
+>*js* 提供了诸如 *parseInt* 和 *parseFloat* 这些转换函数,通过这些转换函数可以进行数据类型的转换 。
+>
+>2. 强制类型转换
+>
+>还可使用强制类型转换(*type casting*)处理转换值的类型。
+>
+>例如:
+>
+>- *Boolean*(*value*) 把给定的值转换成 *Boolean* 型;
+>- *Number*(*value*)——把给定的值转换成数字(可以是整数或浮点数);
+>- *String*(*value*)——把给定的值转换成字符串。
+>
+>3. 利用 *js* 变量弱类型转换。
+>
+>例如:
+>
+>- 转换字符串:直接和一个空字符串拼接,例如:`a = "" + 数据`
+>
+>- 转换布尔:!!数据类型,例如:`!!"Hello"`
+>
+>- 转换数值:数据*1 或 /1,例如:`"Hello * 1"`
+
+
+
+### 95. 纯函数
+
+>参考答案:
+>
+>一个函数,如果符合以下两个特点,那么它就可以称之为**纯函数**:
+>
+>1. 对于相同的输入,永远得到相同的输出
+>2. 没有任何可观察到的副作用
+
+> 解析:
+>
+> 针对上面的两个特点,我们一个一个来看。
+>
+> - 相同输入得到相同输出
+>
+> 我们先来看一个不纯的反面典型:
+>
+> ```
+> let greeting = 'Hello'
+>
+> function greet (name) {
+> return greeting + ' ' + name
+> }
+>
+> console.log(greet('World')) // Hello World
+> ```
+>
+> 上面的代码中,*greet('World')* 是不是永远返回 *Hello World* ? 显然不是,假如我们修改 *greeting* 的值,就会影响 *greet* 函数的输出。即函数 *greet* 其实是 **依赖外部状态** 的。
+>
+> 那我们做以下修改:
+>
+> ```
+> function greet (greeting, name) {
+> return greeting + ' ' + name
+> }
+>
+> console.log(greet('Hi', 'Savo')) // Hi Savo
+> ```
+>
+> 将 *greeting* 参数也传入,这样对于任何输入参数,都有与之对应的唯一的输出参数了,该函数就符合了第一个特点。
+>
+> - 没有副作用
+>
+> 副作用的意思是,这个函数的运行,**不会修改外部的状态**。
+>
+> 下面再看反面典型:
+>
+> ```
+> const user = {
+> username: 'savokiss'
+> }
+>
+> let isValid = false
+>
+> function validate (user) {
+> if (user.username.length > 4) {
+> isValid = true
+> }
+> }
+> ```
+>
+> 可见,执行函数的时候会修改到 *isValid* 的值(注意:如果你的函数没有任何返回值,那么它很可能就具有副作用!)
+>
+> 那么我们如何移除这个副作用呢?其实不需要修改外部的 *isValid* 变量,我们只需要在函数中将验证的结果 *return* 出来:
+>
+> ```
+> const user = {
+> username: 'savokiss'
+> }
+>
+> function validate (user) {
+> return user.username.length > 4;
+> }
+>
+> const isValid = validate(user)
+> ```
+>
+> 这样 *validate* 函数就不会修改任何外部的状态了~
+
+
+
+### 96. *JS* 模块化
+
+>参考答案:
+>
+>模块化主要是用来抽离公共代码,隔离作用域,避免变量冲突等。
+>
+>模块化的整个发展历史如下:
+>
+>**IIFE**: 使用自执行函数来编写模块化,特点:**在一个单独的函数作用域中执行代码,避免变量冲突**。
+>
+>```js
+>(function(){
+> return {
+> data:[]
+> }
+>})()
+>```
+>
+>**AMD**: 使用requireJS 来编写模块化,特点:**依赖必须提前声明好**。
+>
+>```js
+>define('./index.js',function(code){
+> // code 就是index.js 返回的内容
+>})
+>```
+>
+>**CMD**: 使用seaJS 来编写模块化,特点:**支持动态引入依赖文件**。
+>
+>```js
+>define(function(require, exports, module) {
+> var indexCode = require('./index.js');
+>});
+>```
+>
+>**CommonJS**: nodejs 中自带的模块化。
+>
+>```js
+>var fs = require('fs');
+>```
+>
+>**UMD**:兼容AMD,CommonJS 模块化语法。
+>
+>**webpack(require.ensure)**:webpack 2.x 版本中的代码分割。
+>
+>**ES Modules**: ES6 引入的模块化,支持import 来引入另一个 js 。
+>
+>```js
+>import a from 'a';
+>```
+
+
+
+### 97. 看过 *jquery* 源码吗?
+
+>参考答案:
+>
+>开放题,但是需要注意的是,如果看过 *jquery* 源码,不要简单的回答一个“看过”就完了,应该继续乘胜追击,告诉面试官例如哪个哪个部分是怎么怎么实现的,并针对这部分的源码实现,可以发表一些自己的看法和感想。
+
+
+
+### 98. 说一下 *js* 中的 *this*
+
+> 参考答案:
+>
+> 请参阅前面第 *17* 题答案。
+
+
+
+### 99. *apply call bind* 区别,手写
+
+>参考答案:
+>
+>apply call bind 区别 ?
+>
+>*call* 和 *apply* 的功能相同,区别在于传参的方式不一样:
+>
+>- *fn.call(obj, arg1, arg2, ...)* 调用一个函数, 具有一个指定的 *this* 值和分别地提供的参数(参数的列表)。
+>- *fn.apply(obj, [argsArray])* 调用一个函数,具有一个指定的 *this* 值,以及作为一个数组(或类数组对象)提供的参数。
+>
+>*bind* 和 *call/apply* 有一个很重要的区别,一个函数被 *call/apply* 的时候,会直接调用,但是 *bind* 会创建一个新函数。当这个新函数被调用时,*bind( )* 的第一个参数将作为它运行时的 *this*,之后的一序列参数将会在传递的实参前传入作为它的参数。
+>
+>实现 *call* 方法:
+>
+>```js
+>Function.prototype.call2 = function (context) {
+> //没传参数或者为 null 是默认是 window
+> var context = context || (typeof window !== 'undefined' ? window : global)
+> // 首先要获取调用 call 的函数,用 this 可以获取
+> context.fn = this
+> var args = []
+> for (var i = 1; i < arguments.length; i++) {
+> args.push('arguments[' + i + ']')
+> }
+> eval('context.fn(' + args + ')')
+> delete context.fn
+>}
+>
+>// 测试
+>var value = 3
+>var foo = {
+> value: 2
+>}
+>
+>function bar(name, age) {
+> console.log(this.value)
+> console.log(name)
+> console.log(age)
+>}
+>bar.call2(null)
+>// 浏览器环境: 3 undefinde undefinde
+>// Node环境:undefinde undefinde undefinde
+>
+>bar.call2(foo, 'cc', 18) // 2 cc 18
+>
+>```
+>
+>
+>
+>实现 *apply* 方法:
+>
+>```js
+>Function.prototype.apply2 = function (context, arr) {
+> var context = context || (typeof window !== 'undefined' ? window : global)
+> context.fn = this;
+>
+> var result;
+> if (!arr) {
+> result = context.fn();
+> }
+> else {
+> var args = [];
+> for (var i = 0, len = arr.length; i < len; i++) {
+> args.push('arr[' + i + ']');
+> }
+> result = eval('context.fn(' + args + ')')
+> }
+>
+> delete context.fn
+> return result;
+>}
+>
+>// 测试:
+>
+>var value = 3
+>var foo = {
+> value: 2
+>}
+>
+>function bar(name, age) {
+> console.log(this.value)
+> console.log(name)
+> console.log(age)
+>}
+>bar.apply2(null)
+>// 浏览器环境: 3 undefinde undefinde
+>// Node环境:undefinde undefinde undefinde
+>
+>bar.apply2(foo, ['cc', 18]) // 2 cc 18
+>```
+>
+>
+>
+>实现 *bind* 方法:
+>
+>```js
+>Function.prototype.bind2 = function (oThis) {
+> if (typeof this !== "function") {
+> // closest thing possible to the ECMAScript 5 internal IsCallable function
+> throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
+> }
+> var aArgs = Array.prototype.slice.call(arguments, 1),
+> fToBind = this,
+> fNOP = function () { },
+> fBound = function () {
+> return fToBind.apply(this instanceof fNOP && oThis
+> ? this
+> : oThis || window,
+> aArgs.concat(Array.prototype.slice.call(arguments)));
+> };
+>
+> fNOP.prototype = this.prototype;
+> fBound.prototype = new fNOP();
+>
+> return fBound;
+>}
+>
+>// 测试
+>var test = {
+> name: "jack"
+>}
+>var demo = {
+> name: "rose",
+> getName: function () { return this.name; }
+>}
+>
+>console.log(demo.getName()); // 输出 rose 这里的 this 指向 demo
+>
+>// 运用 bind 方法更改 this 指向
+>var another2 = demo.getName.bind2(test);
+>console.log(another2()); // 输出 jack 这里 this 指向了 test 对象了
+>```
+
+
+
+### 100. 手写 *reduce flat*
+
+> 参考答案:
+>
+> *reduce* 实现:
+>
+> ```js
+> Array.prototype.my_reduce = function (callback, initialValue) {
+> if (!Array.isArray(this) || !this.length || typeof callback !== 'function') {
+> return []
+> } else {
+> // 判断是否有初始值
+> let hasInitialValue = initialValue !== undefined;
+> let value = hasInitialValue ? initialValue : tihs[0];
+> for (let index = hasInitialValue ? 0 : 1; index < this.length; index++) {
+> const element = this[index];
+> value = callback(value, element, index, this)
+> }
+> return value
+> }
+> }
+>
+> let arr = [1, 2, 3, 4, 5]
+> let res = arr.my_reduce((pre, cur, i, arr) => {
+> console.log(pre, cur, i, arr)
+> return pre + cur
+> }, 10)
+> console.log(res)//25
+> ```
+>
+>
+>
+> *flat* 实现:
+>
+> ```js
+> let arr = [1, [2, 3, [4, 5, [12, 3, "zs"], 7, [8, 9, [10, 11, [1, 2, [3, 4]]]]]]];
+>
+> //万能的类型检测方法
+> const checkType = (arr) => {
+> return Object.prototype.toString.call(arr).slice(8, -1);
+> }
+> //自定义flat方法,注意:不可以使用箭头函数,使用后内部的this会指向window
+> Array.prototype.myFlat = function (num) {
+> //判断第一层数组的类型
+> let type = checkType(this);
+> //创建一个新数组,用于保存拆分后的数组
+> let result = [];
+> //若当前对象非数组则返回undefined
+> if (!Object.is(type, "Array")) {
+> return;
+> }
+> //遍历所有子元素并判断类型,若为数组则继续递归,若不为数组则直接加入新数组
+> this.forEach((item) => {
+> let cellType = checkType(item);
+> if (Object.is(cellType, "Array")) {
+> //形参num,表示当前需要拆分多少层数组,传入Infinity则将多维直接降为一维
+> num--;
+> if (num < 0) {
+> let newArr = result.push(item);
+> return newArr;
+> }
+> //使用三点运算符解构,递归函数返回的数组,并加入新数组
+> result.push(...item.myFlat(num));
+> } else {
+> result.push(item);
+> }
+> })
+> return result;
+> }
+> console.time();
+>
+> console.log(arr.flat(Infinity)); //[1, 2, 3, 4, 5, 12, 3, "zs", 7, 8, 9, 10, 11, 1, 2, 3, 4];
+>
+> console.log(arr.myFlat(Infinity)); //[1, 2, 3, 4, 5, 12, 3, "zs", 7, 8, 9, 10, 11, 1, 2, 3, 4];
+> //自定义方法和自带的flat返回结果一致!!!!
+> console.timeEnd();
+> ```
+
+
+
+### 101. == 隐试转换的原理?是怎么转换的
+
+> 参考答案:
+>
+> **两个与类型转换有关的函数:valueOf()和toString()**
+>
+> - valueOf()的语义是,返回这个对象逻辑上对应的原始类型的值。比如说,String包装对象的valueOf(),应该返回这个对象所包装的字符串。
+> - toString()的语义是,返回这个对象的字符串表示。用一个字符串来描述这个对象的内容。
+>
+> valueOf()和toString()是定义在Object.prototype上的方法,也就是说,所有的对象都会继承到这两个方法。但是在Object.prototype上定义的这两个方法往往不能满足我们的需求(Object.prototype.valueOf()仅仅返回对象本身),因此js的许多内置对象都重写了这两个函数,以实现更适合自身的功能需要(比如说,String.prototype.valueOf就覆盖了在Object.prototype中定义的valueOf)。当我们自定义对象的时候,最好也重写这个方法。重写这个方法时要遵循上面所说的语义。
+>
+> **js内部用于实现类型转换的4个函数**
+>
+> 这4个方法实际上是ECMAScript定义的4个抽象的操作,它们在js内部使用,进行类型转换。js的使用者不能直接调用这些函数。
+>
+> - ToPrimitive ( input [ , PreferredType ] )
+> - ToBoolean ( argument )
+> - ToNumber ( argument )
+> - ToString ( argument )
+>
+> 需要区分这里的 ToString() 和上文谈到的 toString(),一个是 js 引擎内部使用的函数,另一个是定义在对象上的函数。
+>
+> (1)ToPrimitive ( input [ , PreferredType ] )
+>
+> 将 input 转化成一个原始类型的值。PreferredType参数要么不传入,要么是Number 或 String。**如果PreferredType参数是Number**,ToPrimitive这样执行:
+>
+> 1. 如果input本身就是原始类型,直接返回input。
+> 2. 调用**input.valueOf()**,如果结果是原始类型,则返回这个结果。
+> 3. 调用**input.toString()**,如果结果是原始类型,则返回这个结果。
+> 4. 抛出TypeError异常。
+>
+> **以下是PreferredType不为Number时的执行顺序。**
+>
+> - 如果PreferredType参数是String,则交换上面这个过程的第2和第3步的顺序,其他执行过程相同。
+> - 如果PreferredType参数没有传入
+> - 如果input是内置的Date类型,PreferredType 视为String
+> - 否则PreferredType 视为 Number
+>
+> **可以看出,ToPrimitive依赖于valueOf和toString的实现。**
+>
+> (2)ToBoolean ( argument )
+>
+>
+>
+> 只需要记忆 *0, null, undefined, NaN, ""* 返回 *false* 就可以了,其他一律返回 *true*。
+>
+> (3)ToNumber ( argument )
+>
+>
+>
+> ToNumber的转化并不总是成功,有时会转化成NaN,有时则直接抛出异常。
+>
+> (4)ToString ( argument )
+>
+>
+>
+> 当js期望得到某种类型的值,而实际在那里的值是其他的类型,就会发生隐式类型转换。系统内部会自动调用我们前面说ToBoolean ( argument )、ToNumber ( argument )、ToString ( argument ),尝试转换成期望的数据类型。
+
+
+
+### 102. ['1', '2', '3'].map(parseInt) 结果是什么,为什么 (字节)
+
+> 参考答案:
+>
+> [1, NaN, NaN]
+
+>解析:
+>
+>一、为什么会是这个结果?
+>1. *map* 函数
+>
+>将数组的每个元素传递给指定的函数处理,并返回处理后的数组,所以 *['1','2','3'].map(parseInt)* 就是将字符串 *1,2,3* 作为元素;*0,1,2* 作为下标分别调用 *parseInt* 函数。即分别求出 *parseInt('1',0), parseInt('2',1), parseInt('3',2)* 的结果。
+>
+>2. *parseInt* 函数(重点)
+>
+>概念:以第二个参数为基数来解析第一个参数字符串,通常用来做十进制的向上取整(省略小数)如:parseInt(2.7) //结果为2
+>
+>特点:接收两个参数 *parseInt(string,radix)*
+>
+>*string*:字母(大小写均可)、数组、特殊字符(不可放在开头,特殊字符及特殊字符后面的内容不做解析)的任意字符串,如 '2'、'2w'、'2!'
+>
+>*radix*:解析字符串的基数,基数规则如下:
+>
+> 1) 区间范围介于 *2~36* 之间;
+>
+> 2 ) 当参数为 *0*,*parseInt( )* 会根据十进制来解析;
+>
+> 3 ) 如果忽略该参数,默认的基数规则:
+>
+> 如果 *string* 以 "0x" 开头,parseInt() 会把 string 的其余部分解析为十六进制的整数;parseInt("0xf") // 15
+> 如果 *string* 以 0 开头,其后的字符解析为八进制或十六进制的数字;parseInt("08") // 8
+> 如果 *string* 以 1 ~ 9 的数字开头,parseInt() 将把它解析为十进制的整数;parseInt("88.99f") // 88
+> 只有字符串中的第一个数字会被返回。parseInt("10.33") // 返回10;
+> 开头和结尾的空格是允许的。parseInt(" 69 10 ") // 返回69
+> 如果字符串的第一个字符不能被转换为数字,返回 NaN。parseInt("f") // 返回 NaN 而 parseInt("f",16) // 返回15
+>
+>二、*parseInt* 方法解析的运算过程
+>
+>parseInt('101.55',10); // 以十进制解析,运算过程:向上取整数(不做四舍五入,省略小数),结果为 101。
+>
+>parseInt('101',2); // 以二进制解析,运算过程:1*2的2次方+0*2的1次方+1*2的0次方=4+0+1=5,结果为 5。
+>
+>parseInt('101',8); // 以八进制解析,运算过程:1*8的2次方+0*8的1次方+1*8的0次方=64+0+1=65,结果为 65。
+>
+>parseInt('101',16); // 以十六进制解析,运算过程:1*16的2次方+0*16的1次方+1*16的0次方=256+0+1=257,结果为 257。
+>
+>三、再来分析一下结果
+>
+>*['1','2','3'].map(parseInt)* 即
+>
+>parseInt('1',0); radix 为 0,parseInt( ) 会根据十进制来解析,所以结果为 *1*;
+>
+>parseInt('2',1); radix 为 1,超出区间范围,所以结果为 *NaN*;
+>
+>parseInt('3',2); radix 为 2,用2进制来解析,应以 *0* 和 *1* 开头,所以结果为 *NaN*。
+
+
+
+### 103. 防抖,节流是什么,如何实现 (字节)
+
+> 参考答案:
+>
+> 我们在平时开发的时候,会有很多场景会频繁触发事件,比如说搜索框实时发请求,*onmousemove、resize、onscroll* 等,有些时候,我们并不能或者不想频繁触发事件,这时候就应该用到函数防抖和函数节流。
+>
+> 函数防抖(*debounce*),指的是短时间内多次触发同一事件,只执行最后一次,或者只执行最开始的一次,中间的不执行。
+>
+> 具体实现:
+>
+> ```js
+> /**
+> * 函数防抖
+> * @param {function} func 一段时间后,要调用的函数
+> * @param {number} wait 等待的时间,单位毫秒
+> */
+> function debounce(func, wait){
+> // 设置变量,记录 setTimeout 得到的 id
+> let timerId = null;
+> return function(...args){
+> if(timerId){
+> // 如果有值,说明目前正在等待中,清除它
+> clearTimeout(timerId);
+> }
+> // 重新开始计时
+> timerId = setTimeout(() => {
+> func(...args);
+> }, wait);
+> }
+> }
+> ```
+>
+>
+>
+> 函数节流(*throttle*),指连续触发事件但是在 *n* 秒中只执行一次函数。即 *2n* 秒内执行 *2* 次... 。节流如字面意思,会稀释函数的执行频率。
+>
+> 具体实现:
+>
+> ```js
+> function throttle(func, wait) {
+> let context, args;
+> let previous = 0;
+> return function () {
+> let now = +new Date();
+> context = this;
+> args = arguments;
+> if (now - previous > wait) {
+> func.apply(context, args);
+> previous = now;
+> }
+> }
+> }
+> ```
+
+
+
+### 104. 介绍下 *Set、Map、WeakSet* 和 *WeakMap* 的区别(字节)
+
+> 参考答案:
+>
+> **Set**
+>
+> - 成员唯一、无序且不重复
+>
+> - 键值与键名是一致的(或者说只有键值,没有键名)
+>
+> - 可以遍历,方法有 *add, delete,has*
+>
+> **WeakSet**
+>
+> - 成员都是对象
+>
+> - 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存 *DOM* 节点,不容易造成内存泄漏
+>
+> - 不能遍历,方法有 *add, delete,has*
+>
+> **Map**
+>
+> - 本质上是健值对的集合,类似集合
+>
+> - 可以遍历,方法很多,可以跟各种数据格式转换
+>
+> **WeakMap**
+>
+> - 只接受对象作为健名(*null* 除外),不接受其他类型的值作为健名
+> - 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾机制回收,此时键名是无效的
+>
+> - 不能遍历,方法有 *get、set、has、delete*
+
+
+
+### 105. *setTimeout、Promise、Async/Await* 的区别(字节)
+
+> 参考答案:
+>
+> 事件循环中分为宏任务队列和微任务队列。
+>
+> 其中 *setTimeout* 的回调函数放到宏任务队列里,等到执行栈清空以后执行;
+>
+> *promise.then* 里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完再执行;
+>
+> *async* 函数表示函数里面可能会有异步方法,*await* 后面跟一个表达式,*async* 方法执行时,遇到 *await* 会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行。
+
+
+
+### 106. *Promise* 构造函数是同步执行还是异步执行,那么 *then* 方法呢?(字节)
+
+> 参考答案:
+>
+> *promise* 构造函数是同步执行的,*then* 方法是异步执行,*then* 方法中的内容加入微任务中。
+
+
+
+### 107. 情人节福利题,如何实现一个 *new* (字节)
+
+>参考答案:
+>首先我们需要明白 *new* 的原理。关于 *new* 的原理,主要分为以下几步:
+>
+> - 创建一个空对象 。
+>
+> - 由 *this* 变量引用该对象 。
+>
+> - 该对象继承该函数的原型(更改原型链的指向) 。
+>
+> - 把属性和方法加入到 *this* 引用的对象中。
+>
+> - 新创建的对象由 *this* 引用 ,最后隐式地返回 *this*
+>
+> 明白了这个原理后,我们就可以尝试来实现一个 *new* 方法,参考示例如下:
+>
+> ```js
+> // 构造器函数
+let Parent = function (name, age) {
+ this.name = name;
+ this.age = age;
+};
+Parent.prototype.sayName = function () {
+ console.log(this.name);
+};
+//自己定义的new方法
+let newMethod = function (Parent, ...rest) {
+ // 1.以构造器的prototype属性为原型,创建新对象;
+ let child = Object.create(Parent.prototype);
+ // 2.将this和调用参数传给构造器执行
+ let result = Parent.apply(child, rest);
+ // 3.如果构造器没有手动返回对象,则返回第一步的对象
+ return typeof result === 'object' ? result : child;
+};
+//创建实例,将构造函数Parent与形参作为参数传入
+const child = newMethod(Parent, 'echo', 26);
+child.sayName() //'echo';
+//最后检验,与使用new的效果相同
+console.log(child instanceof Parent)//true
+console.log(child.hasOwnProperty('name'))//true
+console.log(child.hasOwnProperty('age'))//true
+console.log(child.hasOwnProperty('sayName'))//false
+> ```
+
+
+
+### 108. 实现一个 *sleep* 函数(字节)
+
+> 参考答案:
+>
+> ```js
+> function sleep(delay) {
+> var start = (new Date()).getTime();
+> while ((new Date()).getTime() - start < delay) {
+> continue;
+> }
+> }
+>
+> function test() {
+> console.log('111');
+> sleep(2000);
+> console.log('222');
+> }
+>
+> test()
+> ```
+>
+> 这种实现方式是利用一个伪死循环阻塞主线程。因为 *JS* 是单线程的。所以通过这种方式可以实现真正意义上的 *sleep*。
+
+
+
+### 109. 使用 sort() 对数组 [3, 15, 8, 29, 102, 22] 进行排序,输出结果 (字节)
+
+> 参考答案:
+>
+> *sort* 方法默认按照 *ASCII* 码来排序,如果要按照数字大小来排序,需要传入一个回调函数,如下:
+>
+> ```js
+> [3, 15, 8, 29, 102, 22].sort((a,b) => {return a - b});
+> ```
+
+
+
+### 110. 实现 5.add(3).sub(2) (百度)
+
+> 参考答案:
+>
+> 这里想要实现的是链式操作,那么我们可以考虑在 *Number* 类型的原型上添加 *add* 和 *sub* 方法,这两个方法返回新的数
+>
+> 示例如下:
+>
+> ```js
+> Number.prototype.add = function (number) {
+> if (typeof number !== 'number') {
+> throw new Error('请输入数字~');
+> }
+> return this.valueOf() + number;
+> };
+> Number.prototype.minus = function (number) {
+> if (typeof number !== 'number') {
+> throw new Error('请输入数字~');
+> }
+> return this.valueOf() - number;
+> };
+> console.log((5).add(3).minus(2)); // 6
+> ```
+
+
+
+### 111. 给定两个数组,求交集
+
+> 参考答案:
+>
+> 示例代码如下:
+>
+> ```js
+> function intersect(nums1, nums2) {
+> let i = j = 0,
+> len1 = nums1.length,
+> len2 = nums2.length,
+> newArr = [];
+> if (len1 === 0 || len2 === 0) {
+> return newArr;
+> }
+> nums1.sort(function (a, b) {
+> return a - b;
+> });
+> nums2.sort(function (a, b) {
+> return a - b;
+> });
+> while (i < len1 || j < len2) {
+> if (nums1[i] > nums2[j]) {
+> j++;
+> } else if (nums1[i] < nums2[j]) {
+> i++;
+> } else {
+> if (nums1[i] === nums2[j]) {
+> newArr.push(nums1[i]);
+> }
+> if (i < len1 - 1) {
+> i++;
+> } else {
+> break;
+> }
+> if (j < len2 - 1) {
+> j++;
+> } else {
+> break;
+> }
+> }
+> }
+> return newArr;
+> };
+> // 测试
+> console.log(intersect([3, 5, 8, 1], [2, 3]));
+> ```
+
+
+
+### 112. 为什么普通 *for* 循环的性能远远高于 *forEach* 的性能,请解释其中的原因。
+
+> 参考答案:
+>
+> *for* 循环按顺序遍历,*forEach* 使用 *iterator* 迭代器遍历
+>
+> 下面是一段性能测试的代码:
+>
+> ```js
+> let arrs = new Array(100000);
+> console.time('for');
+> for (let i = 0; i < arrs.length; i++) {
+> };
+> console.timeEnd('for');
+> console.time('forEach');
+> arrs.forEach((arr) => {
+> });
+> console.timeEnd('forEach');
+>
+> for: 2.263ms
+> forEach: 0.254ms
+> ```
+>
+> 在10万这个级别下,`forEach`的性能是`for`的十倍
+>
+> ```js
+> for: 2.263ms
+> forEach: 0.254ms
+> ```
+>
+> 在100万这个量级下,`forEach`的性能是和`for`的一致
+>
+> ```js
+> for: 2.844ms
+> forEach: 2.652ms
+> ```
+>
+> 在1000万级以上的量级上 ,`forEach`的性能远远低于`for`的性能
+>
+> ```js
+> for: 8.422ms
+> forEach: 30.328m
+> ```
+>
+> 我们从语法上面来观察:
+>
+> ```js
+> arr.forEach(callback(currentValue [, index [, array]])[, thisArg])
+> ```
+>
+> 可以看到 *forEach* 是有回调的,它会按升序为数组中含有效值的每一项执行一次 *callback*,且除了抛出异常以外,也没有办法中止或者跳出 *forEach* 循环。那这样的话执行就会额外的调用栈和函数内的上下文。
+>
+> 而 *for* 循环则是底层写法,不会产生额外的消耗。
+>
+> 在实际业务中没有很大的数组时,*for* 和 *forEach* 的性能差距其实很小,*forEach* 甚至会优于 *for* 的时间,且更加简洁,可读性也更高,一般也会优先使用 *forEach* 方法来进行数组的循环处理。
+
+
+
+### 113. 实现一个字符串匹配算法,从长度为 n 的字符串 S 中,查找是否存在字符串 T,T 的长度是 m,若存在返回所在位置。
+
+> 参考答案:
+>
+> ```js
+> // 完全不用 API
+> var getIndexOf = function (s, t) {
+> let n = s.length;
+> let m = t.length;
+> if (!n || !m || n < m) return -1;
+> for (let i = 0; i < n; i++) {
+> let j = 0;
+> let k = i;
+> if (s[k] === t[j]) {
+> k++; j++;
+> while (k < n && j < m) {
+> if (s[k] !== t[j]) break;
+> else {
+> k++; j++;
+> }
+> }
+> if (j === m) return i;
+> }
+> }
+> return -1;
+> }
+>
+> // 测试
+> console.log(getIndexOf("Hello World", "rl"))
+> ```
+
+
+
+### 114. 使用 *JavaScript Proxy* 实现简单的数据绑定
+
+> 参考答案:
+>
+> 示例代码如下:
+>
+> ```js
+>
+> hello,world
+>
+>
+>
+>
+>
+> ```
+
+
+
+### 115. 数组里面有 *10* 万个数据,取第一个元素和第 *10* 万个元素的时间相差多少(字节)
+
+> 参考答案:
+>
+> 消耗时间几乎一致,差异可以忽略不计
+
+> 解析:
+>
+> - 数组可以直接根据索引取的对应的元素,所以不管取哪个位置的元素的时间复杂度都是 O(1)
+> - *JavaScript* 没有真正意义上的数组,所有的数组其实是对象,其“索引”看起来是数字,其实会被转换成字符串,作为属性名(对象的 *key*)来使用。所以无论是取第 *1* 个还是取第 *10* 万个元素,都是用 *key* 精确查找哈希表的过程,其消耗时间大致相同。
+
+
+
+### 116. 打印出 *1~10000* 以内的对称数
+
+> 参考答案:
+>
+> ```js
+> function isSymmetryNum(start, end) {
+> for (var i = start; i < end + 1; i++) {
+> var iInversionNumber = +(i.toString().split("").reverse().join(""));
+>
+> if (iInversionNumber === i && i > 10) {
+> console.log(i);
+> }
+>
+> }
+> }
+> isSymmetryNum(1, 10000);
+> ```
+
+
+
+### 117. 简述同步和异步的区别
+
+> 参考答案:
+>
+> 同步意味着每一个操作必须等待前一个操作完成后才能执行。
+> 异步意味着操作不需要等待其他操作完成后才开始执行。
+> 在 *JavaScript* 中,由于单线程的特性导致所有代码都是同步的。但是,有些异步操作(例如:`XMLHttpRequest` 或 `setTimeout`)并不是由主线程进行处理的,他们由本机代码(浏览器 API)所控制,并不属于程序的一部分。但程序中被执行的回调部分依旧是同步的。
+>
+> 加分回答:
+>
+> - *JavaScript* 中的同步任务是指在主线程上排队执行的任务,只有前一个任务执行完成后才能执行后一个任务;异步任务是指进入任务队列(*task queue*)而非主线程的任务,只有当任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程中进行执行。
+> - *JavaScript* 的并发模型是基于 “*event loop*”。
+> - 像 `alert` 这样的方法回阻塞主线程,以致用户关闭他后才能继续进行后续的操作。
+> - *JavaScript* 主要用于和用户互动及操作 DOM,多线程的情况和异步操作带来的复杂性相比决定了他单线程的特性。
+> - *Web Worker* 虽然允许 *JavaScript* 创建多个线程,但子线程完全受主线程控制,且不能操作 *DOM*。因此他还是保持了单线程的特性。
+
+
+
+### 118. 怎么添加、移除、复制、创建、和查找节点
+
+> 参考答案:
+>
+> 1)创建新节点
+>
+> *createDocumentFragment*( ) // 创建一个*DOM* 片段
+>
+> *createElement*( ) // 创建一个具体的元素
+>
+> *createTextNode*( ) // 创建一个文本节点
+>
+> (2)添加、移除、替换、插入
+>
+> *appendChild*( )
+>
+> *removeChild*( )
+>
+> *replaceChild*( )
+>
+> *insertBefore*( ) // 在已有的子节点前插入一个新的子节点
+>
+> (3)查找
+>
+> *getElementsByTagName*( ) //通过标签名称
+>
+> *getElementsByName*( ) // 通过元素的 *Name* 属性的值
+>
+> *getElementById*( ) // 通过元素 *Id*,唯一性
+>
+> *querySelector*( ) // 用于接收一个 *CSS* 选择符,返回与该模式匹配的第一个元素
+>
+> *querySelectorAll*( ) // 用于选择匹配到的所有元素
+
+
+
+### 119. 实现一个函数 *clone* 可以对 *Javascript* 中的五种主要数据类型(*Number、string、 Object、Array、Boolean*)进行复制
+
+> 参考答案:
+>
+> 示例代码如下:
+>
+> ```js
+> /**
+> * 对象克隆
+> * 支持基本数据类型及对象
+> * 递归方法
+> */
+> function clone(obj) {
+> var o;
+> switch (typeof obj) {
+> case "undefined":
+> break;
+> case "string":
+> o = obj + "";
+> break;
+> case "number":
+> o = obj - 0;
+> break;
+> case "boolean":
+> o = obj;
+> break;
+> case "object": // object 分为两种情况 对象(Object)或数组(Array)
+> if (obj === null) {
+> o = null;
+> } else {
+> if (Object.prototype.toString.call(obj).slice(8, -1) === "Array") {
+> o = [];
+> for (var i = 0; i < obj.length; i++) {
+> o.push(clone(obj[i]));
+> }
+> } else {
+> o = {};
+> for (var k in obj) {
+> o[k] = clone(obj[k]);
+> }
+> }
+> }
+> break;
+> default:
+> o = obj;
+> break;
+> }
+> return o;
+> }
+> ```
+
+
+
+### 120. 如何消除一个数组里面重复的元素
+
+> 参考答案:
+>
+> 请参阅前面第 *2* 题。
+
+
+
+### 121. 写一个返回闭包的函数
+
+> 参考答案:
+>
+> ```js
+> function foo() {
+> var i = 0;
+> return function () {
+> console.log(i++);
+> }
+> }
+> var f1 = foo();
+> f1(); // 0
+> f1(); // 1
+> f1(); // 2
+> ```
+
+
+
+### 122. 使用递归完成 1 到 100 的累加
+
+> 参考答案:
+>
+> ```js
+> function add(x, y){
+> if(x === y){
+> return x;
+> } else {
+> return y + add(x, y-1);
+> }
+> }
+>
+> console.log(add(1, 100))
+> ```
+
+
+
+### 123. *Javascript* 有哪几种数据类型
+
+> 参考答案:
+>
+> 请参阅前面第 *26* 题。
+
+
+
+### 124. 如何判断数据类型
+
+> 参考答案:
+>
+> 请参阅前面第 *69* 题。
+
+
+
+### 125. console.log(1+'2')和 console.log(1-'2')的打印结果
+
+> 参考答案:
+>
+> 第一个打印出 '12',是一个 *string* 类型的值。
+>
+> 第二个打印出 -1,是一个 *number* 类型的值
+
+
+
+### 126. *JS* 的事件委托是什么,原理是什么
+
+> 参考答案:
+>
+> 事件委托,又被称之为事件代理。在 *JavaScript* 中,添加到页面上的事件处理程序数量将直接关系到页面整体的运行性能。导致这一问题的原因是多方面的。
+>
+> 首先,每个函数都是对象,都会占用内存。内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致的 *DOM* 访问次数,会延迟整个页面的交互就绪时间。
+>
+> 对事件处理程序过多问题的解决方案就是事件委托。
+>
+> 事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。例如,*click* 事件会一直冒泡到 *document* 层次。也就是说,我们可以为整个页面指定一个 *onclick* 事件处理程序,而不必给每个可单击的元素分别添加事件处理程序。
+
+
+
+### 127. 如何改变函数内部的 *this* 指针的指向
+
+> 参考答案:
+>
+> 可以通过 *call、apply、bind* 方法来改变 *this* 的指向,关于 *call、apply、bind* 方法的具体使用,请参阅前面 *102* 题
+
+
+
+### 128. *JS* 延迟加载的方式有哪些?
+
+> 参考答案:
+>
+> - *defer* 属性
+> - *async* 属性
+> - 使用 *jQuery* 的 *getScript*( ) 方法
+> - 使用 *setTimeout* 延迟方法
+> - 把 *JS* 外部引入的文件放到页面底部,来让 *JS* 最后引入
+
+
+
+### 129. 说说严格模式的限制
+
+> 参考答案:
+>
+> 什么是严格模式?
+>
+> 严格模式对 *JavaScript* 的语法和行为都做了一些更改,消除了语言中一些不合理、不确定、不安全之处;提供高效严谨的差错机制,保证代码安全运行;禁用在未来版本中可能使用的语法,为新版本做好铺垫。在脚本文件第一行或函数内第一行中引入"use strict"这条指令,就能触发严格模式,这是一条没有副作用的指令,老版的浏览器会将其作为一行字符串直接忽略。
+>
+> 例如:
+>
+> ```js
+> "use strict";//脚本第一行
+> function add(a,b){
+> "use strict";//函数内第一行
+> return a+b;
+> }
+>
+> ```
+>
+> 进入严格模式后的限制
+>
+> - 变量必须声明后再赋值
+> - 不能有重复的参数名,函数的参数也不能有同名属性
+> - 不能使用*with*语句
+> - 不能对只读属性赋值
+> - 不能使用前缀 *0*表示八进制数
+> - 不能删除不可删除的属性
+> - *eval* 不会在它的外层作用域引入变量。
+> - *eval*和*arguments*不能被重新赋值
+> - *arguments* 不会自动反应函数的变化
+> - 不能使用 *arguments.callee*
+> - 不能使用 *arguments.caller*
+> - 禁止 *this* 指向全局对象
+> - 不能使用 *fn.caller* 和 *fn.arguments* 获取函数调用的堆栈
+> - 增加了保留字
+
+
+
+### 130. *attribute* 和 *property* 的区别是什么?
+
+> 参考答案:
+>
+> property 和 attribute 非常容易混淆,两个单词的中文翻译也都非常相近(property:属性,attribute:特性),但实际上,二者是不同的东西,属于不同的范畴。
+>
+> - property是DOM中的属性,是JavaScript里的对象;
+> - attribute是HTML标签上的特性,它的值只能够是字符串;
+>
+> 简单理解,Attribute就是dom节点自带的属性,例如html中常用的id、class、title、align等。
+>
+> 而Property是这个DOM元素作为对象,其附加的内容,例如childNodes、firstChild等。
+
+
+
+### 131. *ES6* 能写 *class* 么,为什么会出现 *class* 这种东西?
+
+> 参考答案:
+>
+> 在 *ES6* 中,可以书写 *class*。因为在 *ES6* 规范中,引入了 *class* 的概念。使得 *JS* 开发者终于告别了直接使用原型对象模仿面向对象中的类和类继承时代。
+>
+> 但是 *JS* 中并没有一个真正的 *class* 原始类型, *class* 仅仅只是对原型对象运用语法糖。
+>
+> 之所以出现 *class* 关键字,是为了使 *JS* 更像面向对象,所以 *ES6* 才引入 *class* 的概念。
+
+
+
+### 132. 常见兼容性问题
+
+> 参考答案:
+>
+> 常见的兼容性问题很多,这里列举一些:
+>
+> 1. 关于获取行外样式 *currentStyle* 和 *getComputedStyle* 出现的兼容问题
+>
+> 我们都知道 *JS* 通过 *style* 不可以获取行外样式,如果我们需要获取行外样式就会使用这两种
+>
+> - IE 下:*currentStyle*
+>
+> - chrome、FF 下:*getComputedStyle* 第二个参数的作用是获取伪类元素的属性值
+>
+>
+>
+> 2. 关于“索引”获取字符串每一项出现的兼容性的问题
+>
+> 对于字符串也有类似于数组这样通过下标索引获取每一项的值
+>
+> ```js
+> var str = 'abcd';
+> console.log(str[2]);
+> ```
+>
+> 但是低版本的浏览器 *IE6、7* 不兼容
+>
+>
+>
+> 3. 关于使用 *firstChild、lastChild* 等,获取第一个/最后一个元素节点是产生的问题
+>
+> - IE6-8下: *firstChild,lastChild,nextSibling,previousSibling* 获取第一个元素节点
+> - 高版本浏览器IE9+、FF、Chrome:获取的空白文本节点
+>
+>
+>
+> 4. 关于使用 *event* 对象,出现兼容性问题
+>
+> 在 *IE8* 及之前的版本浏览器中,*event* 事件对象是作为 *window* 对象的一个属性。
+>
+> 所以兼容的写法如下:
+>
+> ```js
+> function(event){
+> event = event || window.event;
+> }
+> ```
+>
+>
+>
+> 5. 关于事件绑定的兼容性问题
+>
+> - *IE8* 以下用: attachEvent('事件名',fn);
+>
+> - *FF、Chrome、IE9-10* 用: attachEventLister('事件名',fn,false);
+>
+>
+>
+> 6. 关于获取滚动条距离而出现的问题
+>
+> 当我们获取滚动条滚动距离时:
+>
+> - *IE、Chrome: document.body.scrollTop*
+>
+> - *FF: document.documentElement.scrollTop*
+>
+> 兼容处理:
+>
+> *var scrollTop = document.documentElement.scrollTop||document.body.scrollTop*
+
+
+
+### 133. 函数防抖节流的原理
+
+> 参考答案:
+>
+> 请参阅前面第 *49、106* 题。
+
+
+
+### 134. 原始类型有哪几种?*null* 是对象吗?
+
+> 参考答案:
+>
+> 在 *JavaScript* 中,数据类型整体上来讲可以分为两大类:**基本类型**和**引用数据类型**
+>
+> 基本数据类型,一共有 *6* 种:
+>
+> ```text
+> string,symbol,number,boolean,undefined,null
+> ```
+>
+> 其中 *symbol* 类型是在 *ES6* 里面新添加的基本数据类型。
+>
+> 引用数据类型,就只有 *1* 种:
+>
+> ```js
+> object
+> ```
+>
+> 基本数据类型的值又被称之为原始值或简单值,而引用数据类型的值又被称之为复杂值或引用值。
+>
+> 关于原始类型和引用类型的区别,可以参阅第 *26* 题。
+>
+> *null* 表示空,但是当我们使用 *typeof* 来进行数据类型检测的时候,得到的值是 *object*。
+>
+> 具体原因可以参阅前面第 *68* 题。
+
+
+
+### 135. 为什么 *console.log(0.2+0.1==0.3) // false*
+
+> 参考答案:
+>
+> 因为浮点数的计算存在 *round-off* 问题,也就是浮点数不能够进行精确的计算。并且:
+>
+> - 不仅 *JavaScript*,所有遵循 *IEEE 754* 规范的语言都是如此;
+> - 在 *JavaScript* 中,所有的 *Number* 都是以 *64-bit* 的双精度浮点数存储的;
+> - 双精度的浮点数在这 *64* 位上划分为 *3* 段,而这 *3* 段也就确定了一个浮点数的值,*64bit* 的划分是“*1-11-52*”的模式,具体来说:
+> - 就是 *1* 位最高位(最左边那一位)表示符号位,*0* 表示正,*1* 表示负;
+> - *11* 位表示指数部分;
+> - *52* 位表示尾数部分,也就是有效域部分
+
+
+
+### 136. 说一下 *JS* 中类型转换的规则?
+
+> 参考答案:
+>
+> 类型转换可以分为两种,**隐性转换**和**显性转换**。
+>
+> **1. 隐性转换**
+>
+> 当不同数据类型之间进行相互运算,或者当对非布尔类型的数据求布尔值的时候,会发生隐性转换。
+>
+> 预期为数字的时候:算术运算的时候,我们的结果和运算的数都是数字,数据会转换为数字来进行计算。
+>
+> | 类型 | 转换前 | 转换后 |
+> | --------- | --------- | ------ |
+> | number | 4 | 4 |
+> | string | "1" | 1 |
+> | string | "abc" | NaN |
+> | string | "" | 0 |
+> | boolean | true | 1 |
+> | boolean | false | 0 |
+> | undefined | undefined | NaN |
+> | null | null | 0 |
+>
+> 预期为字符串的时候:如果有一个操作数为字符串时,使用`+`符号做相加运算时,会自动转换为字符串。
+>
+> 预期为布尔的时候:前面在介绍布尔类型时所提到的 9 个值会转为 false,其余转为 true
+>
+> **2. 显性转换**
+>
+> 所谓显性转换,就是只程序员强制将一种类型转换为另外一种类型。显性转换往往会使用到一些转换方法。常见的转换方法如下:
+>
+> - 转换为数值类型:`Number()`,`parseInt()`,`parseFloat()`
+>
+> - 转换为布尔类型:`Boolean()`
+>
+> - 转换为字符串类型:`toString()`,`String()`
+>
+> 当然,除了使用上面的转换方法,我们也可以通过一些快捷方式来进行数据类型的显性转换,如下:
+>
+> - 转换字符串:直接和一个空字符串拼接,例如:`a = "" + 数据`
+>
+> - 转换布尔:!!数据类型,例如:`!!"Hello"`
+>
+> - 转换数值:数据*1 或 /1,例如:`"Hello * 1"`
+
+
+
+### 137. 深拷贝和浅拷贝的区别?如何实现
+
+> 参考答案:
+>
+> - **浅拷贝**:只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用,我们把这种拷贝叫做浅拷贝(浅复制)
+>
+> 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
+>
+> - **深拷贝**:在堆中重新分配内存,并且把源对象所有属性都进行新建拷贝,以保证深拷贝的对象的引用图不包含任何原有对象或对象图上的任何对象,拷贝后的对象与原来的对象是完全隔离,互不影响。
+>
+> **浅拷贝方法**
+>
+> 1. 直接赋值
+> 2. *Object.assign* 方法:可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。当拷贝的 *object* 只有一层的时候,是深拷贝,但是当拷贝的对象属性值又是一个引用时,换句话说有多层时,就是一个浅拷贝。
+> 3. *ES6* 扩展运算符,当 *object* 只有一层的时候,也是深拷贝。有多层时是浅拷贝。
+> 4. *Array.prototype.concat* 方法
+> 5. *Array.prototype.slice* 方法
+> 6. *jQuery* 中的 *$.extend*:在 *jQuery* 中,*$.extend(deep,target,object1,objectN)* 方法可以进行深浅拷贝。*deep* 如过设为 *true* 为深拷贝,默认是 *false* 浅拷贝。
+>
+> **深拷贝方法**
+>
+> 1. *$.extend(deep,target,object1,objectN)*,将 *deep* 设置为 *true*
+> 2. *JSON.parse(JSON.stringify)*:用 *JSON.stringify* 将对象转成 *JSON* 字符串,再用 *JSON.parse* 方法把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。这种方法虽然可以实现数组或对象深拷贝,但不能处理函数。
+> 3. 手写递归
+>
+> 示例代码如下:
+>
+> ```js
+> function deepCopy(oldObj, newobj) {
+> for (var key in oldObj) {
+> var item = oldObj[key];
+> // 判断是否是对象
+> if (item instanceof Object) {
+> if (item instanceof Function) {
+> newobj[key] = oldObj[key];
+> } else {
+> newobj[key] = {}; //定义一个空的对象来接收拷贝的内容
+> deepCopy(item, newobj[key]); //递归调用
+> }
+>
+> // 判断是否是数组
+> } else if (item instanceof Array) {
+> newobj[key] = []; //定义一个空的数组来接收拷贝的内容
+> deepCopy(item, newobj[key]); //递归调用
+> } else {
+> newobj[key] = oldObj[key];
+> }
+> }
+> }
+> ```
+
+
+
+### 138. 如何判断 *this*?箭头函数的 *this* 是什么
+
+> 参考答案:
+>
+> 有关如何判断 *this*,可以参阅前面 17 题。
+>
+> 有关箭头函数的 *this* 指向,可以参阅前面 *24、25* 题
+
+
+
+### 139. *call、apply* 以及 *bind* 函数内部实现是怎么样的
+
+> 参考答案:
+>
+> 请参阅前面 *102* 题。
+
+
+
+### 140. 为什么会出现 *setTimeout* 倒计时误差?如何减少
+
+> 参考答案:
+>
+> 定时器是属于宏任务(*macrotask*) 。如果当前执行栈所花费的时间大于定时器时间,那么定时器的回调在宏任务(*macrotask*) 里,来不及去调用,所有这个时间会有误差。
+
+
+
+### 141. 谈谈你对 *JS* 执行上下文栈和作用域链的理解
+
+> 参考答案:
+>
+> **什么是执行上下文?**
+>
+> 简而言之,执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。
+>
+> **执行上下文的类型**
+>
+> JavaScript 中有三种执行上下文类型。
+>
+> - **全局执行上下文** — 这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 `this` 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
+> - **函数执行上下文** — 每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。
+> - **Eval 函数执行上下文** — 执行在 `eval` 函数内部的代码也会有它属于自己的执行上下文。
+>
+> **调用栈**
+>
+> 调用栈是解析器(如浏览器中的的javascript解析器)的一种机制,可以在脚本调用多个函数时,跟踪每个函数在完成执行时应该返回控制的点。(如什么函数正在执行,什么函数被这个函数调用,下一个调用的函数是谁)
+>
+> - 当脚本要调用一个函数时,解析器把该函数添加到栈中并且执行这个函数。
+> - 任何被这个函数调用的函数会进一步添加到调用栈中,并且运行到它们被上个程序调用的位置。
+> - 当函数运行结束后,解释器将它从堆栈中取出,并在主代码列表中继续执行代码。
+> - 如果栈占用的空间比分配给它的空间还大,那么则会导致“栈溢出”错误。
+>
+> **作用域链**
+>
+> 当访问一个变量时,编译器在执行这段代码时,会首先从当前的作用域中查找是否有这个标识符,如果没有找到,就会去父作用域查找,如果父作用域还没找到继续向上查找,直到全局作用域为止,,而作用域链,就是有当前作用域与上层作用域的一系列变量对象组成,它保证了当前执行的作用域对符合访问权限的变量和函数的有序访问。
+
+
+
+### 142. *new* 的原理是什么?通过 *new* 的方式创建对象和通过字面量创建有什么区别?
+
+> 参考答案:
+>
+> 关于 *new* 的原理,主要分为以下几步:
+>
+> - 创建一个空对象 。
+>
+> - 由 *this* 变量引用该对象 。
+>
+> - 该对象继承该函数的原型(更改原型链的指向) 。
+>
+> - 把属性和方法加入到 *this* 引用的对象中。
+>
+> - 新创建的对象由 *this* 引用 ,最后隐式地返回 *this*,过程如下:
+>
+> ```js
+> var obj = {};
+> obj.__proto__ = Base.prototype;
+> Base.call(obj);
+> ```
+>
+> 通过 *new* 的方式创建对象和通过字面量创建的对象,区别在于 *new* 出来的对象的原型对象为`构造函数.prototype`,而字面量对象的原型对象为 `Object.prototype`
+>
+> 示例代码如下:
+>
+> ```js
+> function Computer() {}
+> var c = new Computer();
+> var d = {};
+> console.log(c.__proto__ === Computer.prototype); // true
+> console.log(d.__proto__ === Object.prototype); // true
+> ```
+
+
+
+### 143. *prototype* 和 \__*proto*__ 区别是什么?
+
+> 参考答案:
+>
+> *prototype* 是构造函数上面的一个属性,指向实例化出来对象的原型对象。
+>
+> \__*proto*__ 是对象上面的一个隐式属性,指向自己的原型对象。
+
+
+
+### 144. 使用 ES5 实现一个继承?
+
+> 参考答案:
+>
+> 请参阅第 *47* 题。
+
+
+
+### 145. 取数组的最大值(*ES5、ES6*)
+
+> 参考答案:
+>
+> ```js
+> var arr = [3, 5, 8, 1];
+> // ES5 方式
+> console.log(Math.max.apply(null, arr)); // 8
+> // ES6 方式
+> console.log(Math.max(...arr)); // 8
+> ```
+
+
+
+### 146. *ES6* 新的特性有哪些?
+
+> 参考答案:
+>
+> 请参阅前面第 *44* 题。
+
+
+
+### 147. *Promise* 有几种状态, *Promise* 有什么优缺点 ?
+
+> 参考答案:
+>
+> *Promise* 有三种状态:
+>
+> *pending、fulfilled、rejected*(未决定,履行,拒绝),同一时间只能存在一种状态,且状态一旦改变就不能再变。*Promise* 是一个构造函数,*promise* 对象代表一项有两种可能结果(成功或失败)的任务,它还持有多个回调,出现不同结果时分别发出相应回调。
+>
+> - 初始化状态:*pending*
+> - 当调用 *resolve*(成功) 状态:*pengding=>fulfilled*
+> - 当调用 *reject*(失败) 状态:*pending=>rejected*
+>
+> *Promise* 的优点是解决了回调地狱,缺点是代码并没有因为新方法的出现而减少,反而变得更加复杂,同时理解难度也加大。所以后面出现了 *async/await* 的异步解决方案。
+
+
+
+### 148. *Promise* 构造函数是同步还是异步执行,*then* 呢 ? *Promise* 如何实现 *then* 处理 ?
+
+> 参考答案:
+>
+> *promise* 构造函数是同步执行的,*then* 方法是异步执行,*then* 方法中的内容加入微任务中。
+>
+> 接下来我们来看 *promise* 如何实现 *then* 的处理。
+>
+> 我们知道 *then* 是用来处理 *resolve* 和 *reject* 函数的回调。那么首先我们来定义 *then* 方法。
+>
+> ##### 1、then方法需要两个参数,其中onFulfilled代表resolve成功的回调,onRejected代表reject失败的回调。
+>
+> ```js
+> then(onFulfilled,onRejected){}
+> ```
+>
+> ##### 2、我们知道promise的状态是不可逆的,在状态发生改变后,即不可再次更改,只有状态为FULFILLED才会调用onFulfilled,状态为REJECTED调用onRejected
+>
+> ```js
+> then(onFulfilled, onRejected){
+> if (this.status == Promise.FULFILLED) {
+> onFulfilled(this.value)
+> }
+> if (this.status == Promise.REJECTED) {
+> onRejected(this.value)
+> }
+> }
+> ```
+>
+> ##### 3、then方法的每个方法都不是必须的,所以我们要处理当没有传递参数时,应该设置默认值
+>
+> ```js
+> then(onFulfilled,onRejected){
+> if(typeof onFulfilled !=='function'){
+> onFulfilled = value => value;
+> }
+> if(typeof onRejected !=='function'){
+> onRejected = value => value;
+> }
+> if(this.status == Promise.FULFILLED){
+> onFulfilled(this.value)
+> }
+> if(this.status == Promise.REJECTED){
+> onRejected(this.value)
+> }
+> }
+> ```
+>
+> ##### 4、在执行then方法时,我们要考虑到传递的函数发生异常的情况,如果函数发生异常,我们应该让它进行错误异常处理,统一交给onRejected来处理错误
+>
+> ```js
+> then(onFulfilled,onRejected){
+> if(typeof onFulfilled !=='function'){
+> onFulfilled = value => value;
+> }
+> if(typeof onRejected !=='function'){
+> onRejected = value => value;
+> }
+> if(this.status == Promise.FULFILLED){
+> try{onFulfilled(this.value)}catch(error){ onRejected(error) }
+> }
+> if(this.status == Promise.REJECTED){
+> try{onRejected(this.value)}catch(error){ onRejected(error) }
+> }
+> }
+> ```
+>
+> ##### 5、但是现在我们自己封装的promise有个小问题,我们知道原生的promise中then方法都是异步执行,在一个同步任务执行之后再调用,而我们的现在的情况则是同步调用,因此我们要使用setTimeout来将onFulfilled和onRejected来做异步宏任务执行。
+>
+> ```js
+> if(this.status=Promise.FULFILLED){
+> setTimeout(()=>{
+> try{onFulfilled(this.value)}catch(error){onRejected(error)}
+> })
+> }
+> if(this.status=Promise.REJECTED){
+> setTimeout(()=>{
+> try{onRejected(this.value)}catch(error){onRejected(error)}
+> })
+> }
+> ```
+>
+> ##### 现在then方法中,可以处理status为FULFILLED和REJECTED的情况,但是不能处理为pedding的情况,接下来进行几处修改。
+>
+> ##### 6、在构造函数中,添加callbacks来保存pending状态时处理函数,当状态改变时循环调用
+>
+> ```js
+> constructor(executor) {
+> ...
+> this.callbacks = [];
+> ...
+> }
+> ```
+>
+> ##### 7、在then方法中,当status等于pending的情况时,将待执行函数存放到callbacks数组中。
+>
+> ```js
+> then(onFulfilled,onRejected){
+> ...
+> if(this.status==Promise.PENDING){
+> this.callbacks.push({
+> onFulfilled:value=>{
+> try {
+> onFulfilled(value);
+> } catch (error) {
+> onRejected(error);
+> }
+> }
+> onRejected: value => {
+> try {
+> onRejected(value);
+> } catch (error) {
+> onRejected(error);
+> }
+> }
+> })
+> }
+> ...
+> }
+> ```
+>
+> ##### 8、当执行resolve和reject时,在堆callacks数组中的函数进行执行
+>
+> ```js
+> resolve(vale){
+> if(this.status==Promise.PENDING){
+> this.status = Promise.FULFILLED;
+> this.value = value;
+> this.callbacks.map(callback => {
+> callback.onFulfilled(value);
+> });
+> }
+> }
+> reject(value){
+> if(this.status==Promise.PENDING){
+> this.status = Promise.REJECTED;
+> this.value = value;
+> this.callbacks.map(callback => {
+> callback.onRejected(value);
+> });
+> }
+> }
+> ```
+>
+> ##### 9、then方法中,关于处理pending状态时,异步处理的方法:只需要将resolve与reject执行通过setTimeout定义为异步任务
+>
+> ```js
+> resolve(value) {
+> if (this.status == Promise.PENDING) {
+> this.status = Promise.FULFILLED;
+> this.value = value;
+> setTimeout(() => {
+> this.callbacks.map(callback => {
+> callback.onFulfilled(value);
+> });
+> });
+> }
+> }
+> reject(value) {
+> if (this.status == Promise.PENDING) {
+> this.status = Promise.REJECTED;
+> this.value = value;
+> setTimeout(() => {
+> this.callbacks.map(callback => {
+> callback.onRejected(value);
+> });
+> });
+> }
+> }
+> ```
+>
+> 到此,promise的then方法的基本实现就结束了。
+
+
+
+### 149. *Promise* 和 *setTimeout* 的区别 ?
+
+> 参考答案:
+>
+> *JavaScript* 将异步任务分为 *MacroTask*(宏任务) 和 *MicroTask*(微任务),那么它们区别何在呢?
+>
+> 1. 依次执行同步代码直至执行完毕;
+> 2. 检查MacroTask 队列,若有触发的异步任务,则取第一个并调用其事件处理函数,然后跳至第三步,若没有需处理的异步任务,则直接跳至第三步;
+> 3. 检查MicroTask队列,然后执行所有已触发的异步任务,依次执行事件处理函数,直至执行完毕,然后跳至第二步,若没有需处理的异步任务中,则直接返回第二步,依次执行后续步骤;
+> 4. 最后返回第二步,继续检查MacroTask队列,依次执行后续步骤;
+> 5. 如此往复,若所有异步任务处理完成,则结束;
+>
+> *Promise* 是一个微任务,主线程是一个宏任务,微任务队列会在宏任务后面执行
+>
+> *setTimeout* 返回的函数是一个新的宏任务,被放入到宏任务队列
+>
+> 所以 *Promise* 会先于新的宏任务执行
+
+
+
+### 150. 如何实现 *Promise.all* ?
+
+> 参考答案:
+>
+> `Promise.all` 接收一个 `promise` 对象的数组作为参数,当这个数组里的所有 `promise` 对象全部变为`resolve`或 有 `reject` 状态出现的时候,它才会去调用 `.then` 方法,它们是并发执行的。
+>
+> 总结 `promise.all` 的特点
+>
+> 1、接收一个 `Promise` 实例的数组或具有 `Iterator` 接口的对象,
+>
+> 2、如果元素不是 `Promise` 对象,则使用 `Promise.resolve` 转成 `Promise` 对象
+>
+> 3、如果全部成功,状态变为 `resolved`,返回值将组成一个数组传给回调
+>
+> 4、只要有一个失败,状态就变为 `rejected`,返回值将直接传递给回调
+> `all()` 的返回值也是新的 `Promise` 对象
+>
+> 实现 `Promise.all` 方法
+>
+> ```js
+> function promiseAll(promises) {
+> return new Promise(function (resolve, reject) {
+> if (!isArray(promises)) {
+> return reject(new TypeError('arguments must be an array'));
+> }
+> var resolvedCounter = 0;
+> var promiseNum = promises.length;
+> var resolvedValues = new Array(promiseNum);
+> for (var i = 0; i < promiseNum; i++) {
+> (function (i) {
+> Promise.resolve(promises[i]).then(function (value) {
+> resolvedCounter++
+> resolvedValues[i] = value
+> if (resolvedCounter == promiseNum) {
+> return resolve(resolvedValues)
+> }
+> }, function (reason) {
+> return reject(reason)
+> })
+> })(i)
+> }
+> })
+> }
+> ```
+
+
+
+### 151. 如何实现 *Promise.finally* ?
+
+> 参考答案:
+>
+> *finally* 方法是 *ES2018* 的新特性
+>
+> *finally* 方法用于指定不管 *Promise* 对象最后状态如何,都会执行的操作,执行 *then* 和 *catch* 后,都会执行 *finally* 指定的回调函数。
+>
+> 方法一:借助 *promise.prototype.finally* 包
+>
+> ```js
+> npm install promise-prototype-finally
+> ```
+>
+> ```js
+> const promiseFinally = require('promise.prototype.finally');
+>
+> // 向 Promise.prototype 增加 finally()
+> promiseFinally.shim();
+>
+> // 之后就可以按照上面的使用方法使用了
+> ```
+>
+>
+>
+> 方法二:实现 *Promise.finally*
+>
+> ```js
+> Promise.prototype.finally = function (callback) {
+> let P = this.constructor;
+> return this.then(
+> value => P.resolve(callback()).then(() => value),
+> reason => P.resolve(callback()).then(() => { throw reason })
+> );
+> };
+> ```
+>
+>
+
+
+
+### 152. 如何判断 *img* 加载完成
+
+> 参考答案:
+>
+> - 为 *img DOM* 节点绑定 *load* 事件
+> - *readystatechange* 事件:*readyState* 为 *complete* 和 *loaded* 则表明图片已经加载完毕。测试 *IE6-IE10* 支持该事件,其它浏览器不支持。
+> - *img* 的 *complete* 属性:轮询不断监测 *img* 的 *complete* 属性,如果为 *true* 则表明图片已经加载完毕,停止轮询。该属性所有浏览器都支持。
+
+
+
+### 153. 如何阻止冒泡?
+
+> 参考答案:
+>
+> ```js
+> // 方法一:IE9+,其他主流浏览器
+> event.stopPropagation()
+> // 方法二:火狐未实现
+> event.cancelBubble = true;
+> // 方法三:不建议滥用,jq 中可以同时阻止冒泡和默认事件
+> return false;
+> ```
+
+
+
+### 154. 如何阻止默认事件?
+
+> 参考答案:
+>
+> ```js
+> // 方法一:全支持
+> event.preventDefault();
+> // 方法二:该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。
+> event.returnValue=false;
+> // 方法三:不建议滥用,jq 中可以同时阻止冒泡和默认事件
+> return false;
+> ```
+
+
+
+### 155. 如何用原生 *js* 给一个按钮绑定两个 *onclick* 事件?
+
+> 参考答案:
+>
+> 使用 *addEventListener* 方法来绑定事件,就可以绑定多个同种类型的事件。
+
+
+
+### 156. 拖拽会用到哪些事件
+
+> 参考答案:
+>
+> 在以前,书写一个拖拽需要用到 *mousedown、mousemove、mouseup* 这 *3* 个事件。
+>
+> *HTML5* 推出后,新推出了一组拖拽相关的 *API*,涉及到的事件有 *dragstart、dragover、drop* 这 *3* 个事件。
+
+
+
+### 157. *document.write* 和 *innerHTML* 的区别
+
+> 参考答案:
+>
+> *document.write* 是直接写入到页面的内容流,如果在写之前没有调用 *document.open*, 浏览器会自动调用 *open*。每次写完关闭之后重新调用该函数,会导致页面全部重绘。
+>
+> *innerHTML* 则是 *DOM* 页面元素的一个属性,代表该元素的 *html* 内容。你可以精确到某一个具体的元素来进行更改。如果想修改 *document* 的内容,则需要修改 *document.documentElement.innerElement*。
+>
+> *innerHTML* 很多情况下都优于 *document.write*,其原因在于不会导致页面全部重绘。
+
+
+
+### 158. *jQuery* 的事件委托方法 *bind 、live、delegate、one、on* 之间有什么区别?
+
+> 参考答案:
+>
+> 这几个方法都可以实现事件处理。其中 *on* 集成了事件处理的所有功能,也是目前推荐使用的方法。
+>
+> *one* 是指添加的是一次性事件,意味着只要触发一次该事件,相应的处理方法执行后就自动被删除。
+>
+> *bind* 是较早版本的绑定事件的方法,现在已被 *on* 替代。
+>
+> *live* 和 *delegate* 主要用来做事件委托。*live* 的版本较早,现在已被废弃。*delegate* 目前仍然可用,不过也可用 *on* 来替代它。
+
+
+
+### 159. *$(document).ready* 方法和 *window.onload* 有什么区别?
+
+> 参考答案:
+>
+> 主要有两点区别:
+>
+> 1. 执行时机
+>
+> *window.onload* 方法是在网页中的所有的元素(包括元素的所有关联文件)都完全加载到浏览器之后才执行。而通过 *jQuery* 中的`$(document).ready`方法注册的事件处理程序,只要在 *DOM* 完全就绪时,就可以调用了,比如一张图片只要`
+>
+> 更多 *Array* 相关用法可以参阅:*https://www.w3school.com.cn/jsref/jsref_obj_array.asp*
+
+
+
+### 198. 数组去重的多种实现方式
+
+> 参考答案:
+>
+> 请参阅前面第 *2* 题答案。
+
+
+
+### 199. 什么是预解析(预编译)
+
+> 参考答案:
+>
+> 所谓的预解析(预编译)就是:在当前作用域中,*JavaScript* 代码执行之前,浏览器首先会默认的把所有带 *var* 和 *function* 声明的变量进行提前的声明或者定义。
+>
+> 另外,*var* 声明的变量和 *function* 声明的函数在预解析的时候有区别,*var* 声明的变量在预解析的时候只是提前的声明,*function* 声明的函数在预解析的时候会提前声明并且会同时定义。也就是说 *var* 声明的变量和 *function* 声明的函数的区别是在声明的同时有没有同时进行定义。
+
+
+
+### 200. 原始值类型和引用值类型的区别是什么?
+
+> 参考答案:
+>
+> 可以参阅前面第 *26* 题
+
+
+
+### 201. 冒泡排序的思路,不用 *sort*
+
+> 参考答案:
+>
+> 示例代码如下:
+>
+> ```js
+> var examplearr = [8, 94, 15, 88, 55, 76, 21, 39];
+> function sortarr(arr) {
+> for (i = 0; i < arr.length - 1; i++) {
+> for (j = 0; j < arr.length - 1 - i; j++) {
+> if (arr[j] > arr[j + 1]) {
+> var temp = arr[j];
+> arr[j] = arr[j + 1];
+> arr[j + 1] = temp;
+> }
+> }
+> }
+> return arr;
+> }
+> sortarr(examplearr);
+> console.log(examplearr); // [8, 15, 21, 39, 55, 76, 88, 94]
+> ```
+
+
+
+### 202. *symbol* 用途
+
+> 参考答案:
+>
+> 可以用来表示一个独一无二的变量防止命名冲突。但是面试官问还有吗?我没想出其他的用处就直接答我不知道了,还可以利用 symbol 不会被常规的方法(除了 Object.getOwnPropertySymbols 外)遍历到,所以可以用来模拟私有变量。
+>
+> 主要用来提供遍历接口,布置了 symbol.iterator 的对象才可以使用 for···of 循环,可以统一处理数据结构。调用之后回返回一个遍历器对象,包含有一个 next 方法,使用 next 方法后有两个返回值 value 和 done 分别表示函数当前执行位置的值和是否遍历完毕。
+>
+> Symbol.for() 可以在全局访问 symbol
+
+
+
+### 203. 什么是函数式编程,应用场景是什么
+
+> 参考答案:
+>
+> 函数式编程和面向对象编程一样,是一种编程范式。强调执行的过程而非结果,通过一系列的嵌套的函数调用,完成一个运算过程。
+> 它主要有以下几个特点:
+>
+> 1. 函数是"一等公民":函数优先,和其他数据类型一样。
+> 2. 只用"表达式",不用"语句":通过表达式(*expression*)计算过程得到一个返回值,而不是通过一个语句(*statement*)修改某一个状态。
+> 3. 无副作用:不污染变量,同一个输入永远得到同一个数据。
+> 4. 不可变性:前面一提到,不修改变量,返回一个新的值。
+>
+> 函数式编程的概念其实出来也已经好几十年了,我们能在很多编程语言身上看到它的身影。比如比较纯粹的 *Haskell*,以及一些语言开始逐渐成为多范式编程语言,比如 *Swift*,还有 *Kotlin,Java,Js* 等都开始具备函数式编程的特性。
+>
+> **函数式编程在前端的应用场景**
+>
+> - *Stateless components*:*React* 在 *0.14* 之后推出的无状态组件
+> - *Redux*
+>
+> **函数式编程在后端的应用场景**
+>
+> - *Lambda* 架构
+
+
+
+### 204. 事件以及事件相关的兼容性问题
+
+> 参考答案:
+>
+> 事件最早是在 *IE3* 和 *Navigator2* 中出现的,当时是作为分担服务器运算负担的一种手段。要实现和网页的互动,就需要通过 *JavaScript* 里面的事件来实现。
+>
+> 每次用户与一个网页进行交互,例如点击链接,按下一个按键或者移动鼠标时,就会触发一个事件。我们的程序可以检测这些事件,然后对此作出响应。从而形成一种交互。
+>
+> 当我们绑定事件时,需要遵循事件三要素
+>
+> - 事件源:是指那个元素引发的事件。比如当你点击图标的时候,会跳转到百度首页。那么这个图标就是事件源。
+> - 事件:事件是指执行的动作。例如,点击,鼠标划过,按下键盘,获得焦点。
+> - 事件驱动程序:事件驱动程序即执行的结果。例如,当你点击图标的时候,会跳转到百度首页。那么跳转到百度首页就是事件的处理结果。
+>
+> ```js
+> 事件源.事件 = function() {
+> 事件处理函数
+> }
+> ```
+>
+> 常见的兼容问题,可以参阅前面 *135* 题。
+
+
+
+### 205. *JS* 小数不精准,如何计算
+
+> 参考答案:
+>
+> 方法一:指定要保留的小数位数(0.1+0.2).toFixed(1) = 0.3;这个方法toFixed是进行四舍五入的也不是很精准,对于计算金额这种严谨的问题,不推荐使用,而且不同浏览器对toFixed的计算结果也存在差异。
+>
+> 方法二:把需要计算的数字升级(乘以10的n次幂)成计算机能够精确识别的整数,等计算完毕再降级(除以10的n次幂),这是大部分编程语言处理精度差异的通用方法。
+
+
+
+### 206. 写一个 *mySetInterVal(fn, a, b)*,每次间隔 *a,a+b,a+2b* 的时间,然后写一个 *myClear*,停止上面的 *mySetInterVal*
+
+> 参考答案:
+>
+> 该题的思路就是每一次在定时器中重启定时器并且在时间每一次都加 *b*,并且要把定时器返回回来,可以作为*myClear*的参数。
+>
+> 代码如下:
+>
+> ```js
+> var mySetInterVal = function (fn, a, b) {
+> var timer = null;
+> var settimer = function (fn, a, b) {
+> timer = setTimeout(() => {
+> fn();
+> settimer(fn, a + b, b);
+> }, a);
+> }
+> settimer(fn, a, b);
+> return timer;
+> }
+>
+> var timer = mySetInterVal(() => { console.log('timer') }, 1000, 1000);
+> var myClear = function (timer) {
+> timer && clearTimeout(timer);
+> }
+> ```
+
+
+
+### 207. 合并二维有序数组成一维有序数组,归并排序的思路
+
+> 参考答案:
+>
+> 示例代码如下:
+>
+> ```js
+> function merge(left, right) {
+> let result = []
+> while (left.length > 0 && right.length > 0) {
+> if (left[0] < right[0]) {
+> result.push(left.shift())
+> } else {
+> result.push(right.shift())
+> }
+> }
+> return result.concat(left).concat(right)
+> }
+> function mergeSort(arr) {
+> if (arr.length === 1) {
+> return arr
+> }
+> while (arr.length > 1) {
+> let arrayItem1 = arr.shift();
+> let arrayItem2 = arr.shift();
+> let mergeArr = merge(arrayItem1, arrayItem2);
+> arr.push(mergeArr);
+> }
+> return arr[0]
+> }
+>
+> let arr1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 2, 3], [4, 5, 6]];
+> let arr2 = [[1, 4, 6], [7, 8, 10], [2, 6, 9], [3, 7, 13], [1, 5, 12]];
+> console.log(mergeSort(arr1))
+> console.log(mergeSort(arr2))
+> ```
+
+
+
+### 208. 给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。
+
+> 参考答案:
+>
+> 首先,我们肯定需要封装一个函数,而这个函数接收一个字符串作为参数,返回不含有重复字符的子串长度。来看下面的示例:
+>
+> 示例 1:
+>
+> 输入: “abcabcbb”
+> 输出: 3
+> 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
+>
+> 示例 2:
+>
+> 输入: “bbbbb”
+> 输出: 1
+> 解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
+>
+> 示例 3:
+>
+> 输入: “pwwkew”
+> 输出: 3
+> 解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
+> 请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
+>
+> 示例代码:
+>
+> ```js
+> var lengthOfLongestSubstring = function (s) {
+> var y = [];
+> var temp = [];
+> var maxs = 0;
+> if (s == "") {
+> return 0;
+> }
+> if (s.length == 1) {
+> return 1;
+> }
+> for (var i = 0; i < s.length; i++) {
+> if (temp.includes(s[i])) {
+>
+> y.push(temp.length);
+> temp.shift();
+> continue;
+> } else {
+> temp.push(s[i])
+> y.push(temp.length);
+> }
+>
+> }
+> for (var j = 0; j < y.length; j++) {
+> if (maxs <= y[j]) {
+> maxs = y[j]
+> }
+> }
+> return maxs;
+> };
+> // 测试
+> console.log(lengthOfLongestSubstring('abcabcbb')); // 3
+> console.log(lengthOfLongestSubstring('bbbbb')); // 1
+> console.log(lengthOfLongestSubstring('pwwkew')); // 3
+>
+> ```
+
+
+
+### 209. 有一堆整数,请把他们分成三份,确保每一份和尽量相等(11,42,23,4,5,6 4 5 6 11 23 42 56 78 90)(滴滴 *2020*)
+
+> 参考答案:
+>
+> 本道题目是一道考察算法的题目,主要是考察编程基本功和一定的想像力。
+>
+> 具体的实现如下:
+>
+> ```js
+> function fun(total, n) {
+> //先对整个数组进行排序
+> total.sort((a, b) => a - b);
+>
+> //求和
+> var sum = 0;
+> for (var i = 0; i < total.length; i++) {
+> sum += total[i];
+> }
+>
+> var avg = Math.ceil(sum / n);
+>
+> //结果数组
+> var result = []; //长度为n
+>
+> for (var i = 0; i < n; i++) {
+> result[i] = [total.pop()];
+> result[i].sum = result[i][0];
+>
+> //组成一个分数组
+> while (result[i].sum < avg && total.length > 0) {
+> for (var j = 0; j < total.length; j++) {
+> if (result[i].sum + total[j] >= avg) {
+> result[i].push(total[j]);
+> result[i].sum += total[j];
+> break;
+> }
+> }
+>
+> if (j == total.length) {
+> result[i].push(total.pop());
+> result[i].sum += result[i][result[i].length - 1];
+> } else {
+> //从数组中移除此元素
+> total.splice(j, 1);
+> }
+> }
+>
+> sum -= result[i].sum;
+> avg = Math.ceil(sum / (n - 1 - i));
+>
+> }
+> return result;
+> }
+>
+> // 测试
+> var arr = [11, 42, 23, 4, 5, 6, 4, 5, 6, 11, 23, 42, 56, 78, 90];
+> console.log(fun(arr, 3));
+> // [
+> // [ 90, 56, sum: 146 ],
+> // [ 78, 42, 11, sum: 131 ],
+> // [ 42, 23, 23, 11, 6, 6, 5, 5, 4, 4, sum: 129 ]
+> // ]
+> ```
+
+
+
+### 210. 手写发布订阅(头条2020)
+
+> 参考答案:
+>
+> 示例代码如下:
+>
+> ```html
+>
+> this is a test
+> {{msg}}{{msg}} +>
+>
+> JavaScript 中的函数是存储在堆内存中的,具体的步骤如下:
+>
+> 1. 开辟堆内存(*16* 进制得到内存地址)
+> 2. 声明当前函数的作用域(函数创建的上下文才是他的作用域,和在那执行的无关)
+> 3. 把函数的代码以字符串的形式存储在堆内存中(函数再不执行的情况下,只是存储在堆内存中的字符串)
+> 4. 将函数堆的地址,放在栈中供变量调用(函数名)
+
+
+
+### 218. *JavaScript* 是如何运行的?解释型语言和编译型语言的差异是什么?
+
+> 参考答案:
+>
+> 关于第一个问题,这不是三言两语或者几行文字就能够讲清楚的,这里放上一篇博文地址:
+>
+> *https://segmentfault.com/a/1190000019530109*
+>
+> 之后在直播课或者录屏课进行详细的讲解
+>
+> 第二个问题:解释型语言和编译型语言的差异是什么?
+>
+> 电脑能认得的是二进制数,不能够识别高级语言。所有高级语言在电脑上执行都需要先转变为机器语言。但是高级语言有两种类型:编译型语言和解释型语言。常见的编译型语言语言有C/C++、Pascal/Object 等等。常见的解释性语言有python、JavaScript等等。
+>
+> 编译型语言先要进行编译,然后转为特定的可执行文件,这个可执行文件是针对平台的(CPU类型),可以这么理解你在PC上编译一个C源文件,需要经过预处理,编译,汇编等等过程生成一个可执行的二进制文件。当你需要再次运行改代码时,不需要重新编译代码,只需要运行该可执行的二进制文件。优点,编译一次,永久执行。还有一个优点是,你不需要提供你的源代码,你只需要发布你的可执行文件就可以为客户提供服务,从而保证了你的源代码的安全性。但是,如果你的代码需要迁移到linux、ARM下时,这时你的可执行文件就不起作用了,需要根据新的平台编译出一个可执行的文件。这也就是多个平台需要软件的多个版本。缺点是,跨平台能力差。
+>
+> 解释型语言需要一个解释器,在源代码执行的时候被解释器翻译为一个与平台无关的中间代码,解释器会把这些代码翻译为及其语言。打个比方,编译型中的编译相当于一个翻译官,它只能翻译英语,而且中文文章翻译一次就不需要重新对文章进行二次翻译了,但是如果需要叫这个翻译官翻译德语就不行了。而解释型语言中的解释器相当于一个会各种语言的机器人,而且这个机器人回一句一句的翻译你的语句。对于不同的国家,翻译成不同的语言,所以,你只需要带着这个机器人就可以。解释型语言的有点是,跨平台,缺点是运行时需要源代码,知识产权保护性差,运行效率低。
+
+
+
+### 219. 列举你所了解的编程范式?
+
+> 参考答案:
+>
+> 编程范式 *Programming paradigm* 是指计算机中编程的典范模式或方法。
+>
+> 常见的编程范式有:函数式编程、程序编程、面向对象编程、指令式编程等。
+>
+> 不同的编程语言也会提倡不同的“编程范型”。一些语言是专门为某个特定的范型设计的,如 *Smalltalk* 和 *Java* 支持面向对象编程。而 *Haskell* 和 *Scheme* 则支持函数式编程。现代编程语言的发展趋势是支持多种范型,例如 *ES* 支持函数式编程的同时也支持面向对象编程。
+
+
+
+### 220. 什么是面向切面(AOP)的编程?
+
+> 参考答案:
+>
+> **什么是AOP?**
+>
+> AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后, 再通过“动态织入”的方式掺入业务逻辑模块中。
+>
+>
+>
+> **AOP能给我们带来什么好处?**
+>
+> AOP的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用日志统计等功能模块。
+>
+>
+>
+> **JavaScript实现AOP的思路?**
+>
+> 通常,在 JavaScript 中实现 AOP,都是指把一个函数“动态织入”到另外一个函数之中,具体的实现技术有很多,下面我用扩展 Function.prototype 来做到这一点。
+>
+> 主要就是两个函数,在Function的原型上加上before与after,作用就是字面的意思,在函数的前面或后面执行,相当于**无侵入**把一个函数插入到另一个函数的前面或后面,应用得当可以很好的实现代码的解耦,js中的代码实现如下:
+>
+> ```js
+> //Aop构造器
+> function Aop(options){
+> this.options = options
+> }
+> //业务方法执行前钩子
+> Aop.prototype.before = function(cb){
+> cb.apply(this)
+> }
+> //业务方法执行后钩子
+> Aop.prototype.after = function(cb){
+> cb.apply(this)
+> }
+> //业务方法执行器
+> Aop.prototype.execute = function(beforeCb,runner,afterCb){
+> this.before(beforeCb)
+> runner.apply(this)
+> this.after(afterCb)
+> }
+>
+> var aop = new Aop({
+> afterInfo:'执行后',
+> runnerInfo:'执行中',
+> beforeInfo:'执行前'
+> })
+>
+> var beforeCb = function(){
+> console.log(this.options.beforeInfo)
+> }
+> var afterCb = function(){
+> console.log(this.options.afterInfo)
+> }
+> var runnerCb = function(){
+> console.log(this.options.runnerInfo)
+> }
+>
+> aop.execute(beforeCb,runnerCb,afterCb)
+> ```
+>
+> 应用的一些例子:
+>
+> 1. 为 *window.onload* 添加方法,防止 *window.onload* 被二次覆盖
+> 2. 无侵入统计某个函数的执行时间
+> 3. 表单校验
+> 4. 统计埋点
+> 5. 防止 *csrf* 攻击
+
+
+
+### 221. *JavaScript* 中的 *const* 数组可以进行 *push* 操作吗?为什么?
+
+> 参考答案:
+>
+> 可以进行 *push* 操作。虽然 *const* 表示常量,但是当我们把一个数组赋值给 *const* 声明的变量时,实际上是把这个数组的地址赋值给该变量。而 *push* 操作是在数组地址所指向的堆区添加元素,地址本身并没有发生改变。
+>
+> 示例代码:
+>
+> ```js
+> const arr = [1];
+> arr.push(2);
+> console.log(arr); // [1, 2]
+> ```
+
+
+
+### 222. JavaScript 中对象的属性描述符有哪些?分别有什么作用?
+
+> 参考答案:
+>
+> 从*ES5*开始,添加了对对象**属性描述符**的支持。现在*JavaScript*中支持 *4* 种属性描述符:
+>
+> - **configurable:** 当且仅当该属性的*configurable*键值为*true*时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
+> - **enumerable:** 当且仅当该属性的*enumerable*键值为*true*时,该属性才会出现在对象的枚举属性中。
+> - **value:** 该属性对应的值。可以是任何有效的 *JavaScript* 值(数值,对象,函数等)。
+> - **writable:** 当且仅当该属性的*writable*键值为*true*时,属性的值,也就是上面的value,才能被赋值运算符改变。
+
+
+
+### 223. *JavaScript* 中 *console* 有哪些 *api* ?
+
+> 参考答案:
+>
+> **console.assert(expression, object[, object...])**
+>
+> 接收至少两个参数,第一个参数的值或返回值为`false`的时候,将会在控制台上输出后续参数的值。
+>
+> **console.count([label])**
+>
+> 输出执行到该行的次数,可选参数 label 可以输出在次数之前。
+>
+> **console.dir(object)**
+>
+> 将传入对象的属性,包括子对象的属性以列表形式输出。
+>
+> **console.error(object[, object...])**
+>
+> 用于输出错误信息,用法和常见的`console.log`一样,不同点在于输出内容会标记为错误的样式,便于分辨。
+>
+> **console.group**
+>
+> 这是个有趣的方法,它能够让控制台输出的语句产生不同的层级嵌套关系,每一个`console.group()`会增加一层嵌套,相反要减少一层嵌套可以使用`console.groupEnd()`方法。
+>
+> **console.info(object[, object...])**
+>
+> 此方法与之前说到的`console.error`一样,用于输出信息,没有什么特别之处。
+>
+> **console.table()**
+>
+> 可将传入的对象,或数组以表格形式输出,相比传统树形输出,这种输出方案更适合内部元素排列整齐的对象或数组,不然可能会出现很多的 undefined。
+>
+> **console.log(object[, object...])**
+>
+> 输入一段 *log* 信息。
+>
+> **console.profile([profileLabel])**
+>
+> 这是个挺高大上的东西,可用于性能分析。在 JS 开发中,我们常常要评估段代码或是某个函数的性能。在函数中手动打印时间固然可以,但显得不够灵活而且有误差。借助控制台以及`console.profile()`方法我们可以很方便地监控运行性能。
+>
+> **console.time(name)**
+> 计时器,可以将成对的`console.time()`和`console.timeEnd()`之间代码的运行时间输出到控制台上,`name`参数可作为标签名。
+>
+> **console.trace()**
+>
+> `console.trace()`用来追踪函数的调用过程。在大型项目尤其是框架开发中,函数的调用轨迹可以十分复杂,`console.trace()`方法可以将函数的被调用过程清楚地输出到控制台上。
+>
+> **console.warn(object[, object...])**
+>
+> 输出参数的内容,作为警告提示。
+
+
+
+### 224. 简单对比一下 *Callback、Promise、Generator、Async* 几个异步 *API* 的优劣?
+
+> 参考答案:
+>
+> 请参阅前面第 *31* 题答案。
+
+
+
+### 225. *Object.defineProperty* 有哪几个参数?各自都有什么作用
+
+> 参考答案:
+>
+> 在 *JavaScript* 中,通过 *Object.defineProperty* 方法可以设置对象属性的特性,选项如下:
+>
+> - *get*:一旦目标属性被访问时,就会调用相应的方法
+> - *set*:一旦目标属性被设置时,就会调用相应的方法
+> - *value*:这是属性的值,默认是 *undefined*
+> - *writable*:这是一个布尔值,表示一个属性是否可以被修改,默认是 *true*
+> - *enumerable*:这是一个布尔值,表示在用 *for-in* 循环遍历对象的属性时,该属性是否可以显示出来,默认值为 *true*
+> - *configurable*:这是一个布尔值,表示我们是否能够删除一个属性或者修改属性的特性,默认值为 *true*
+
+
+
+### 226. *Object.defineProperty* 和 *ES6* 的 *Proxy* 有什么区别?
+
+> 参考答案:
+>
+> ##### 1、*Object.defineproperty*
+>
+> 可以用于监听对象的数据变化
+>
+> 语法: ***Object.defineproperty(obj, key, descriptor)***
+>
+> ```javascript
+> let obj = {
+> age: 11
+> }
+> let value = 'xiaoxiao';
+> //defineproperty 有 gettter 和 setter
+> Object.defineproperty(obj, 'name', {
+> get() {
+> return value
+> },
+> set(newValue) {
+> value = newValue
+> }
+> })
+> obj.name = 'pengpeng';
+> ```
+>
+> 此外 还有以下配置项 :
+>
+> - *configurable*
+> - *enumerable*
+> - *value*
+>
+> 缺点:
+>
+> 1. 无法监听数组变化
+>
+> 2. 只能劫持对象的属性,属性值也是对象那么需要深度遍历
+>
+> ##### 2、*proxy* :可以理解为在被劫持的对象之前 加了一层拦截
+>
+> ```javascript
+> let proxy = new Proxy({}, {
+> get(obj, prop) {
+> return obj[prop]
+> },
+> set(obj, prop, val) {
+> obj[prop] = val
+> }
+> })
+> ```
+>
+> - *proxy* 返回的是一个新对象, 可以通过操作返回的新的对象达到目的
+> - *proxy* 有多达 *13* 种拦截方法
+>
+> **总结:**
+>
+> - *Object.defineProperty* 无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应
+> - *Object.defineProperty* 只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。*Proxy* 可以劫持整个对象,并返回一个新的对象。
+> - *Proxy* 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。
+
+
+
+### 227. *intanceof* 操作符的实现原理及实现
+
+> 参考答案:
+>
+> *instanceof* 主要作用就是判断一个实例是否属于某种类型
+>
+> 例如:
+>
+> ```js
+> let Dog = function(){}
+> let tidy = new Dog()
+> tidy instanceof Dog //true
+> ```
+>
+> *intanceof* 操作符实现原理
+>
+> ```js
+> function wonderFulInstanceOf(instance, constructorFn) {
+> let constructorFnProto = constructorFn.prototype; // 取右表达式的 prototype 值,函数构造器指向的function
+> instanceProto = instance.__proto__; // 取左表达式的__proto__值,实例的__proto__
+> while (true) {
+> if (instanceProto === null) {
+> return false;
+> }
+> if (instanceProto === constructorFnProto) {
+> return true;
+> }
+> instanceProto = instanceProto.__proto__
+> }
+> }
+> ```
+>
+> 其实 *instanceof* 主要的实现原理就是只要 *constructorFn* 的 *prototype* 在*instance*的原型链上即可。
+>
+> 因此,*instanceof* 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 *prototype*,如果查找失败,则会返回 *false*,告诉我们左边变量并非是右边变量的实例。
+
+
+
+### 228. 强制类型转换规则?
+
+> 参考答案:
+>
+> 首先需要参阅前面第 *104* 题答案。了解隐式转换所调用的函数。
+>
+> 当程序员显式调用 Boolean(value)、Number(value)、String(value) 完成的类型转换,叫做显示类型转换。
+>
+> 当通过 new Boolean(value)、new Number(value)、new String(value) 传入各自对应的原始类型的值,可以实现“装箱”,将原始类型封装成一个对象。
+>
+> 其实这三个函数不仅仅可以当作构造函数,它们可以直接当作普通的函数来使用,将任何类型的参数转化成原始类型的值:
+>
+> ```javascript
+> Boolean('sdfsd'); // true
+> Number("23"); // 23
+> String({a:24}); // "[object Object]"
+> ```
+>
+> 其实这三个函数用于类型转换的时候,调用的就是 js 内部的 *ToBoolean ( argument )、ToNumber ( argument )、ToString ( argument )* 方法,从而达到显式转换的效果。
+
+
+
+### 229. *Object.is*( ) 与比较操作符 “===”、“==” 的区别
+
+> 参考答案:
+>
+> == (或者 !=) 操作在需要的情况下自动进行了类型转换。=== (或 !==)操作不会执行任何转换。
+>
+> ===在比较值和类型时,可以说比==更快。
+>
+> 而在*ES6*中,*Object.is*( ) 类似于 ===,但在三等号判等的基础上特别处理了 *NaN* 、-0 和 +0 ,保证 -0 和 +0 不再相同,但 *Object.is(NaN, NaN)* 会返回 *true*。
+
+
+
+### 230. `+` 操作符什么时候用于字符串的拼接?
+
+> 参考答案:
+>
+> 在有一边操作数是字符串时会进行字符串拼接。
+>
+> 示例代码:
+>
+> ```js
+> console.log(5 + '5', typeof (5 + '5')); // 55 string
+> ```
+
+
+
+### 231. *object.assign* 和扩展运算法是深拷贝还是浅拷贝
+
+> 参考答案:
+>
+> 这两个方式都是浅拷贝。
+>
+> 在拷贝的对象只有一层时是深拷贝,但是一旦对象的属性值又是一个对象,也就是有两层或者两层以上时,就会发现这两种方式都是浅拷贝。
+
+
+
+### 232. *const* 对象的属性可以修改吗
+
+> 参考答案:
+>
+> 可以修改,具体原因可以参阅前面第 *231* 题。
+
+
+
+### 233. 如果 *new* 一个箭头函数的会怎么样
+
+> 参考答案:
+>
+> 会报错,因为箭头函数无法作为构造函数。
+
+
+
+### 234. 扩展运算符的作用及使用场景
+
+> 参考答案:
+>
+> 扩展运算符是三个点(...),主要用于展开数组,将一个数组转为参数序列。
+>
+> 扩展运算符使用场景:
+>
+> - 代替数组的 *apply* 方法
+> - 合并数组
+> - 复制数组
+> - 把 *arguments* 或 *NodeList* 转为数组
+> - 与解构赋值结合使用
+> - 将字符串转为数组
+
+
+
+### 235. *Proxy* 可以实现什么功能?
+
+> 参考答案:
+>
+> *Proxy* 是 *ES6* 中新增的一个特性。*Proxy* 让我们能够以简洁易懂的方式控制外部对对象的访问。其功能非常类似于设计模式中的代理模式。
+>
+> *Proxy* 在目标对象的外层搭建了一层拦截,外界对目标对象的某些操作,必须通过这层拦截。
+>
+> 使用 *Proxy* 的好处是对象只需关注于核心逻辑,一些非核心的逻辑(如:读取或设置对象的某些属性前记录日志;设置对象的某些属性值前,需要验证;某些属性的访问控制等)可以让 *Proxy* 来做。从而达到关注点分离,降级对象复杂度的目的。
+>
+> *Proxy* 的基本语法如下:
+>
+> ```javascript
+> var proxy = new Proxy(target, handler);
+> ```
+>
+> 通过构造函数来生成 *Proxy* 实例,构造函数接收两个参数。*target* 参数是要拦截的目标对象,*handler* 参数也是一个对象,用来定制拦截行为。
+>
+> *Vue 3.0* 主要就采用的 *Proxy* 特性来实现响应式,相比以前的 *Object.defineProperty* 有以下优点:
+>
+> - 可以劫持整个对象,并返回一个新的对象
+> - 有 *13* 种劫持操作
+
+
+
+### 236. 对象与数组的解构的理解
+
+> 参考答案:
+>
+> 解构是 *ES6* 的一种语法规则,可以将一个对象或数组的某个属性提取到某个变量中。
+>
+> 解构对象示例:
+>
+> ```js
+> // var/let/const{属性名}=被解构的对象
+> const user = {
+> name: "abc",
+> age: 18,
+> sex: "男",
+> address: {
+> province: "重庆",
+> city: "重庆"
+> }
+> }
+> let { name, age, sex, address} = user;
+> console.log(name, age, sex, address);
+> ```
+>
+> 解构数组示例:
+>
+> ```js
+> const [a, b, c] = [1, 2, 3];
+> ```
+
+
+
+### 237. 如何提取高度嵌套的对象里的指定属性?
+
+> 参考答案:
+>
+> 一般会使用递归的方式来进行查找。下面是一段示例代码:
+>
+> ```js
+> function findKey(data, field) {
+> let finding = '';
+> for (const key in data) {
+> if (key === field) {
+> finding = data[key];
+> }
+> if (typeof (data[key]) === 'object') {
+> finding = findKey(data[key], field);
+> }
+> if (finding) {
+> return finding;
+> }
+> }
+> return null;
+> }
+> // 测试
+> console.log(findKey({
+> name: 'zhangsan',
+> age: 18,
+> stuInfo: {
+> stuNo: 1,
+> classNo: 2,
+> score: {
+> htmlScore: 100,
+> cssScore: 90,
+> jsScore: 95
+> }
+> }
+> }, 'cssScore')); // 90
+> ```
+
+
+
+### 238. *Unicode、UTF-8、UTF-16、UTF-32* 的区别?
+
+> 参考答案:
+>
+> *Unicode* **为世界上所有字符都分配了一个唯一的数字编号**,这个编号范围从 *0x000000* 到 *0x10FFFF* (十六进制),有 *110* 多万,每个字符都有一个唯一的 *Unicode* 编号,这个编号一般写成 *16* 进制,在前面加上 U+。例如:“马”的 *Unicode* 是 *U+9A6C*。
+> *Unicode* 就相当于一张表,建立了字符与编号之间的联系。
+>
+>
+>
+> ***Unicode* 本身只规定了每个字符的数字编号是多少,并没有规定这个编号如何存储。**
+>
+> 那我们可以直接把 *Unicode* 编号直接转换成二进制进行存储,怎么对应到二进制表示呢?
+>
+> *Unicode* 可以使用的编码有三种,分别是:
+>
+> - *UFT-8*:一种变长的编码方案,使用 *1~6* 个字节来存储;
+> - *UFT-32*:一种固定长度的编码方案,不管字符编号大小,始终使用 *4* 个字节来存储;
+> - *UTF-16*:介于 *UTF-8* 和 *UTF-32* 之间,使用 *2* 个或者 *4* 个字节来存储,长度既固定又可变。
+
+
+
+### 239. 为什么函数的 *arguments* 参数是类数组而不是数组?如何遍历类数组?
+
+> 参考答案:
+>
+> 首先了解一下什么是数组对象和类数组对象。
+>
+> 数组对象:使用单独的变量名来存储一系列的值。从 *Array* 构造函数中继承了一些用于进行数组操作的方法。
+>
+> 例如:
+>
+> ```js
+> var mycars = new Array();
+> mycars[0] = "zhangsan";
+> mycars[1] = "lisi";
+> mycars[2] = "wangwu";
+> ```
+>
+> 类数组对象:**对于一个普通的对象来说,如果它的所有 property 名均为正整数,同时也有相应的length属性,那么虽然该对象并不是由Array构造函数所创建的,它依然呈现出数组的行为,在这种情况下,这些对象被称为“类数组对象”。**
+>
+> **两者区别**
+>
+> - 一个是对象,一个是数组
+>
+> - 数组的*length*属性,当新的元素添加到列表中的时候,其值会自动更新。类数组对象的不会。
+>
+> - 设置数组的*length*属性可以扩展或截断数组。
+>
+> - 数组也是*Array*的实例可以调用*Array*的方法,比如*push、pop*等等
+>
+> 所以说*arguments*对象不是一个 *Array* 。它类似于*Array*,但除了*length*属性和索引元素之外没有任何*Array*属性。
+>
+> 可以使用 *for...in* 来遍历 *arguments* 这个类数组对象。
+
+
+
+### 240. *escape、encodeURI、encodeURIComponent* 的区别
+
+> 参考答案:
+>
+> *escape* 除了 *ASCII* 字母、数字和特定的符号外,对传进来的字符串全部进行转义编码,因此如果想对 *URL* 编码,最好不要使用此方法。
+>
+> *encodeURI* 用于编码整个 *URI*,因为 *URI* 中的合法字符都不会被编码转换。
+>
+> *encodeURIComponent* 方法在编码单个*URIComponent*(指请求参数)应当是最常用的,它可以讲参数中的中文、特殊字符进行转义,而不会影响整个 *URL*。
+
+
+
+### 241. *use strict* 是什么意思 ? 使用它区别是什么?
+
+> 参考答案:
+>
+> *use strict* 代表开启严格模式,这种模式使得 *Javascript* 在更严格的条件下运行,实行更严格解析和错误处理。
+>
+> 开启“严格模式”的优点:
+>
+> - 消除 *Javascript* 语法的一些不合理、不严谨之处,减少一些怪异行为;
+> - 消除代码运行的一些不安全之处,保证代码运行的安全;
+> - 提高编译器效率,增加运行速度;
+> - 为未来新版本的 *Javascript* 做好铺垫。
+
+
+
+### 242. *for...in* 和 *for...of* 的区别
+
+> 参考答案:
+>
+> *JavaScript* 原有的 *for...in* 循环,只能获得对象的键名,不能直接获取键值。*ES6* 提供 *for...of* 循环,允许遍历获得键值。
+>
+> 例如:
+>
+> ```js
+> var arr = ['a', 'b', 'c', 'd'];
+>
+> for (let a in arr) {
+> console.log(a); // 0 1 2 3
+> }
+>
+> for (let a of arr) {
+> console.log(a); // a b c d
+> }
+> ```
+
+
+
+### 243. *ajax、axios、fetch* 的区别
+
+> 参考答案:
+>
+> *ajax* 是指一种创建交互式网页应用的网页开发技术,并且可以做到无需重新加载整个网页的情况下,能够更新部分网页,也叫作局部更新。
+>
+> 使用 *ajax* 发送请求是依靠于一个对象,叫 *XmlHttpRequest* 对象,通过这个对象我们可以从服务器获取到数据,然后再渲染到我们的页面上。现在几乎所有的浏览器都有这个对象,只有 *IE7* 以下的没有,而是通过 *ActiveXObject* 这个对象来创建的。
+>
+> *Fetch* 是 *ajax* 非常好的一个替代品,基于 *Promise* 设计,使用 *Fetch* 来获取数据时,会返回给我们一个 *Pormise* 对象,但是 *Fetch* 是一个低层次的 *API*,想要很好的使用 *Fetch*,需要做一些封装处理。
+>
+> 下面是 *Fetch* 的一些缺点
+>
+> - *Fetch* 只对网络请求报错,对 *400,500* 都当做成功的请求,需要封装去处理
+> - *Fetch* 默认不会带 *cookie*,需要添加配置项。
+> - *Fetch* 不支持 *abort*,不支持超时控制,使用 *setTimeout* 及 *Promise.reject* 的实现超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费。
+> - *Fetch* 没有办法原生监测请求的进度,而 *XHR* 可以。
+>
+> *Vue2.0* 之后,*axios* 开始受到更多的欢迎了。其实 *axios* 也是对原生 *XHR* 的一种封装,不过是 *Promise* 实现版本。它可以用于浏览器和 *nodejs* 的 *HTTP* 客户端,符合最新的 *ES* 规范。
+
+
+
+### 244. 下面代码的输出是什么?( *D* )
+
+```javascript
+function sayHi() {
+ console.log(name);
+ console.log(age);
+ var name = "Lydia";
+ let age = 21;
+}
+
+sayHi();
+```
+
+- A: *Lydia* 和 *undefined*
+- B: *Lydia* 和 *ReferenceError*
+- C: *ReferenceError* 和 *21*
+- D: *undefined* 和 *ReferenceError*
+
+> **分析:**
+>
+> 在 *sayHi* 函数内部,通过 *var* 声明的变量 *name* 会发生变量提升,*var name* 会提升到函数作用域的顶部,其默认值为 *undefined*。因此输出 *name* 时得到的值为 *undefined*;
+>
+> *let* 声明的 *age* 不会发生变量提升,在输出 *age* 时该变量还未声明,因此会抛出 *ReferenceError* 的报错。
+
+
+
+### 245. 下面代码的输出是什么?( *C* )
+
+```javascript
+for (var i = 0; i < 3; i++) {
+ setTimeout(() => console.log(i), 1);
+}
+
+for (let i = 0; i < 3; i++) {
+ setTimeout(() => console.log(i), 1);
+}
+```
+
+- A: *0 1 2* 和 *0 1 2*
+- B: *0 1 2* 和 *3 3 3*
+- C: *3 3 3* 和 *0 1 2*
+
+> 分析:
+>
+> *JavaScript* 中的执行机制,*setTimeout* 为异步代码,因此在 *setTimeout* 执行时,*for* 循环已经执行完毕。
+>
+> 第一个 *for* 循环中的变量 *i* 通过 *var* 声明, 为全局变量,因此每一次的 *i++* 都会将全局变量 *i* 的值加 *1*,当第一个 *for* 执行完成后 *i* 的值为 *3*。所以再执行 *setTimeout* 时,输出 *i* 的值都为 *3*;
+>
+> 第二个 *for* 循环中的变量 *i* 通过 *let* 声明,为局部变量,因此每一次 *for* 循环时都会产生一个块级作用域,用来存储本次循环中新产生的 *i* 的值。当循环结束后,*setTimeout* 会沿着作用域链去对应的块级作用域中寻找对应的 *i* 值。
+
+
+
+### 246. 下面代码的输出是什么?( *B* )
+
+```javascript
+const shape = {
+ radius: 10,
+ diameter() {
+ return this.radius * 2;
+ },
+ perimeter: () => 2 * Math.PI * this.radius
+};
+
+shape.diameter();
+shape.perimeter();
+```
+
+- A: *20* 和 *62.83185307179586*
+- B: *20* 和 *NaN*
+- C: *20* 和 *63*
+- D: *NaN* 和 *63*
+
+> **分析:**
+>
+> *diameter* 作为对象的方法,其内部的 *this* 指向调用该方法的对象,因此 *this.raduus* 获取到的是 *shape.radius* 的值 *10*,再乘以 *2* 输出的值即为 *20*;
+>
+> *perimeter* 是一个箭头函数,其内部的 *this* 应该继承声明时所在上下文中的 *this*,在这里即继承全局的 *this*,因此 *this.radius* 值的为 *undefined*,*undefined* 与数值相乘后值为 *NaN*。
+
+
+
+### 247. 下面代码的输出是什么?( *A* )
+
+```
++true;
+!"Lydia";
+```
+
+- A: *1* 和 *false*
+- B: *false* 和 *NaN*
+- C: *false* 和 *false*
+
+> **分析:**
+>
+> 一元加号会将数据隐式转换为 *number* 类型,*true* 转换为数值为 *1*;
+>
+> 非运算符 *!* 会将数据隐式转换为 *boolean* 类型后进行取反,*"Lydia"* 转换为布尔值为 *true*,取反后为 *false*。
+
+
+
+### 248. 哪个选项是不正确的?( *A* )
+
+```javascript
+const bird = {
+ size: "small"
+};
+
+const mouse = {
+ name: "Mickey",
+ small: true
+};
+```
+
+- A: *mouse.bird.size*
+- B: *mouse[bird.size]*
+- C: *mouse[bird["size"]]*
+- D: 以上选项都对
+
+> **分析:**
+>
+> *mouse* 对象中没有 *bird* 属性,当访问一个对象不存在的属性时值为 *undefined*,因此 *mouse.bird* 的值为 *undefined*,而 *undefined* 作为原始数据类型没有 *size* 属性,因此再访问 *undefined.size* 时会报错。
+
+
+
+### 249. 下面代码的输出是什么?( *A* )
+
+```javascript
+let c = { greeting: "Hey!" };
+let d;
+
+d = c;
+c.greeting = "Hello";
+console.log(d.greeting);
+```
+
+- A: *Hello*
+- B: *undefined*
+- C: *ReferenceError*
+- D: *TypeError*
+
+> **分析:**
+>
+> 在 *JavaScript* 中,复杂类型数据在进行赋值操作时,进行的是「引用传递」,因此变量 *d* 和 *c* 指向的是同一个引用。当 *c* 通过引用去修改了数据后,*d* 再通过引用去访问数据,获取到的实际就是 *c* 修改后的数据。
+
+
+
+### 250. 下面代码的输出是什么?( *C* )
+
+```js
+let a = 3;
+let b = new Number(3);
+let c = 3;
+
+console.log(a == b);
+console.log(a === b);
+console.log(b === c);
+```
+
+- A: *true* *false* *true*
+- B: *false* *false* *true*
+- C: *true* *false* *false*
+- D: *false* *true* *true*
+
+> **分析:**
+>
+> *new Number()* 是 *JavaScript* 中一个内置的构造函数。变量 *b* 虽然看起来像一个数字,但它并不是一个真正的数字:它有一堆额外的功能,是一个对象。
+>
+> == 会触发隐式类型转换,右侧的对象类型会自动转换为 *Number* 类型,因此最终返回 *true*。
+>
+> === 不会触发隐式类型转换,因此在比较时由于数据类型不相等而返回 *false*。
+
+
+
+### 251. 下面代码的输出是什么?( *D* )
+
+```js
+class Chameleon {
+ static colorChange(newColor) {
+ this.newColor = newColor;
+ }
+
+ constructor({ newColor = "green" } = {}) {
+ this.newColor = newColor;
+ }
+}
+
+const freddie = new Chameleon({ newColor: "purple" });
+freddie.colorChange("orange");
+```
+
+- A: *orange*
+- B: *purple*
+- C: *green*
+- D: *TypeError*
+
+> **分析**:
+>
+> *colorChange* 方法是静态的。 静态方法仅在创建它们的构造函数中存在,并且不能传递给任何子级。 由于 *freddie* 是一个子级对象,函数不会传递,所以在 *freddie* 实例上不存在 *colorChange* 方法:抛出*TypeError*。
+
+
+
+### 252. 下面代码的输出是什么?( *A* )
+
+```js
+let greeting;
+greetign = {}; // Typo!
+console.log(greetign);
+```
+
+- A: *{}*
+- B: *ReferenceError: greetign is not defined*
+- C: *undefined*
+
+> **分析:**
+>
+> 控制台会输出空对象,因为我们刚刚在全局对象上创建了一个空对象!
+>
+> 当我们错误地将 *greeting* 输入为 *greetign* 时,*JS* 解释器实际上在浏览器中将其视为 *window.greetign = {}*。
+
+
+
+### 253. 当我们执行以下代码时会发生什么?( *A* )
+
+ ```js
+function bark() {
+ console.log("Woof!");
+}
+
+bark.animal = "dog";
+ ```
+
+ - A 什么都不会发生
+ - B: *SyntaxError. You cannot add properties to a function this way.*
+ - C: *undefined*
+ - D: *ReferenceError*
+
+ > **分析:**
+ >
+ > 因为函数也是对象!(原始类型之外的所有东西都是对象)
+ >
+ > 函数是一种特殊类型的对象,我们可以给函数添加属性,且此属性是可调用的。
+
+
+
+### 254. 下面代码的输出是什么?( *A* )
+
+```js
+function Person(firstName, lastName) {
+ this.firstName = firstName;
+ this.lastName = lastName;
+}
+
+const member = new Person("Lydia", "Hallie");
+Person.getFullName = () => this.firstName + this.lastName;
+
+console.log(member.getFullName());
+```
+
+- A: *TypeError*
+- B: *SyntaxError*
+- C: *Lydia Hallie*
+- D: *undefined* *undefined*
+
+> **分析:**
+>
+> *Person.getFullName* 是将方法添加到了函数身上,因此当我们通过实例对象 *member* 去调用该方法时并不能找到该方法。
+
+
+
+### 255. 下面代码的输出是什么?( *A* )
+
+```js
+function Person(firstName, lastName) {
+ this.firstName = firstName;
+ this.lastName = lastName;
+}
+
+const lydia = new Person("Lydia", "Hallie");
+const sarah = Person("Sarah", "Smith");
+
+console.log(lydia);
+console.log(sarah);
+```
+
+- A: *Person { firstName: "Lydia", lastName: "Hallie" }* 和 *undefined*
+- B: *Person { firstName: "Lydia", lastName: "Hallie" }* 和 *Person { firstName: "Sarah", lastName: "Smith" }*
+- C: *Person { firstName: "Lydia", lastName: "Hallie" }* 和 *{}*
+- D: *Person { firstName: "Lydia", lastName: "Hallie" }* 和 *ReferenceError*
+
+> **分析:**
+>
+> *lydia* 是调用构造函数后得到的实例对象,拥有 *firstName* 和 *lastName* 属性;
+>
+> *sarah* 是调用普通函数后得到的返回值,而 *Person* 作为普通函数没有返回值;
+
+
+
+### 256. 事件传播的三个阶段是什么?( *D* )
+
+- A: 目标 > 捕获 > 冒泡
+- B: 冒泡 > 目标 > 捕获
+- C: 目标 > 冒泡 > 捕获
+- D: 捕获 > 目标 > 冒泡
+
+
+
+### 257. 下面代码的输出是什么?( *C* )
+
+```js
+function sum(a, b) {
+ return a + b;
+}
+
+sum(1, "2");
+```
+
+- A: *NaN*
+- B: *TypeError*
+- C: *"12"*
+- D: *3*
+
+> **分析:**
+>
+> 任意数据类型在跟 *String* 做 + 运算时,都会隐式转换为 *String* 类型。
+>
+> 即 *a* 所对应的 *Number* 值 *1*,被隐式转换为了 *String* 值 "1",最终字符串拼接的到 "12"。
+
+
+
+### 258. 下面代码的输出是什么?( *C* )
+
+```js
+let number = 0;
+console.log(number++);
+console.log(++number);
+console.log(number);
+```
+
+- A: *1 1 2*
+- B: *1 2 2*
+- C: *0 2 2*
+- D: *0 1 2*
+
+> **分析:**
+>
+> ++ 后置时,先输出,后加 *1*;++ 前置时,先加 *1*,后输出;
+>
+> 第一次输出的值为 0,输出完成后 *number* 加 *1* 变为 *1*。
+>
+> 第二次输出,*number* 先加 *1* 变为 *2*,然后输出值 *2*。
+>
+> 第三次输出,*number* 值没有变化,还是 *2*。
+
+
+
+### 259. 下面代码的输出是什么?( *B* )
+
+```js
+function getPersonInfo(one, two, three) {
+ console.log(one);
+ console.log(two);
+ console.log(three);
+}
+
+const person = "Lydia";
+const age = 21;
+
+getPersonInfo`${person} is ${age} years old`;
+```
+
+- A: *Lydia* *21* *["", "is", "years old"]*
+- B: *["", "is", "years old"]* *Lydia* *21*
+- C: *Lydia* *["", "is", "years old"]* *21*
+
+> **分析:**
+>
+> 如果使用标记的模板字符串,则第一个参数的值始终是字符串值的数组。 其余参数获取传递到模板字符串中的表达式的值!
+
+
+
+### 260. 下面代码的输出是什么?( *C* )
+
+```js
+function checkAge(data) {
+ if (data === { age: 18 }) {
+ console.log("You are an adult!");
+ } else if (data == { age: 18 }) {
+ console.log("You are still an adult.");
+ } else {
+ console.log(`Hmm.. You don't have an age I guess`);
+ }
+}
+
+checkAge({ age: 18 });
+
+```
+
+- A: *You are an adult!*
+- B: *You are still an adult.*
+- C: *Hmm.. You don't have an age I guess*
+
+> **分析:**
+>
+> 在比较相等性时,原始类型通过它们的值进行比较,而对象通过它们的引用进行比较。
+>
+> *data* 和条件中的 *{ age: 18 }* 两个不同引用的对象,因此永远都不相等。
+
+
+
+### 261. 下面代码的输出是什么?( *C* )
+
+```js
+function getAge(...args) {
+ console.log(typeof args);
+}
+
+getAge(21);
+```
+
+- A: *"number"*
+- B: *"array"*
+- C: *"object"*
+- D: *"NaN"*
+
+> **分析:**
+>
+> *ES6* 中的不定参数(…*args*)返回的是一个数组。
+>
+> *typeof* 检查数组的类型返回的值是 *object*。
+
+
+
+### 262. 下面代码的输出是什么?( *C* )
+
+```js
+function getAge() {
+ "use strict";
+ age = 21;
+ console.log(age);
+}
+
+getAge();
+```
+
+- A: *21*
+- B: *undefined*
+- C: *ReferenceError*
+- D: *TypeError*
+
+> **分析:**
+>
+> *"use strict"* 严格模式中,使用未声明的变量会引发报错。
+
+
+
+### 263. 下面代码的输出是什么?( *A* )
+
+```js
+const sum = eval("10*10+5");
+```
+
+- A: *105*
+- B: *"105"*
+- C: *TypeError*
+- D: *"10\*10+5"*
+
+> **分析:**
+>
+> *eval* 方法会将字符串当作 *JavaScript* 代码进行解析。
+
+
+
+### 264. *cool_secret* 可以访问多长时间?( *B* )
+
+```js
+sessionStorage.setItem("cool_secret", 123);
+```
+
+- A:永远,数据不会丢失。
+- B:用户关闭选项卡时。
+- C:当用户关闭整个浏览器时,不仅是选项卡。
+- D:用户关闭计算机时。
+
+> **分析:**
+>
+> *sessionStorage* 是会话级别的本地存储,当窗口关闭,则会话结束,数据删除。
+
+
+
+### 265. 下面代码的输出是什么?( *B* )
+
+```js
+var num = 8;
+var num = 10;
+
+console.log(num);
+```
+
+- A: *8*
+- B: *10*
+- C: *SyntaxError*
+- D: *ReferenceError*
+
+> **分析:**
+>
+> *var* 声明的变量允许重复声明,但后面的值会覆盖前面的值。
+
+
+
+### 266. 下面代码的输出是什么?( *C* )
+
+```js
+const obj = { 1: "a", 2: "b", 3: "c" };
+const set = new Set([1, 2, 3, 4, 5]);
+
+obj.hasOwnProperty("1");
+obj.hasOwnProperty(1);
+set.has("1");
+set.has(1);
+```
+
+- A: *false* *true* *false* *true*
+- B: *false* *true* *true* *true*
+- C: *true* *true* *false* *true*
+- D: *true* *true* *true* *true*
+
+
+
+### 267. 下面代码的输出是什么?( *C* )
+
+```js
+const obj = { a: "one", b: "two", a: "three" };
+console.log(obj);
+```
+
+- A: *{ a: "one", b: "two" }*
+- B: *{ b: "two", a: "three" }*
+- C: *{ a: "three", b: "two" }*
+- D: *SyntaxError*
+
+> **分析:**
+>
+> 如果对象有两个具有相同名称的键,则后面的将替前面的键。它仍将处于第一个位置,但具有最后指定的值。
+
+
+
+### 268. 下面代码的输出是什么?( *C* )
+
+```js
+for (let i = 1; i < 5; i++) {
+ if (i === 3) continue;
+ console.log(i);
+}
+```
+
+- A: *1 2*
+- B: *1 2 3*
+- C: *1 2 4*
+- D: *1 3 4*
+
+> **分析:**
+>
+> 当 *i* 的值为 *3* 时,进入 *if* 语句执行 *continue*,结束本次循环,立即进行下一次循环。
+
+
+
+### 269. 下面代码的输出是什么?( *A* )
+
+```js
+String.prototype.giveLydiaPizza = () => {
+ return "Just give Lydia pizza already!";
+};
+
+const name = "Lydia";
+
+name.giveLydiaPizza();
+```
+
+- A: *"Just give Lydia pizza already!"*
+- B: *TypeError: not a function*
+- C: *SyntaxError*
+- D: *undefined*
+
+> **分析:**
+>
+> *String* 是一个内置的构造函数,我们可以为它添加属性。 我们给它的原型添加了一个方法。 原始类型的字符串自动转换为字符串对象,由字符串原型函数生成。 因此,所有字符串(字符串对象)都可以访问该方法!
+>
+> 当使用基本类型的字符串调用 *giveLydiaPizza* 时,实际上发生了下面的过程:
+>
+> - 创建一个 *String* 的包装类型实例
+> - 在实例上调用 *substring* 方法
+> - 销毁实例
+
+
+
+### 270. 下面代码的输出是什么?( *B* )
+
+```js
+const a = {};
+const b = { key: "b" };
+const c = { key: "c" };
+
+a[b] = 123;
+a[c] = 456;
+
+console.log(a[b]);
+```
+
+- A: *123*
+- B: *456*
+- C: *undefined*
+- D: *ReferenceError*
+
+> **分析:**
+>
+> 当 *b* 和 *c* 作为一个对象的键时,会自动转换为字符串,而对象自动转换为字符串化时,结果都为 *[Object object]*。因此 *a[b]* 和 *a[c]* 其实都是同一个属性 *a["Object object"]*。
+>
+> 对象同名的属性后面的值会覆盖前面的,因此最终 *a["Object object"]* 的值为 *456*。
+
+
+
+### 271. 下面代码的输出是什么?( *B* )
+
+```js
+const foo = () => console.log("First");
+const bar = () => setTimeout(() => console.log("Second"));
+const baz = () => console.log("Third");
+
+bar();
+foo();
+baz();
+```
+
+- A: *First* *Second* *Third*
+- B: *First* *Third* *Second*
+- C: *Second* *First* *Third*
+- D: *Second* *Third* *First*
+
+> **分析:**
+>
+> *bar* 函数中执行的是一段异步代码,按照 *JavaScript* 中的事件循环机制,主线程中的所有同步代码执行完成后才会执行异步代码。因此 *"Second"* 最后输出。
+
+
+
+### 272. 单击按钮时 *event.target* 是什么?( *C* )
+
+```html
+