> For the complete documentation index, see [llms.txt](https://anjia1.gitbook.io/web/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://anjia1.gitbook.io/web/js/qa/basic.md).

# 5.1 语言基础

## \~\~\~\~\~\~\~\~ 数据类型 \~\~\~\~\~\~\~

{% code lineNumbers="true" %}

```javascript
function sayHi() {
  console.log(name);
  console.log(age);
  var name = 'Lydia';
  let age = 21;
}
sayHi()
```

{% endcode %}

以上代码的输出是什么？

<details>

<summary>var, let</summary>

输出：

* undefined
* ReferenceError: Cannot access 'age' before initialization

解析：

* var 声明的变量会被预解析，`var name` 会被提升到作用域的最顶部
  * 所以在第 2 行 `console.log(name)` 时，name 是存在的，但是没有被赋值
  * 所以第 2 行是输出 `undefined`
* 而 let 会有暂时性死区
  * 所以在 let 声明变量之前，都无法使用该变量

</details>

```javascript
var name = 'World!';
(function () {
    if (typeof name === 'undefined') {
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
} })();
```

以上代码的输出是什么？

<details>

<summary>var</summary>

输出：'Goodbye Jack'

解析：用 var 定义的变量“都”会被提升至所有代码的最前面。所以以上代码，实际上类似：

```javascript
var name = 'World!';
(function () {
    var name;
    if (typeof name === 'undefined') {
        // var name = 'Jack';
        name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
} })();
```

TODO：where 会提升到 where

</details>

```javascript
(function(){
  var x = y = 1;
})();
console.log(y);
console.log(x);
```

以上代码的输出是什么？

<details>

<summary>var</summary>

输出：

* 1
* `ReferenceError: x is not defined`

解析：在作用域内，`var` 变量定义和函数定义会被先行提升。

所以，上面的代码，实际上是：

```javascript
var y; // 而 y 则隐式成了个全局变量
(function(){
  var x;  // 只“变量提升”第一个变量
  // var x = y = 1;
  y = 1;
  x = 1;
})();
console.log(y);
console.log(x);
```

考察点：`var` 变量提升、隐式定义全局变量

</details>

```javascript
var a = 11;
function test2(){
    this.a = 22;
    let b = ()=>{
        console.log(this.a)
    }
    b();
}
var x = new test2();
```

以上代码的输出是什么？

<details>

<summary>var</summary>

输出：22

定义时绑定

</details>

```javascript
for(var i=0;i<5;i++) { 
    setTimeout(function(){ 
        console.log(i);
    },1000);
}
console.log(i)
```

以上代码的输出是什么？

<details>

<summary>var</summary>

输出：5 个 5

每次 for 循环的时候 setTimeout 都会执行，但是里面的 function 不会被立即执行，而是会放进任务队列。

</details>

<details>

<summary>写一个函数，第 n 秒打印 n</summary>

```javascript
// 1. var + 闭包
for(var i=0;i<5;i++){ 
    (function(i){ 
        setTimeout(function(){
            console.log(i)
        }, 1000*i) 
    })(i)
}

// 2. let
for(let i=0;i<5;i++){ 
    setTimeout(function(){
        console.log(i)
    },1000*i)
}
```

</details>

<details>

<summary>暂时性死区</summary>

块级作用域

在代码块内，使用 let, const 命令声明变量之前，该变量都是不可用的。这在语法上，称为“暂时性死区”。

</details>

<details>

<summary>symbol</summary>

Symbol 是 ES6 的新增属性，代表用给定名称作为唯一标识，这种类型的值可以这样创建，`let id=symbol('id')`

symbl 确保唯一，即使采用相同的名称，也会产生不同的值，我们创建一个字段，仅为知道对应 symbol 的人能访问。

symbol 并不是 100% 隐藏：

* 有内置方法 `Object.getOwnPropertySymbols(obj)` 可以获得所有的 symbol。
* 也有一个方法 `Reflect.ownKeys(obj)` 返回对象所有的键，包括 symbol

但大多数内置方法和语法结构，都遵循通用约定——它们是隐藏的。

</details>

```javascript
var a = 111111111111111110000
var b = 1111
console.log(a + b)
```

以上代码的输出是什么？

<details>

<summary>Number 精度</summary>

输出：`111111111111111110000`（和 a 的值相等）

解析：

![](/files/5CkkRpRIMY2OARxD9HWf)

对于整数部分，有效数字最多能存 16 位。

```javascript
// 整数部分，a 的有效数字有 16 位
'111111111111111110000'.length  // 21
a.toExponential()               // 1.1111111111111111e+20

// 而当 a 参加+运算时，它是不安全的
Number.MAX_SAFE_INTEGER         // 9007199254740991
a >= Number.MAX_SAFE_INTEGER    // true
'9007199254740991'.length       // 16

// a + b, 最后 4 位精度刚好被扔了，所以结果值还是 a
1.11111111111111110000e+20
0.00000000000000001111e+20
```

关于浮点数精度的详情可查阅：<https://anjia1.gitbook.io/cs/floating-point/reading>

</details>

```javascript
[,,,].join(",")
```

以上代码的运行结果是什么？

<details>

<summary>对象字面量</summary>

结果：",,"

解析：JavaScript 在使用字面量创建数组时，如果末尾有个 `,` 会被省略。

所以 `[,,,]` 实际上只有三个元素 `[,,]`，值都为空即 `undefined`。而当三个元素使用 `join(,)` 时，会有两个分隔符 `,`。

```javascript
[,,,] // [empty × 3]
```

</details>

```javascript
function sidEffecting(ary) {
  ary[0] = ary[2]
}
function bar(a,b,c) {
  c = 10
  sidEffecting(arguments)
  return a + b + c
}
console.log(bar(1,1,1))
```

以上代码的输出是什么？

<details>

<summary>函数参数 arguments</summary>

输出：21

解析：函数内部的 arguments 是个引用，且和变量之间存在着值联动。

* arguments 是个类数组，有 length 属性的 Object
* arguments 对象的内部属性是用形参创建的 getter 和 setter 方法，它们任意一个的改变都会影响到另一方

</details>

<details>

<summary>arguments</summary>

arguments 是类数组对象。

它有 length 属性，但不能调用数组方法，可用 Array.from() 转换

</details>

<details>

<summary>箭头函数获取 arguments</summary>

可用 `...rest` 参数获取

</details>

<details>

<summary>判断变量类型的方法</summary>

1. 运算符 `typeof`
2. 运算符 `instanceof`
3. 属性 `constructor`
4. 方法 `toString()`

```javascript
typeof a

a instanceof A
a.constructor == A

Object.prototype.toString.call(a)
```

</details>

<details>

<summary>类型转换规则</summary>

`x == y` 的结果是 true 或者 false

1. 若 Type(x) 和 Type(y) 相同，则
   1. Type 是 Undefined，返回 true
   2. Type 是 Null，返回 true
   3. Type 是 Boolean，同为 true/false 返回 true，否则返回 false
   4. Type 是 String，当是完全相同的字符序列时返回 true，否则返回 false
   5. Type 是 Number，则
      * 若有一个为 NaN，则返回 false
      * 若是 +0 和 -0，则返回 true
      * 若是相等数值，返回 true
      * 否则返回 false
   6. Type 是对象，若引用的是同一个对象则返回 true，否则返回 false
2. 一个是 null，一个是 undefined
   * 若 x 是 null，y 是 undefined，则返回 true
   * 若 x 是 undefined，y 是 null，则返回 true
3. 一个是 String，一个是 Number
   * 若 Type(x) 是 Number，Type(y) 是 String，则返回 x == ToNumber(y) 的结果
   * 若 Type(x) 是 String，Type(y) 是 Number，则返回 ToNumber(x) == y 的结果
4. 其中一个是 Boolean
   * 若 Type(x) 是 Boolean，返回 ToNumber(x) == y 的结果
   * 若 Type(y) 是 Boolean，返回 x == ToNumber(y) 的结果
5. 一个是 String 或 Number，一个是 Object
   * 若 Type(x) 是 String 或 Number，Type(y) 是 Object，返回 x == ToPrimitive(y)
   * 若 Type(x) 是 Object，Type(y) 是 String 或 Number，返回 ToPrimitive(x) == y
6. 返回 false

#### 相关内容

* ToPrimitive()
  * 若是转成 string，则先调 toString()
  * 若是转成 number，则先调 valueOf()
* ToNumber()

</details>

<details>

<summary>数据类型</summary>

在 JavaScript 中，基本数据类型 = 原始数据类型（值类型） + 对象（引用类型）

1. 值类型：按值访问，可操作保存在变量中的实际值
2. 引用类型
   * Object, Array, RegExp, Date, Function
   * 值类型的包装器：String, Number, Boolean
   * 内置对象：Global, Math

</details>

## \~\~\~\~\~\~\~\~ 类型转换 \~\~\~\~\~\~\~

<details>

<summary>其它类型转换为 Number 的规则</summary>

1. 原始类型
   1. Undefined, `NaN`
   2. Null, `0`
   3. Boolean, `true` 1, `false` 0
   4. String, '' 0, 非空非数值 `NaN`, 非空数值 `Number`
   5. Number
   6. BigInt
   7. Symbol, 不能转, 会报错
2. Object
   * 先转换成相应的原始类型值
     * `ToPrimitive` 会先调用 `valueOf()` 再调用 `toString()`
   * 再按原始类型的规则转

```javascript
// 原始类型
Number(undefined) // NaN
Number(null)      // 0 

Number(true)      // 1
Number(false)     // 0

Number('')        // 0
Number('123')     // 123
Number('hello')   // NaN

Number(Symbol('1'))  // TypeError: Cannot convert a Symbol value to a number

// object
Number([])           // 0
Number({})           // NaN

Number([1])          // 1
Number(['1'])        // 1

Number([1,2,3])      // NaN
Number({name: 'hi'}) // NaN
Number(()=>{})       // NaN
```

</details>

{% code lineNumbers="true" %}

```javascript
var a = [0];
if ([0]) {
  console.log(a == true);
} else {
  console.log("hi");
}
```

{% endcode %}

以上代码的输出是什么？

<details>

<summary>boolean 和 <code>==</code></summary>

输出：false

解析：

* 第 2 行，转 boolean 类型：数组 `[0]` 不为空，所以值是 true
* 第 3 行，运算符 `==`：如果一个操作数是 boolean，则另一个操作值在比较之前先转换为 Number，而 `Number([0])` 的值是 0，于是最终变成了 `0 == true`

</details>

{% code lineNumbers="true" %}

```javascript
console.log(+true);
console.log(!'hi');
```

{% endcode %}

以上代码的输出是什么？

<details>

<summary><code>+</code> 和 <code>!</code> </summary>

输出：1 false

解析：类型转换

* 第 1 行，把 true 转成 Number
* 第 2 行，把 String 转成 Boolean

</details>

```javascript
var a = [1, 2, 3]
var b = [1, 2, 3]
var c = [1, 2, 4]

console.log(a == b)
console.log(a === b)
console.log(a > c)
console.log(a < c)
```

以上代码的输出是什么？

<details>

<summary>数组比大小</summary>

输出：false, false, false, true

解析：在 JavaScript 中

* Array 本质上是 Object（引用类型）
  * 引用类型比相等，比的是地址，所以，`==` 和 `===` 都不相等
* 而 Array 的 `>` 和 `<` 运算符，方式却类似于字符串，比较字典顺序
  * 所以，`a < c` 是 true

</details>

## \~\~\~\~\~\~\~\~\~ 运算符 \~\~\~\~\~\~\~

<details>

<summary><code>Object.is()</code> 和 <code>==</code>, <code>===</code> 的区别</summary>

* `==` 会进行隐式类型转换
* `===` 不进行类型转换，是严格相等&#x20;
* `Object.is()` 是在 `===` 的基础上特别处理了 NaN, -0, +0

```javascript
Object.is(+0, -0)    // false
Object.is(NaN, NaN)  // true
```

最重要的是，`Object.is()` 是有其特殊用途的，而不应该理解为是更严格的相等。

</details>

```javascript
var val = 'hi'
console.log('Value is ' + (val === 'hi') ? 'Something' : 'Nothing')
```

以上代码的输出是什么？

<details>

<summary><code>+</code> 和 <code>?</code> 的优先级</summary>

输出："Something"

解析：`+` 的优先级要高于 `?`

</details>

```javascript
 const person = {
    firstName: 'Lydia',
    lastname: 'Hallie',
    pet: {
        name: 'Mara',
        breed: 'Dutch Tulip Hound'
    },
    getFullName() {
        return `${this.firstName}/${this.lastName}`
    }
}
console.log(person.pet?.name)
console.log(person.pet?.family?.name)
console.log(person.getFullName?.())
console.log(person.getLastName?.())
```

以上代码的输出是什么？

<details>

<summary><code>?.</code></summary>

输出：Mara undefined Lydia/Hallie undefined

解析：`?.` 可选操作链，ES2020 新特性。 当 ? 前面的属性不是 undefined 或 null 时才会继续执行后续代码，否则直接返回左侧结果。

</details>

## \~\~\~\~\~\~\~\~\~ 语句 \~\~\~\~\~\~\~\~

```javascript
function foo(){
    alert(1)
}()
```

<details>

<summary>以上代码是 IIFE 吗？为什么？</summary>

不是 IIFE。而且代码运行时会报错：`Uncaught SyntaxError: Unexpected token ')'`

解析：以 function 关键字开头的语句，会被解析为函数声明，而函数声明是不允许直接运行的。只有当解析器把这句话解析为函数表达式时，才能直接运行。比如改成以运算符开头：

```javascript
!function foo(){
    alert(1)
}();  // true

!function foo(){
    alert(2)
    return 'hi'
}();  // false

(function foo(){
    alert(3)
})(); // undefined, 最日常的写法

(function foo(){
    alert(4)
}()); // undefined, 全部包裹也可以
```

任何消除函数声明和函数表达式之间歧义的方法，都可以被解析器正确识别。

```javascript
var i = function(){return 10}();   // undefined
1 && function(){return true}();    // true
1, function(){alert(1)}();         // undefined
```

如果不在乎返回值，添加一元运算符都是有效的：

```javascript
!function(){alert(1)}()        // true
+function(){alert(1)}()        // NaN
-function(){alert(1)}()        // NaN
~function(){alert(1)}()        // -1
...

void function(){alert(1)}()    // undefined
new function(){alert(1)}()     // Object {}
delete function(){alert(1)}()  // true
```

基础概念：语句、表达式、表达式语句

详情可查阅：<https://swordair.com/function-and-exclamation-mark/>

</details>

```javascript
let obj = {
  name: "Lydia",
  age: 21
}
for (let item in obj) {
  console.log(item)
}
```

以上代码的输出是什么？

<details>

<summary><code>for-in</code></summary>

输出：name, age

解析：`for-in` 遍历的是对象自身的属性和方法，`let key in obj`

</details>

```javascript
const myLife = ['吃', '喝', '玩', '乐'];
for (let item in myLife) {
  console.log(item);
}
for (let item of myLife) {
  console.log(item);
}
```

以上代码的输出是什么？

<details>

<summary><code>for-in</code> 和 <code>for-of</code></summary>

输出：

* 0 1 2 3
* 吃 喝 玩 乐

解析：

* `for-in` ≈ `let key in obj`
* `for-of` ≈ `let item in obj`

</details>

## \~\~\~\~\~\~\~\~\~ 方法 \~\~\~\~\~\~\~\~

```javascript
const arr = [1, 2, [3, 4, [5]]];
console.log(arr.flat(1));
```

以上代码的输出是什么？

<details>

<summary><code>Array.flat()</code></summary>

输出：`[1, 2, 3, 4, [5]]`

扁平化，会创建一个新的、被扁平化的数组。扁平化的深度，取决于参数。

`Array.prototype.flat()` 会创建一个 new array，用所有的 sub-array elements 递归地 concat 到一起。depth 由参数指定，默认是 1。

```javascript
const arr1 = [0, 1, 2, [3, 4]];
arr1.flat(1); // [0, 1, 2, 3, 4]

const arr2 = [0, 1, 2, [[[3, 4]]]];
arr2.flat(1); // [0, 1, 2, [[3, 4]]]
arr2.flat(2); // [0, 1, 2, [3, 4]]
arr2.flat(3); // [0, 1, 2, 3, 4]
```

</details>

```javascript
var ary = Array(3);
ary[0] = 2
ary.map(function(elem) { return '1'; });
```

以上代码的运行结果是什么？

<details>

<summary><code>Array.map()</code></summary>

结果：`['1', empty × 2]`

解析：`Array.prototype.map()` 只有在数组中被初始化过的元素，才会被触发

</details>

```javascript
[1, 2, 3, 4].reduce((x, y) => console.log(`x=${x} y=${y}`))
```

以上代码的输出是什么？

<details>

<summary><code>Array.reduce()</code></summary>

输出：

* x=1 y=2
* x=undefined y=3
* x=undefined y=4

解析：最容易理解 `reduce()` 情况，是求返回数组中所有元素的总和。如下：

```javascript
// 简写
[1, 2, 3, 4].reduce((x, y) => x + y)

// 增加 log
[1, 2, 3, 4].reduce((x, y) => {
    console.log(x, y)
    return x + y
})
```

而当回调函数中没有返回值时，则第一个参数为 undefined

</details>

```javascript
const name = 'Lydia Hallie';
const age = 21;

console.log(Number.isNaN(name));
console.log(Number.isNaN(age));

console.log(isNaN(name));
console.log(isNaN(age));
```

以上代码的输出是什么？

<details>

<summary><code>isNaN()</code> 和 <code>Number.isNaN()</code></summary>

输出：false, false, true, false

解析：

* `isNaN()` 会先将传入的参数转换为 Number 类型
* `Number.isNaN()` 是先判断值的类型是不是 Number，若不是则直接返回 false
  * 即只有当值是 `NaN` 时才会返回 true

</details>

<details>

<summary>常用的 string 方法</summary>

</details>

<details>

<summary>去除字符串首尾空格</summary>

使用正则 `(^\s*)|(\s*$)`

</details>

<details>

<summary>常用的 Array 方法</summary>

</details>

<details>

<summary>实现深度拷贝</summary>

```javascript
var deepCopy = function(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}
```

</details>

<details>

<summary>数组去重</summary>

1. ES6 Set 去重 `Array.from(new Set(array))`
2. indexOf 循环去重
3. Object 键值对去重：把数组的值存成 Object 的 key 值，比如 Object\[value1] = true

</details>

<details>

<summary>获得对象上的属性</summary>

从 ES5 开始，有三种方法可以列出对象的属性

1. `for(let i in obj)` 访问对象及其原型链中所有可枚举的类型
2. `object.keys` 返回一个数组，包括所有可枚举的属性名称
3. `object.getOwnPropertyNames` 返回一个数组包含不可枚举的属性

</details>

## \~\~\~\~\~\~\~\~ 原型和类 \~\~\~\~\~\~\~\~

* 原型链的顶端是什么？
* Object 的原型是什么？
* Object 的原型的原型是什么？
* 在数组的原型链上，实现删除数组重复数据的方法

<details>

<summary>原型链</summary>

</details>

<details>

<summary>new 操作符做了哪些事情</summary>

new 操作符新建了一个空对象，这个对象原型指向构造函数的 prototype，执行构造函数后返回这个对象。

#### new 操作符原理

1. 创建一个类的实例：创建一个空对象 obj，然后把这个空对象的 **proto** 设置为构造函数的 prototype
2. 初始化实例：构造函数被传入参数并调用，关键字 this 被设定指向该实例 obj
3. 返回实例 obj

</details>

```javascript
Function.prototype.a = 1;
Object.prototype.b = 2; 
function A() {}

var a = new A();
console.log(a.a, a.b); 
console.log(A.a, A.b); 
```

以上代码的输出是什么？

<details>

<summary>Function 和 Object</summary>

* undefined, 2
* 1, 2

</details>

<details>

<summary><code>Function._proto_(getPrototypeOf)</code> 是什么？</summary>

获取一个对象的原型：

* 在 chrome 中，可以用 `_proto_`&#x20;
* 在 ES6 中，可以用 `Object.getPrototypeOf`

Function.proto 是指 Function 继承自什么对象。

```javascript
Function.__proto__ === Object.prototype   // false
Function.__proto__ === Function.prototype // true
```

Function 的原型也是 Function

![](/files/ArowimKKoARBr75670uL)

</details>

1. `Hero("37er")` 执行结果为 `Hi! This is 37er`
2. `Hero("37er").kill(1).recover(30)` 执行结果为 `Hi! This is 37er Kill 1 bug Recover 30 bloods`
3. `Hero("37er").sleep(10).kill(2)` 执行结果为 `Hi! This is 37er` 等待 10s 后 `Kill 2 bugs`

<details>

<summary>编写代码，满足以上条件</summary>

```javascript
function Hero(name) {
    let o = new Object()
    o.name = name
    console.log(`Hi! This is ${o.name}`)

    o.kill = (num) => {
        console.log(`Kill ${num} bug` + (num > 1 ? 's' : ''))
        return o;
    };

    o.recover = function (num) {
        console.log(`Recover ${num} blood` + (num > 1 ? 's' : ''))
        return o;
    }
    o.sleep = function (s) {
        // TODO. 不能直接连调
        // 只能 Hero("37er").sleep(2).then(o => o.kill(2))
        return new Promise((resolve) => {
            setTimeout(() => {
                console.log('sleep...')
                resolve(o)
            }, s * 1000)
        })
    }
    return o;
}
```

</details>

<details>

<summary>类的创建和继承</summary>

1. 基于原型链的 `new function` + prototype
2. ...

P59

</details>

<details>

<summary>继承的实现方式</summary>

1. 原型链继承，将父类的实例作为子类的原型，他的特点是实例是子类的实例也是父 类的实例，父类新增的原型方法/属性，子类都能够访问，并且原型链继承简单易于实 现，缺点是来自原型对象的所有属性被所有实例共享，无法实现多继承，无法向父类构 造函数传参。
2. 构造继承，使用父类的构造函数来增强子类实例，即复制父类的实例属性给子类， 构造继承可以向父类传递参数，可以实现多继承，通过 call 多个父类对象。但是构造继 承只能继承父类的实例属性和方法，不能继承原型属性和方法，无法实现函数服用，每 个子类都有父类实例函数的副本，影响性能
3. 实例继承，为父类实例添加新特性，作为子类实例返回，实例继承的特点是不限制调用方法，不管是 new 子类还是子类返回的对象具有相同的效果，缺点是实例是父类的实例，不是子类的实例，不支持多继承
4. 拷贝继承
   * 特点：支持多继承
   * 缺点：效率较低，内存占用高（因为要拷贝父类的属性），无法获取父类不可枚举的方法（不可枚举方法，不能使用 for in 访问到）
5. 组合继承：通过调用父类构造，继承父类的属性并保留传参的优点，然后通过将父类实例作为子类原型，实现函数复用
6. 寄生组合继承：通过寄生方式，砍掉父类的实例属性，这样，在调用两次父类的构造的时候，就不会初始化两次实例方法/属性，避免的组合继承的缺点

更多 <https://www.cnblogs.com/humin/p/4556820.html>

</details>

<details>

<summary>两个构造函数，实现 A 继承 B</summary>

P108

</details>

## \~\~\~\~\~\~\~\~\~ 运行时 \~\~\~\~\~\~\~\~

<details>

<summary>作用域类型</summary>

</details>

```javascript
let obj1 = {
    name: 'obj1_name',
    print: function(){
        return () => console.log(this.name)
    }
}
let obj2 = { name: 'obj2_name' }

obj1.print()();
obj1.print().call(obj2);
obj1.print.call(obj2)();
```

以上代码的输出是什么？

<details>

<summary>this 和箭头函数</summary>

输出：obj1\_name obj1\_name obj2\_name

解析：

* 普通函数，this 指向看执行时的声明
* 箭头函数，this 值继承父级函数的
  * 箭头函数不绑定自己的 this

</details>

<details>

<summary>bind，apply，call 的区别</summary>

改变函数内部 this 指针的指向函数。

通过 apply 和 call，改变函数的 this 指向：

* 它们两个函数的第一个参数都是一样的，表示要改变指向的那个对象
* 第二个参数，apply 是数组，而 call 则是 arg1,arg2...这种形式。

```javascript
// 在另一个对象 A 上，调用 B 对象的方法
B.apply(A, arguments)
B.call(A, args1, args2)
```

通过 bind 改变 this 作用域会返回一个新的函数，这个函数不会马上执行。

bind 除了返回的是函数之外，其参数和 call 一样。

</details>

<details>

<summary>this 的指向有哪几种？</summary>

1. 默认绑定：全局环境中，this 默认绑定到 window（非严格模式时）
2. 隐式绑定：一般地，被直接对象所包含的函数调用时，也称为方法调用，this 隐式绑定到该直接对象
3. 隐式丢失：隐式丢失是指被隐式绑定的函数丢失绑定对象，从而默认绑定到 window
4. 显式绑定：通过 call(), apply(), bind() 方法把对象绑定到 this 上
5. new 绑定：如果函数或者方法调用之前带有关键字 new，它就构成构造函数调用。对于 this 绑定来说，称为 new 绑定
   * 构造函数通常不使用 return 关键字，它们通常初始化新对象，当构造函数的函数体执行完毕时，它会显式返回。在这种情况下，构造函数调用表达式的计算结果就是这个新对象的值
   * 如果构造函数使用 return 语句但没有指定返回值，或者返回一个原始值，那么这时将忽略返回值，同时使用这个新对象作为调用结果
   * 如果构造函数显式地使用 return 语句返回一个对象，那么调用表达式的值就是这个对象

</details>

<details>

<summary>闭包</summary>

闭包就是能够读取其它函数内部变量的函数，或者子函数在外调用， 子函数所在的父函数的作用域不会被释放。

TODO.&#x20;

* 闭包本身的含义？&#x20;
* 在 JS 里，闭包的形式通常是xxx，因为它符合闭包的定义。
* 在其它语言里，闭包是否有不同的形式？
* 闭包的使用场景

</details>

```javascript
setTimeout(function(){
    console.log(1)
}, 0);

new Promise(function(resolve,reject){
    console.log(2);
    resolve();
}).then(function(){
    console.log(3)
}).then(function(){
    console.log(4)
});

// Node.js
process.nextTick(function(){
    console.log(5)
});

console.log(6);
```

以上代码的输出是什么？

<details>

<summary>Event Loop 中的 Job queue</summary>

输出：`2,6,5,3,4,1`

详见：<https://github.com/forthealllight/blog/issues/5>

</details>

<details>

<summary>setTimeout() 的时间含义</summary>

`setTimeout()` 函数只是将事件插入了任务列表，必须等当前代码都执行完，主线程才会去执行它指定的回调函数，有可能要等很久，所以没办法保证回调函数一定会在 setTimeout 指定的时间内执行。

设置的那个时间 ≈ 插入队列的时间 + 等待的时间

</details>

<details>

<summary>如何实现 sleep()</summary>

1. promise
2. async
3. generate
4. ~~while~~

```javascript
// 1. promise
function sleep(ms){
    var temple = new Promise((resolve)=>{
            console.log(111);
            setTimeout(resolve, ms) // 起码是异步的
        });
    return temple
}
sleep(500).then(function(){
    console.log(222)
})
// 先输出了 111，延迟 500ms 后输出 222


// 2. async
function sleep(ms){
    return new Promise((resolve)=>
                setTimeout(resolve,ms)
            );
}
async function test(){
    var temple = await sleep(1000);
    console.log(1111)
    return temple
}
test();
// 延迟 1000ms 输出了 1111


// 3. generate
function* sleep(ms){
    yield new Promise(function(resolve,reject){ 
                console.log(111);
                setTimeout(resolve,ms);
            })
}
sleep(500).next().value.then(function(){console.log(2222)})
```

```javascript
// 4. while
function sleep(ms){
    var start=Date.now(), expire=start+ms;
    while(Date.now()<expire);
        console.log('1111');
    return;
}
// sleep(1000) 休眠了 1000ms 之后输出了 1111
// 缺点：容易造成死循环（一直占着...主进程...也太不行了吧...）
// 那放在...web worker 里？有点杀鸡用牛刀...
```

</details>

<details>

<summary>如何解决异步回调地狱？</summary>

* promise
* async/await
* generator

promise, generator, async 的使用，P95

</details>

<details>

<summary>promise</summary>

promise 及其底层如何实现, P89

</details>

<details>

<summary>async 和 await</summary>

</details>

<details>

<summary>generator</summary>

</details>

<details>

<summary>让三个 promise 串行执行</summary>

P108

</details>

<details>

<summary>实现一个简单的 promise</summary>

Promises/A+ <https://promisesaplus.com/>

实现一个完美符合Promise/A+规范的Promise <https://github.com/forthealllight/blog/issues/4>

</details>

<details>

<summary>垃圾回收机制</summary>

必要性：

* 由于字符串、对象和数组没有固定大小，所以只有当它们的大小已知时，才能进行动态的存储分配。
* JavaScript 程序每次创建字符串、数组或对象时，解释器都必须分配内存来存储那个实体。

不像 C/C++，JS 自己有一套垃圾回收机制(Garbage Collection)。JavaScript 的解释器可以检测到什么时候不再使用一个对象了，一旦确定了一个对象是无用的，它就可以把它所占用的内存释放掉了。

```javascript
var a = "hello world";
var b = "world";
var a = b;
//这时，会释放掉"hello world"，释放内存以便再引用垃圾回收的方法：标记清除、计数引用。
```

#### 1. 标记清除

这是最常见的垃圾回收方式。

* 当变量进入环境时，就标记这个变量为“进入环境”。从逻辑上讲，永远不能释放进入环境的变量所占的内存，只要执行流程进入相应的环境就可能用到它们。
* 当离开环境时，就标记为离开环境。
* 垃圾回收器在运行的时候，会给存储在内存中的变量都加上标记（所有都加），然后去掉环境变量中的变量，以及被环境变量中的变量所引用的变量（条件性去除标记），删除所有被标记的变量，删除的变量无法在环境变量中被访问所以会被删除，最后垃圾回收器完成了内存的清除工作，并回收它们所占用的内存。

#### 2. 引用记数法

记录每个值被引用的次数。

* 当声明了一个变量，并用一个引用类型的值赋值给改变量，则这个值的引用次数为 1
* 相反的，如果包含了对这个值引用的变量又取得了另外一个值，则原先的引用值引用次 数就减 1，
* 当这个值的引用次数为 0 的时候，说明没有办法再访问这个值了，因此就把 所占的内存给回收进来，这样垃圾收集器再次运行的时候，就会释放引用次数为 0 的这些值。

用引用计数法会存在内存泄露。比如：

```javascript
function problem() {
    var objA = new Object();
    var objB = new Object();
    objA.someOtherObject = objB;
    objB.anotherObject = objA;
}
// objA 和 objB 通过各自的属性相互引用，此时，两个对象的引用次数都为 2
// 在采用引用计数的策略中，由于函数执行之后，这两个对象都离开了作用域，
// 函数执行完成之后，因为计数不为 0，这样的相互引用如果大量存在就会导致内存泄露
```

```javascript
// 在 DOM 对象中，也容易存在这种问题
var element = document.getElementById(’‘);
var myObj = new Object();
myObj.element=element;
element.someObject=myObj;
// 这样就不会有垃圾回收的过程
```

</details>

## \~\~\~\~\~\~\~\~\~ 其它 \~\~\~\~\~\~\~\~\~

<details>

<summary>前端模块化（CommonJS，AMD，ES Modules）</summary>

将复杂的文件拆成一个个独立的模块（比如单个 JS 文件），这样有利于重用（复用性）和维护（版本迭代）。一个模块是能实现特定功能的文件，有了模块就可以方便的使用别人的代码，想要什么功能就能加载什么模块。

与此同时，也会引来模块之间相互依赖的问题，所以有了 commonJS 规范，AMD，CMD 规范等等，以及用于 JS 打包(编译等处理)的 工具 webpack。

1. CommonJS 是一种模块规范，最初被用在 Node.js，成为 Node.js 的模块规范
2. 而浏览器端，在 ES6 之前，类似的模块规范有 AMD
3. ES6 Module 是在语言标准的层面上实现了模块功能。有望成为浏览器和服务器通用的模块解决方案

#### CommonJS <mark style="background-color:blue;">vs</mark> AMD

CommonJS 开始于服务器端的模块化，同步定义的模块化，每个模块都是一个单独的作用域。

* 模块输出 `modules.exports`
* 模块加载 `require()` 引入模块

AMD，异步模块定义。

requireJS 实现了 AMD 规范，主要用于解决下述两个问题：

1. 多个文件有依赖关系，被依赖的文件需要早于依赖它的文件加载到浏览器
2. 加载的时候浏览器会停止页面渲染，加载文件越多，页面失去响应的时间越长

```javascript
// requireJS 定义了一个函数 define，它是全局变量
// 定义模块 define()
define(['dependency'], function(){ 
        var name = 'Byron';
        function printName(){ console.log(name);
    }
    return {
        printName: printName
    };
});

// 加载模块 require()，异步加载，这样不会阻塞浏览器
require(['myModule'], function (my){
    my.printName();
}
// 当前面的模块加载成功，回调才会被执行。
```

#### CommonJS <mark style="background-color:blue;">vs</mark> ES Module

使用上的差别，主要有：

1. 模块输出：值的拷贝 <mark style="background-color:blue;">vs</mark> 值的引用
2. 时机：运行时加载 <mark style="background-color:blue;">vs</mark> 编译时输出接口
3. 形式：单个值导出 <mark style="background-color:blue;">vs</mark> 可导出多个
4. 动态语法可以写在判断里 <mark style="background-color:blue;">vs</mark> 静态语法只能写在顶层
5. this 值：当前模块 <mark style="background-color:blue;">vs</mark> `undefined`（默认是严格模式）

</details>

<details>

<summary>"use strict";</summary>

ES5 引入了严格模式，旨在让代码更合理、更安全。

它对 JavaScript 的使用添加了一些限制，比如：

* 禁止使用 with 语句
* 禁止 this 指向全局对象 (???)
* 对象的属性名不能重复

此外，针对严格模式，编译器也会有特定的优化，所以代码的运行速度也会快一些。

严格模式，还可以很好地向后兼容，为未来的 JavaScript 做好铺垫。

</details>

<details>

<summary>eval</summary>

功能：将对应的字符串解析成 JS 并执行

应该避免使用，因为非常消耗性能(2 次，一次解析成 JS，一次执行)

</details>

<details>

<summary>ES6，ES7 的新语法</summary>

1. 变量的声明和定义
   * 增加了 let、const 声明变量
   * 它们是块级作用域的（ES5 只有全局作用域和函数作用域）
   * 块级作用域的好处是不再需要立即执行的函数表达式，循环体中的闭包不再有问题
2. 箭头函数
3. 异步的 promise, generator, async/await
4. 模块化，更方便地进行模块化编程
5. class，可以更好的面向对象编程

其它：

1. 数据类型
   * 新的原始类型 symbol
   * 新的数据结构 set 和 map
2. 对已有类型的对象包装器，新增了一些方法。比如
   * 字符串：模板字符串
   * 函数：默认参数
   * 对象属性：?.
3. 运算符
   * 结构赋值
   * rest

</details>

* JS 的语言特性
* C++, Java, JavaScript 这三种语言的区别
