### 变量类型和计算
#### 值类型、引用类型
```javascript
// 值类型
let a = 100
let b = a
a = 200
console.log(b) // 100
// 引用类型
let c = { age: 18 }
let d = c
d.age = 20
console.log(c) // {age:20}
```
> 值类型存储于 **栈**
>
> 引用类型存储于 **堆**
>
> 应用类型存储的是内存地址,d = c,表示d和c指向同一块内存
```javascript
// 常见引用类型
const obj = { x:100 }
const arr = ['a','b','c']
const n = null // 特殊引用类型,指针指向为空指针
// 特殊引用类型,但不用于存储数据,所以没有拷贝,复制函数
function fun() {}
```
#### typeof
* 识别所有值类型
* 识别函数
* 判断是否是引用类型(不可再细分、**不能区分数组、对象、null**)
```javascript
// 区分数组 和 对象
[] instanceof Array --> true
```
![image-20210302104739491](https://image.mdashen.com/pic/image-20210302104739491.png)
#### 深拷贝
```javascript
/**
* 深拷贝
* @param {Object} obj 要拷贝的对象
*/
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
// obj 是 null ,或者不是对象和数组,直接返回
return obj
}
// 初始化返回结果
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 保证 key 不是原型的属性
if (obj.hasOwnProperty(key)) {
// 递归调用,解决多级对象\数组
result[key] = deepClone(obj[key])
}
}
// 返回结果
return result
}
```
#### 变量计算-类型转换
##### 字符串拼接
```javascript
100 + 10 // 110 number
100 + '10' // '10010' string
true + '1' // 'true1' 字符串
true + 1 // 2 number
```
##### == 运算符
```javascript
100 == '100' // true
0 == '' // true
0 == false // true
false == '' // true
null == undefined // true
null == false // false
```
```javascript
// 日常使用中 除了 == null 之外,其他都一律使用 ===
const obj = { x: 100 }
if (obj.a == null) {
// 相当于 if (obj.a === null || obj.a === undefined)
}
```
##### truly变量、falsely变量
* truly 变量:!!a === true 的变量
* false 变量:!!a === false 的变量
```javascript
// 举例、1 两次去反、1 是 truly 变量
!1 = false
!!1 = !false => ture
```
```javascript
// 以下是 falsely 变量。除此之外都是 truly 变量
!!0 === false
!!NaN === false
!!'' === false
!!null === false
!!undefined === false
!!false === false
```
* if 语句判断就是 truly 变量;如 `if('')1;else 0; // 0`
### 原型和原型链
#### 类型判断--instanceof
```javascript
// Object 是所有类的父类,基类
class People {
// ……
}
class Student extends People {
// ……
}
const xialuo = new Student()
console.log(xialuo instanceof Student) // true
console.log(xialuo instanceof People) // true
console.log(xialuo instanceof Object) // true
console.log([] instanceof Array) // true
console.log([] instanceof Object) // true
console.log([] instanceof Object) // true
```
#### 原型--隐士原型、显示原型
```javascript
// class 实际上是函数 ,语法糖
typeof People // 'function'
console.log( xialuo.__proto__) // 隐式原型
console.log( Student.prototype ) // 显示原型
xialuo.__proto__ === Student.prototype // ture
```
![image-20210303102314479](https://image.mdashen.com/pic/image-20210303102314479.png)
* 每个 class 都有显示原型 `prototype`
* 每个实例都有隐式原型 `__proto__`
* 实例的 `__proto__` 执行对应 class 的 `prototype`
#### 基于原型和执行规则
* 获取属性 xialuo.name 或执行方法 xialuo.sayhi() 时
* 先在自身属性和方法寻找
* 找不到,去 `__proto__` 总查找
#### 原型链
```javascript
// class Student 是 class People 的子类
People.prototype === Student.prototype.__proto__
```
![image-20210303103401585](https://image.mdashen.com/pic/image-20210303103401585.png)
```javascript
xialuo.hasOwnProperty('name') // true
xialuo.hasOwnProperty('sayHi') // false
xialuo.hasOwnProperty('hasOwnProperty') // false
```
#### instanceof 原理
```javascript
xialuo instanceof People // true
// 按照上图 xialuo 的原型链,去找 People 的显示原型 prototype 能找到 true
```
#### 提示
* class 是 ES6 语法规范,由 ECMA 委员会发布
* ECMA 只规定语法规则,即只规定代码的书写规范,不规定如何使用
* 以上实现方式 为 V8 引擎的实现方式,也是主流实现方式
### 作用域和闭包
#### 作用域、自由变量
```javascript
// 创建 10 个 `<a>` 标签,点击的时候弹出来对应的序号
let a
for (let i = 0; i < 10; i++) {
a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function (e) {
e.preventDefault()
alert(i)
})
document.body.appendChild(a)
}
```
![image-20210303115729445](https://image.mdashen.com/pic/image-20210303115729445.png)
* 全局作用域
* 函数作用域
* 块级作用域(ES6新增)
##### 自由变量
* 一个变量在当前作用域没有定义,但被使用了
* 向上级作用域,一层一层一次寻找,指导找到为止
* 如果找到全局作用域都没找到,则报错 xx is not defined
#### 闭包
* 作用域应用的特殊情况,有两种表现
* 函数作为参数被传递
* 函数作用返回值被返回
```javascript
// 函数作为返回值
function create() {
let a = 100
return function () {
console.log(a)
}
}
let a = 200
let fn = create()
fn() // 100
// create()()
```
```javascript
// 函数作为参数
function print(fnn) {
let b = 200
fnn()
}
let b = 100
function fnn() {
console.log(b)
}
print(fnn) // 100
```
> 闭包:自由变量从函数定义的地方开始找,依次向上级作用域查找。
##### 闭包的实际应用
* 隐藏数据
```javascript
function createCache() {
const data = {} // 闭包中的数据,被隐藏,不被外界访问
return {
set: function (key, val) {
data[key] = val
},
get: function (key) {
return data[key]
},
}
}
const c = createCache()
c.set('a', 100)
// 在外部无法直接访问data中的数据
// console.log(data.a) // data is not defined
console.log(c.get('a'))
```
#### this 赋值问题
* 常见情况
* 作为普通函数
* 使用 call apply bind
* 作为对象方法被调用
* 在 class 方法中调用
* 箭头函数
* **this 在各个场景中取值:函数执行的时候确定的,不是函数定义的时候确认的**
```javascript
function fn1() {
console.log(this)
}
fn1() // window
fn1.call({ x:100 }) //{ x: 100 }
const fn2 = fn1.bind({ x: 200 })
fn2() // {x: 200}
```
![image-20210303123535183](https://image.mdashen.com/pic/image-20210303123535183.png)
##### call apply bind 简介-区别
> 改变函数执行时的上下文,即改变函数运行时的 this 指向
* call 和 apply
```javascript
obj.call(thisObj, arg1, arg2, ...);
obj.apply(thisObj, [arg1, arg2, ...]);
```
> 两者作用一致,都是把`obj`(即this)绑定到`thisObj`,这时候`thisObj`具备了`obj`的属性和方法。或者说`thisObj`『继承』了`obj`的属性和方法。绑定后会立即执行函数。
>
> 唯一区别是**apply接受的是数组参数**,**call接受的是连续参数**。
bind的使用
```javascript
obj.bind(thisObj, arg1, arg2, ...);
```
> 把obj绑定到thisObj,这时候thisObj具备了obj的属性和方法。与call和apply不同的是,bind绑定后不会立即执行。需要调用