├── .czrc ├── .gitignore ├── README.md ├── commitlint.config.js ├── package.json ├── 其他 ├── README.md ├── blog │ └── javascript │ │ ├── 01.类型基础.md │ │ ├── 02.数据类型检测.md │ │ ├── 03.数据类型转换.md │ │ ├── 04.执行上下文.md │ │ ├── 05.作用域和作用域链.md │ │ ├── 06.闭包.md │ │ └── 07.深入理解this.md ├── code │ ├── 1.vue响应式.js │ └── index.html ├── git rebase vs git merge.md ├── interview.md └── interview │ ├── DataStructureAndAlgorithm │ ├── leetcode │ │ ├── Array数组 │ │ │ ├── 1.easy.md │ │ │ └── desc.md │ │ ├── README.md │ │ └── String字符串 │ │ │ ├── 1.反转.md │ │ │ ├── 2.回文.md │ │ │ ├── 3,前缀问题.md │ │ │ └── 其他.md │ ├── 动态规划.md │ └── 动态规划1.md │ ├── MicFront │ ├── single-spa源码分析.md │ └── 微前端的介绍和single-spa应用.md │ ├── Node │ └── 目录.md │ ├── browser │ └── 跨域.md │ ├── git │ └── git_rebase.md │ ├── int │ ├── answer.md │ ├── code_answer.md │ ├── list.md │ ├── question.md │ └── 手写.md │ ├── javascript │ ├── 1.数据类型.md │ ├── 2.原型和原型链.md │ ├── call,apply.md │ ├── 原理以及实现api系列.md │ └── 知识点.md │ ├── vue │ ├── 1.响应式.md │ ├── new Vue.md │ └── 从template到dom.md │ └── work │ └── c.md └── 前端工程化 ├── .DS_Store ├── cli └── 脚手架开发.md ├── metro └── document.md ├── package.json文件说明.md └── webpack └── webpack5常用配置说明.md /.czrc: -------------------------------------------------------------------------------- 1 | { "path": "cz-conventional-changelog" } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## webpack5 常用配置 2 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {extends: ['@commitlint/config-conventional']} 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "front-end-knowledge-system", 3 | "version": "1.0.0", 4 | "description": "前端知识体系搭建", 5 | "main": "index.js", 6 | "scirpt": { 7 | "release": "standard-version" 8 | }, 9 | "husky": { 10 | "hooks": { 11 | "commit-msg": "commitlint -e $GIT_PARAMS" 12 | } 13 | }, 14 | "repository": "git@github.com:charmJiang/front-end-knowledge-system.git", 15 | "author": "tomJiang <1219385811@qq.com>", 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /其他/README.md: -------------------------------------------------------------------------------- 1 | ## javascript 2 | - [01.类型基础](https://github.com/charmJiang/front-end-knowledge-systems/blob/main/blog/javascript/01.%E7%B1%BB%E5%9E%8B%E5%9F%BA%E7%A1%80.md) 3 | 4 | - [02.数据类型检测](https://github.com/charmJiang/front-end-knowledge-systems/blob/main/blog/javascript/02.%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E6%A3%80%E6%B5%8B.md) 5 | 6 | - [03.数据类型转换](https://github.com/charmJiang/front-end-knowledge-systems/blob/main/blog/javascript/03.%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2.md) 7 | 8 | - [04.执行上下文](https://github.com/charmJiang/front-end-knowledge-systems/blob/main/blog/javascript/04.%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87.md) 9 | 10 | - [05.作用域和作用域链](https://github.com/charmJiang/front-end-knowledge-systems/blob/main/blog/javascript/05.%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E4%BD%9C%E7%94%A8%E5%9F%9F%E9%93%BE.md) 11 | 12 | - [06.闭包](https://github.com/charmJiang/front-end-knowledge-systems/blob/main/blog/javascript/06.%E9%97%AD%E5%8C%85.md) 13 | 14 | - [07.深入理解this](https://github.com/charmJiang/front-end-knowledge-systems/blob/main/blog/javascript/07.%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3this.md) 15 | 16 | 17 | ### 其他 18 | 19 | - [跨域的几种形式](https://github.com/charmJiang/front-end-knowledge-systems/blob/main/browser/%E8%B7%A8%E5%9F%9F.md) 20 | 21 | -------------------------------------------------------------------------------- /其他/blog/javascript/01.类型基础.md: -------------------------------------------------------------------------------- 1 | ### 1.类型基础 2 | 3 | #### 1.1.js内置类型 4 | 5 | ![](https://gitee.com/vr2/images/raw/master/img/231452.png) 6 | 7 | 8 | 9 | - `JS` 中分为七种内置类型,七种内置类型又分为两大类型:基本类型和对象(`Object`)。 10 | - 基本类型有七种: `null`,`undefined`,`boolean`,`number`,`string`,`symbol`, `bigint`。(js最大安全数是 **Number.MAX_SAFE_INTEGER == Math.pow(2,53) - 1, 而不是Math.pow(2,52) - 1**) 11 | - 一种引用数据类型——`Object`(Object本质上是由一组无序的名值对组成的)。里面包含 `function、Array、Date`等。JavaScript不支持任何创建自定义类型的机制,而所有值最终都将是上述 8 种数据类型之一。 12 | - **引用数据类型:** 对象`Object`(包含普通对象-`Object`,数组对象-`Array`,正则对象-`RegExp`,日期对象-`Date`,数学函数-`Math`,函数对象-`Function` 13 | 14 | > JavaScript 的数据类型最后都会在初始化之后放在不同的内存中,因此上面的数据类型大致可以分成两类来进行存储: 15 | 16 | - **原始数据类型**:基础类型存储在栈内存,被引用或拷贝时,会创建一个完全相等的变量;占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。 17 | - **引用数据类型**:引用类型存储在堆内存,存储的是地址,多个引用指向同一个地址,这里会涉及一个“共享”的概念;占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。 18 | 19 | ##### JavaScript 中的数据是如何存储在内存中的? 20 | 21 | > 在 JavaScript 中,原始类型的赋值会完整复制变量值,而引用类型的赋值是复制引用地址。 22 | 23 | 在 JavaScript 的执行过程中, 主要有三种类型内存空间,分别是`代码空间`、`栈空间`、`堆空间`。其中的代码空间主要是存储可执行代码的,原始类型(`Number、String、Null、Undefined、Boolean、Symbol、BigInt`)的数据值都是直接保存在“栈”中的,引用类型(Object)的值是存放在“堆”中的。因此在栈空间中(执行上下文),原始类型存储的是变量的值,而引用类型存储的是其在"堆空间"中的地址,当 JavaScript 需要访问该数据的时候,是通过栈中的引用地址来访问的,相当于多了一道转手流程。 24 | 25 | JavaScript 引擎需要用栈来维护程序执行期间上下文的状态,如果栈空间大了话,所有的数据都存放在栈空间里面,那么会影响到上下文切换的效率,进而又影响到整个程序的执行效率。通常情况下,栈空间都不会设置太大,主要用来存放一些原始类型的小数据。而引用类型的数据占用的空间都比较大,所以这一类数据会被存放到堆中,堆空间很大,能存放很多大的数据,不过缺点是分配内存和回收内存都会占用一定的时间。因此需要“栈”和“堆”两种空间。 26 | 27 | ##### 题目1 28 | 29 | ```js 30 | const p = { 31 | name: 'tom', 32 | age: 18 33 | } 34 | 35 | const p2 = p 36 | 37 | p2.name = 'alex' 38 | 39 | // alex 40 | console.log(p.name) 41 | 42 | // alex 43 | console.log(p.name) 44 | 45 | ``` 46 | 47 | 体现了引用类型的“共享”的特性,即这两个值都存在同一块内存中共享,一个发生了改变,另外一个也随之跟着变化。 48 | 49 | ##### 题目2 50 | 51 | ```js 52 | let a = { 53 | name: 'Julia', 54 | age: 20 55 | } 56 | function change(o) { 57 | o.age = 24; 58 | o = { 59 | name: 'Kath', 60 | age: 30 61 | } 62 | return o; 63 | } 64 | let b = change(a); 65 | console.log(b.age); // {name: "Kath", age: 30} 66 | console.log(a.age); // {name: "Julia", age: 24} 67 | ``` 68 | 69 | 原因在于:函数传参进来的 `o`,传递的是对象在堆中的内存地址值,通过调用 `o.age = 24`(第 7 行代码)确实改变了 `a` 对象的 `age` 属性;但是第 12 行代码的 `return` 却又把 `o` 变成了另一个内存地址,将 `{name: "Kath", age: 30}` 存入其中,最后返回 `b` 的值就变成了 `{name: "Kath", age: 30}`。而如果把第 12 行去掉,那么 `b` 就会返回 `undefined` 70 | 71 | ##### 题目3 72 | 73 | ```js 74 | var a = {n: 1}; 75 | var b = a; 76 | a.x = a = {n: 2}; 77 | 78 | a.x // --> undefined 79 | b.x // --> {n: 2} 80 | ``` 81 | 82 | - 1、优先级。`.`的优先级高于`=`,所以先执行`a.x`,堆内存中的`{n: 1}`就会变成`{n: 1, x: undefined}`,改变之后相应的`b.x`也变化了,因为指向的是同一个对象。 83 | - 2、赋值操作是`从右到左`,所以先执行`a = {n: 2}`,`a`的引用就被改变了,然后这个返回值又赋值给了`a.x`,**需要注意**的是这时候`a.x`是第一步中的`{n: 1, x: undefined}`那个对象,其实就是`b.x`,相当于`b.x = {n: 2}` 84 | 85 | ![img](http://resource.muyiy.cn/image/2019-07-24-060217.png) 86 | 87 | #### 1.2 .什么是bignit 88 | 89 | `BigInt`是一种新的数据类型,用于当整数值大于Number数据类型支持的范围时。这种数据类型允许我们安全地对大整数执行算术操作,表示高分辨率的时间戳,使用大整数id,等等,而不需要使用库。 90 | 91 | **为什么需要BigInt?** 92 | 93 | 在JS中,所有的数字都以双精度64位浮点格式表示,那这会带来什么问题呢? 94 | 95 | > 这导致JS中的Number无法精确表示非常大的整数,它会将非常大的整数四舍五入,确切地说,JS中的`Number`类型只能安全地表示`-9007199254740991(-(2^53-1))和9007199254740991((2^53-1))`,任何超出此范围的整数值都可能失去精度。 96 | 97 | ```text 98 | console.log(999999999999999); //=>10000000000000000 99 | ``` 100 | 101 | 同时也会有一定的安全性问题: 102 | 103 | ```text 104 | 9007199254740992 === 9007199254740993; // → true 居然是true! 105 | ``` 106 | 107 | **如何创建并使用BigInt?** 108 | 109 | 要创建`BigInt`,只需要在数字末尾追加`n`即可 110 | 111 | ```text 112 | console.log( 9007199254740995n ); // → 9007199254740995n 113 | console.log( 9007199254740995 ); // → 9007199254740996 114 | ``` 115 | 116 | 另一种创建`BigInt`的方法是用`BigInt()`构造函数 117 | 118 | ```text 119 | BigInt("9007199254740995"); // → 9007199254740995n 120 | ``` 121 | 122 | 简单使用如下: 123 | 124 | ```text 125 | 10n + 20n; // → 30n 126 | 10n - 20n; // → -10n 127 | +10n; // → TypeError: Cannot convert a BigInt value to a number 128 | -10n; // → -10n 129 | 10n * 20n; // → 200n 130 | 20n / 10n; // → 2n 131 | 23n % 10n; // → 3n 132 | 10n ** 3n; // → 1000n 133 | 134 | const x = 10n; 135 | ++x; // → 11n 136 | --x; // → 9n 137 | console.log(typeof x); //"bigint" 138 | ``` 139 | 140 | **值得警惕的点** 141 | 142 | > `BigInt`不支持一元加号运算符, 这可能是某些程序可能依赖于 + 始终生成 `Number` 的不变量,或者抛出异常。另外,更改 `+` 的行为也会破坏 `asm.js` 代码。 143 | 144 | 因为隐式类型转换可能丢失信息,所以不允许在`bigint`和 `Number` 之间进行混合操作。当混合使用大整数和浮点数时,结果值可能无法由`BigInt`或`Number`精确表示。 145 | 146 | ```text 147 | 10 + 10n; // → TypeError 148 | ``` 149 | 150 | > 不能将`BigInt`传递给`Web api`和内置的 JS 函数,这些函数需要一个 Number 类型的数字。尝试这样做会报TypeError错误。 151 | 152 | ```text 153 | Math.max(2n, 4n, 6n); // → TypeError 154 | ``` 155 | 156 | > 当 `Boolean` 类型与 `BigInt` 类型相遇时,`BigInt` 的处理方式与`Number`类似,换句话说,只要不是`0n`,`BigInt`就被视为`truthy`的值。 157 | 158 | ```text 159 | if(0n){//条件判断为false 160 | 161 | } 162 | if(3n){//条件为true 163 | 164 | } 165 | ``` 166 | 167 | - 元素都为BigInt的数组可以进行sort。 168 | - `BigInt`可以正常地进行位运算,如`|`、`&`、`<<`、`>>`和`^` 169 | 170 | **浏览器兼容性** 171 | 172 | caniuse的结果: 173 | 174 | ![](https://gitee.com/vr2/images/raw/master/112222.png) 175 | 176 | 其实现在的兼容性并不怎么好,只有chrome67、firefox、Opera这些主流实现,要正式成为规范,其实还有很长的路要走 177 | 178 | #### 1.3.null和undefined的区别 179 | 180 | - `null` 和`undefined`都是基础数据类型 181 | 182 | - typeof null --> 'object' 183 | 184 | - typeof undefined --> 'undefined' 185 | 186 | **undefined** 187 | 188 | `Undefined`类型只有一个值,即`undefined`。当声明的变量还未被初始化时,变量的默认值为`undefined`。 189 | 190 | - 变量被声明了,但没有赋值时,就等于`undefined`。 191 | - 调用函数时,应该提供的参数没有提供,该参数等于`undefined`。 192 | - 对象没有赋值的属性,该属性的值为`undefined`。 193 | - 函数没有返回值时,默认返回`undefined` 194 | 195 | **null** 196 | 197 | `Null`类型也只有一个值,即`null`。`null`用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象。 198 | 199 | - 作为函数的参数,表示该函数的参数不是对象。 200 | - 作为对象原型链的终点 201 | 202 | 203 | 204 | #### 1.4.为什么typeof null 为 'object'? null是对象吗? 205 | 206 | `null`不是对象。虽然 `typeof null` 会输出 object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象然而 null 表示为全零,所以将它错误的判断为 object 。 207 | 208 | 209 | 210 | #### 1.5. 01 + 0.2 != 0.3? 211 | 212 | 计算机无法直接对十进制的数字进行运算,这是硬件物理特性已经决定的。这样运算就分成了两个部分:**先按照IEEE 754转成相应的二进制,然后对阶运算** 213 | 214 | ##### 1.5.1进制转换 215 | 216 | 0.1和0.2转换成二进制后会无限循环 217 | 218 | ```js 219 | 0.1 -> 0.0001100110011001...(无限循环) 220 | 0.2 -> 0.0011001100110011...(无限循环) 221 | ``` 222 | 223 | 但是由于IEEE 754尾数位数限制,需要将后面多余的位截掉,这样在进制之间的转换中精度已经损失。 224 | 225 | ##### 1.5.2 对阶运算 226 | 227 | 由于指数位数不相同,运算时需要对阶运算 这部分也可能产生精度损,按照上面两步运算(包括两步的精度损失),最后的结果是 228 | 229 | ```js 230 | 0.0100110011001100110011001100110011001100110011001100 231 | ``` 232 | 233 | 结果转换成十进制之后就是0.30000000000000004,这样就有了:0.1 + 0.2 != 0.3 234 | 235 | ##### 1.5.3 结论 236 | 237 | - 0.1和0.2在转换成二进制后会无限循环,由于标准位数的限制后面多余的位数会被截掉,此时就已经出现了精度的损失,相加后因浮点数小数位的限制而截断的二进制数字在转换为十进制就会变成`0.30000000000000004` 238 | 239 | - **精度损失可能出现在进制转化和对阶运算过程中** 240 | - **精度损失可能出现在进制转化和对阶运算过程中** 241 | - **精度损失可能出现在进制转化和对阶运算过程中** 242 | 243 | ##### 1.5.4 怎么解决精度问题? 244 | 245 | - 将数字转成整数 246 | 247 | ```js 248 | function add(num1, num2) { 249 | const num1Digits = (num1.toString().split('.')[1] || '').length; 250 | const num2Digits = (num2.toString().split('.')[1] || '').length; 251 | const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits)); 252 | return (num1 * baseNum + num2 * baseNum) / baseNum; 253 | } 254 | ``` 255 | 256 | - 第三方库 `Math.js`, `big.js`... -------------------------------------------------------------------------------- /其他/blog/javascript/02.数据类型检测.md: -------------------------------------------------------------------------------- 1 | ### 2.深入理解数据类型检测 2 | 3 | - `typeof` 4 | - 只能检测基本数据类型 但是typeof null 是"object",不能检测复杂类型 5 | - `instanceof` 通过原型链去查找的 6 | - 返回的是false 或者true 7 | - 可以识别内置对象类型、自定义类型及其父类型 8 | - 不能识别标准类型,会返回false 9 | - 不能识别undefined、null,会报错 10 | - `constructor`属性 实例对象的constructor属性指向其构造函数。如果是内置类型,则输出`function` 数据类型`(){[native code]}`;如果是自定义类型,则输出`function` 数据类型`(){}` 11 | - 可以识别标准类型、内置对象类型及自定义类型 12 | - 不能识别`undefined`、`nul`,会报错,因为它俩没有构造函数 13 | - `Object.prototype.toString()` 返回 `[object 数据类型]` 14 | - 可以识别标准类型及内置对象类型 15 | - 不能识别自定义类型 16 | - `Array.isArray()` 数组的检测 17 | 18 | #### 2.1typeof 19 | 20 | `typeof`只能判断基本数据类型,不能判断引用数据类型(返回 `object`) 21 | 22 | ```js 23 | let s = 'tom'; 24 | let num = 123; 25 | let bool = true; 26 | let sy = Symbol('tom') 27 | let u; 28 | let n = null; 29 | let o = new Object(); 30 | let bigNum = 10n 31 | 32 | 33 | typeof s // string 34 | typeof num // number 35 | typeof bool // boolean 36 | typeof u // undefined 37 | typeof sy // symbol 38 | 39 | typeof n // object 40 | typeof o // object 41 | typeof bigNum // 'bigint' 42 | ``` 43 | 44 | #### 2.2 instanceof 45 | 46 | - 通过原型链去查找的 47 | - 返回的是false 或者true 48 | - 可以识别内置对象类型、自定义类型及其父类型 49 | - 不能识别标准类型,会返回false 50 | - 不能识别undefined、null,会报错 51 | 52 | ```js 53 | 'a' instanceof String -> false 54 | 12 instanceof Number -> false 55 | true instanceof Boolean -> false 56 | undefined instanceof Undefined ->报错 57 | [] instanceof Array -> true 58 | new Person instanceof Person -> true 59 | new Person instanceof Object -> true 60 | ``` 61 | 62 | 63 | 64 | ##### 2.2.1instanceof原理(手写) 65 | 66 | ​ instanceof 原理就是一层一层查找 __proto__,如果和 constructor.prototype相等则返回 true,如果一直没有查找成功则返回 false。 67 | 68 | ```js 69 | //实现instanceof 70 | 71 | function instance_of(L, R) {//L 表示左表达式,R 表示右表达式 72 | var O = R.prototype;// 取 R 的显示原型 73 | L = L.__proto__;// 取 L 的隐式原型 74 | while (true) { 75 | // Object.prototype.__proto__ === null 76 | if (L === null) 77 | return false; 78 | if (O === L)// 这里重点:当 O 严格等于 L 时,返回 true 79 | return true; 80 | L = L.__proto__; 81 | } 82 | } 83 | ``` 84 | 85 | #### 2.3 constructor 86 | 87 | `constructor`属性 实例对象的constructor属性指向其构造函数。如果是内置类型,则输出`function` 数据类型`(){[native code]}`;如果是自定义类型,则输出`function` 数据类型`(){}` 88 | 89 | - 可以识别标准类型、内置对象类型及自定义类型 90 | - 不能识别`undefined`、`nul`,会报错,因为它俩没有构造函数 91 | 92 | ```js 93 | ('a').constructor -> function String(){[native code]} 94 | (undefined).constructor) -> 报错 95 | null).constructor -> 报错 96 | {name: "jerry"}).constructor -> function Object(){[native code]} 97 | (new Person).constructor -> function Person(){} 98 | // 封装成一个类型识别函数 99 | function type(obj){ 100 | var temp = obj.constructor.toString(); 101 | return temp.replace(/^function (\w+)\(\).+$/,'$1'); 102 | } 103 | ``` 104 | 105 | 106 | 107 | #### 2.4Object.prototype.toString() 108 | 109 | `Object.prototype.toString()` 返回 `[object 数据类型]` 110 | 111 | - 可以识别标准类型及内置对象类型 112 | - 不能识别自定义类型 113 | 114 | ```js 115 | Object.prototype.toString.call('a') -> [object String] 116 | Object.prototype.toString.call(undefined) -> [object Undefined] 117 | Object.prototype.toString.call(null) -> [object Null] 118 | Object.prototype.toString.call({name: "jerry"})) ->[object Object] 119 | Object.prototype.toString.call(function(){}) -> [object Function] 120 | Object.prototype.toString.call(new Person)) -> [object Object] 121 | ``` 122 | 123 | 封装成一个类型识别函数 124 | 125 | ```js 126 | function type(obj){ 127 | return Object.prototype.toString.call(obj).slice(8,-1).toLowerCase(); 128 | } 129 | ``` 130 | 131 | ##### 2.4.1Object.prototype.toString()原理 132 | 133 | 对于 `Object.prototype.toString.call(arg)`,若参数为 `null` 或 `undefined`,直接返回结果。 134 | 135 | ```js 136 | Object.prototype.toString.call(null); // => "[object Null]" 137 | 138 | Object.prototype.toString.call(undefined); // => "[object Undefined]" 139 | ``` 140 | 141 | 若参数不为 `null` 或 `undefined`,则将参数转为对象,再作判断。对于原始类型,转为对象的方法即装箱,此处不赘述。 142 | 143 | 转为对象后,取得该对象的 `[Symbol.toStringTag]` 属性值(可能会遍历原型链)作为 `tag`,如无该属性,或该属性值不为字符串类型,则依下表取得 `tag`, 然后返回 `"[object " + tag + "]"` 形式的字符串。 144 | 145 | ```js 146 | // Boolean 类型,tag 为 "Boolean" 147 | Object.prototype.toString.call(true); // => "[object Boolean]" 148 | 149 | // Number 类型,tag 为 "Number" 150 | Object.prototype.toString.call(1); // => "[object Boolean]" 151 | 152 | // String 类型,tag 为 "String" 153 | Object.prototype.toString.call(""); // => "[object String]" 154 | 155 | // Array 类型,tag 为 "String" 156 | Object.prototype.toString.call([]); // => "[object Array]" 157 | 158 | // Arguments 类型,tag 为 "Arguments" 159 | Object.prototype.toString.call((function() { 160 | return arguments; 161 | })()); // => "[object Arguments]" 162 | 163 | // Function 类型, tag 为 "Function" 164 | Object.prototype.toString.call(function(){}); // => "[object Function]" 165 | 166 | // Error 类型(包含子类型),tag 为 "Error" 167 | Object.prototype.toString.call(new Error()); // => "[object Error]" 168 | 169 | // RegExp 类型,tag 为 "RegExp" 170 | Object.prototype.toString.call(/\d+/); // => "[object RegExp]" 171 | 172 | // Date 类型,tag 为 "Date" 173 | Object.prototype.toString.call(new Date()); // => "[object Date]" 174 | 175 | // 其他类型,tag 为 "Object" 176 | Object.prototype.toString.call(new class {}); // => "[object Object]" 177 | ``` 178 | 179 | 下面为部署了 `Symbol.toStringTag` 的例子。可以看出,属性值期望是一个字符串,否则会被忽略。 180 | 181 | ```js 182 | var o1 = { [Symbol.toStringTag]: "A" }; 183 | var o2 = { [Symbol.toStringTag]: null }; 184 | 185 | Object.prototype.toString.call(o1); // => "[object A]" 186 | Object.prototype.toString.call(o2); // => "[object Object]" 187 | ``` 188 | 189 | `Symbol.toStringTag` 也可以部署在原型链上: 190 | 191 | ```js 192 | class A {} 193 | A.prototype[Symbol.toStringTag] = "A"; 194 | Object.prototype.toString.call(new A()); // => "[object A]" 195 | ``` 196 | 197 | 新标准引入了 `[Symbol.toStringTag]` 属性,是为了把此方法接口化,用于规范新引入的对象对此方法的调用。但对于“老旧”的对象,就只能直接输出值,以保证兼容性。 198 | 199 | [从深入到通俗:Object.prototype.toString.call()](https://zhuanlan.zhihu.com/p/118793721) 200 | 201 | #### 2.5 Array.isArray() 202 | 203 | 数组检测 204 | 205 | ```js 206 | const a = [1,2,3] 207 | Array.isArray(a) -> true 208 | Array.isArray([]) -> true 209 | ``` 210 | 211 | 212 | 213 | #### 2.6判断是否是promise对象 214 | 215 | ```js 216 | function isPromise (val) { 217 | return ( 218 | typeof val.then === 'function' && 219 | typeof val.catch === 'function' 220 | ) 221 | } 222 | ``` 223 | 224 | 225 | 226 | #### 2.7 Object.is 和 === 区别 227 | 228 | `Object.is()` 判断两个值是否[相同](https://www.apiref.com/javascript-zh/Equality_comparisons_and_sameness.htm)。如果下列任何一项成立,则两个值相同: 229 | 230 | - 两个值都是 [`undefined`](https://www.apiref.com/javascript-zh/Reference/Global_Objects/undefined.htm) 231 | - 两个值都是 [`null`](https://www.apiref.com/javascript-zh/Reference/Global_Objects/null.htm) 232 | - 两个值都是 `true` 或者都是 `false` 233 | - 两个值是由相同个数的字符按照相同的顺序组成的字符串 234 | - 两个值指向同一个对象 235 | - 两个值都是数字并且 236 | - 都是正零 `+0` 237 | - 都是负零 `-0` 238 | - 都是 [`NaN`](https://www.apiref.com/javascript-zh/Reference/Global_Objects/NaN.htm) 239 | - 都是除零和 [`NaN`](https://www.apiref.com/javascript-zh/Reference/Global_Objects/NaN.htm) 外的其它同一个数字 240 | 241 | 这种相等性判断逻辑和传统的 [`==`](https://www.apiref.com/javascript-zh/Reference/Operators/Comparison_Operators.htm#Equality) 运算符会对它两边的操作数做隐式类型转换(如果它们类型不同),然后才进行相等性比较,(所以才会有类似 `"" == false` 等于 `true` 的现象),但 `Object.is` 不会做这种类型转换。 242 | 243 | 这与 [`NaN`](https://www.apiref.com/javascript-zh/Reference/Operators/Comparison_Operators.htm#Identity)。 244 | 245 | > `Object`在严格等于的基础上修复了一些特殊情况下的失误,具体来说就是`+0`和`-0`,`NaN`和`NaN`。 源码如下 246 | 247 | ```js 248 | function is(x, y) { 249 | if (x === y) { 250 | //运行到1/x === 1/y的时候x和y都为0,但是1/+0 = +Infinity, 1/-0 = -Infinity, 是不一样的 251 | return x !== 0 || y !== 0 || 1 / x === 1 / y; 252 | } else { 253 | //NaN===NaN是false,这是不对的,我们在这里做一个拦截,x !== x,那么一定是 NaN, y 同理 254 | //两个都是NaN的时候返回true 255 | return x !== x && y !== y; 256 | } 257 | } 258 | ``` 259 | 260 | -------------------------------------------------------------------------------- /其他/blog/javascript/03.数据类型转换.md: -------------------------------------------------------------------------------- 1 | ### 3.深入理解数据类型转换 2 | 3 | #### 3.1转换规则 4 | 5 | - `-、*、/、%`:一律转换成数值后计算 6 | - +: 7 | - 数字 + 字符串 = 字符串, 运算顺序是从左到右 8 | - 数字 + 对象, 优先调用对象的`valueOf -> toString` 9 | - 数字 + `boolean/null` -> 数字 10 | - 数字 + `undefined` -> `NaN` 11 | - `[1].toString() === '1'` 12 | - `{}.toString() === '[object object]'` 13 | - `NaN !== NaN` 、+`undefined` 为 `NaN` 14 | 15 | js中类型转换只有3种情况 16 | 17 | - 转换为布尔值 18 | - 转换为数字 19 | - 转换为字符串 20 | 21 | ![类型转换](https://gitee.com/vr2/images/raw/master/112230.png) 22 | 23 | #### 3.2 转Boolean 24 | 25 | 在条件判断时,除了 `undefined`,`null`, `false`, `NaN`, `''`, `0`, `-0`,其他所有值都转为 `true`,包括所有对象 26 | 27 | #### 3.3 对象转原始类型是根据什么流程运行的 28 | 29 | > 对象转原始类型,会调用内置的`[ToPrimitive]`函数,对于该函数而言,其逻辑如下: 30 | 31 | - 如果有`Symbol.toPrimitive()`方法,优先调用再返回 32 | - 调用`valueOf()`,如果转换为原始类型,则返回 33 | - 调用`toString()`,如果转换为原始类型,则返回 34 | - 如果都没有返回原始类型,会报错 35 | 36 | ```js 37 | var obj = { 38 | value: 3, 39 | valueOf() { 40 | return 4; 41 | }, 42 | toString() { 43 | return '5' 44 | }, 45 | [Symbol.toPrimitive]() { 46 | return 6 47 | } 48 | } 49 | console.log(obj + 1); // 输出7 50 | ``` 51 | 52 | #### 3.4 经典面试题 让a == 1 && a == 2 等式成立 53 | 54 | ```js 55 | var a = { 56 | value: 0, 57 | valueOf: function() { 58 | this.value++; 59 | return this.value; 60 | } 61 | }; 62 | console.log(a == 1 && a == 2);//true 63 | ``` 64 | 65 | 66 | 67 | #### 3.5 四则运算符 68 | 69 | > 它有以下几个特点: 70 | 71 | - 运算中其中一方为字符串,那么就会把另一方也转换为字符串 72 | - 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串 73 | 74 | ```js 75 | 1 + '1' // '11' 76 | true + true // 2 77 | 4 + [1,2,3] // "41,2,3" 78 | ``` 79 | 80 | - 对于第一行代码来说,触发特点一,所以将数字 `1` 转换为字符串,得到结果 `'11'` 81 | - 对于第二行代码来说,触发特点二,所以将 `true` 转为数字 `1` 82 | - 对于第三行代码来说,触发特点二,所以将数组通过 `toString`转为字符串 `1,2,3`,得到结果 `41,2,3` 83 | 84 | > 另外对于加法还需要注意这个表达式 `'a' + + 'b'` 85 | 86 | ```js 87 | 'a' + + 'b' // -> "aNaN" 88 | ``` 89 | 90 | - 因为 `+ 'b'` 等于 `NaN`,所以结果为 `"aNaN"`,你可能也会在一些代码中看到过 `+ '1'`的形式来快速获取 `number` 类型。 91 | - 那么对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字 92 | 93 | ```js 94 | 4 * '3' // 12 95 | 4 * [] // 0 96 | 4 * [1, 2] // NaN 97 | ``` 98 | 99 | 100 | 101 | #### 3.6 比较运算符 102 | 103 | - 如果是对象,就通过 `toPrimitive` 转换对象 104 | - 如果是字符串,就通过 `unicode` 字符索引来比较 105 | 106 | ```js 107 | let a = { 108 | valueOf() { 109 | return 0 110 | }, 111 | toString() { 112 | return '1' 113 | } 114 | } 115 | a > -1 // true 116 | ``` 117 | 118 | > 在以上代码中,因为 `a` 是对象,所以会通过 `valueOf` 转换为原始类型再比较值。 119 | 120 | 121 | 122 | #### 3.7 [] == ![]结果是什么?为什么? 123 | 124 | - `==` 中,左右两边都需要转换为数字然后进行比较 125 | 126 | - `[]`转换为数字为`0` 127 | 128 | - `![]` 首先是转换为布尔值,由于`[]`作为一个引用类型转换为布尔值为`true` 129 | 130 | - 因此`![]`为`false`,进而在转换成数字,变为`0` 131 | 132 | - `0 == 0` , 结果为`true` 133 | 134 | 135 | 136 | #### 3.8 == 和 ===有什么区别 137 | 138 | > `===`叫做严格相等,是指:左右两边不仅值要相等,类型也要相等,例如`'1'===1`的结果是`false`,因为一边是`string`,另一边是`number` 139 | 140 | **==不像===那样严格,对于一般情况,只要值相等,就返回true,但==还涉及一些类型转换,它的转换规则如下** 141 | 142 | - 两边的类型是否相同,相同的话就比较值的大小,例如`1==2`,返回`false` 143 | - 判断的是否是`null`和`undefined`,是的话就返回true 144 | - 判断的类型是否是`String`和`Number`,是的话,把`String`类型转换成`Number`,再进行比较 145 | - 判断其中一方是否是`Boolean`,是的话就把`Boolean`转换成`N`umber\`,再进行比较 146 | - 如果其中一方为`Object`,且另一方为`String`、`Number`或者`Symbol`,会将`Object`转换成字符串,再进行比较 147 | 148 | #### 3.9 隐形转换经典面试题系列 149 | 150 | ```js 151 | 152 | var obj = { 153 | value: 3, 154 | valueOf() { 155 | return 4; 156 | }, 157 | toString() { 158 | return '5' 159 | }, 160 | [Symbol.toPrimitive]() { 161 | return 6 162 | } 163 | } 164 | console.log(obj + 1); // 输出7 165 | 166 | 167 | var a = { 168 | value: 0, 169 | valueOf: function() { 170 | this.value++; 171 | return this.value; 172 | } 173 | }; 174 | console.log(a == 1 && a == 2);//true 175 | 176 | 1 + '1' // '11' 177 | true + true // 2 178 | 4 + [1,2,3] // "41,2,3" 179 | 180 | 181 | 'a' + + 'b' // -> "aNaN" 182 | 183 | 4 * '3' // 12 184 | 4 * [] // 0 185 | 4 * [1, 2] // NaN 186 | 187 | 188 | // 一、只有 + 两边有一边是字符串, 会把其它数据类型调用toString()方法转成字符串然后拼接 189 | // + 是字符串连接符: String(1) + 'true' = '1true' 190 | 1 + "true" // '1true' 191 | 192 | // 二、算术运算符+ :会把其它数据类型调用Number()方法转成数字然后做加法计算 193 | // + 是算法运算 1 + Number(true) 194 | 1 + true // 2 195 | // 1 + Number(undefined) = 1 + NaN = NaN 196 | 1 + undefined //NaN 197 | // 1 + Number(null) = 1+ 0 =1 198 | 1 + null // 1 199 | 200 | // 三、关系运算符 会把其他数据类型转换成number之后再比较关系 201 | 202 | // 1.当关系运算符两边又一边是字符串时,会将其它数据类型使用Number()转换,然后比较关系 203 | // Number('2') > 10 = 2 > 10 = false 204 | '2' > 10 // false 205 | // 2.当关系运算符两边都是字符串,此时同时转成number然后比较关系 206 | // 重点:此时并不是按照Number()的形式转成数字,而是按照字符串对应的unicode编码转成数字 207 | // 使用这个查看字符串的unicode编码: 字符串的charCodeAt(字符下标,默认为0) 208 | // '2'.charCodeAt() > '10'.charCodeAt() = 50 > 49 = true 209 | '2' > '10' // true 210 | // 多个字符从左往右依次比较 211 | // 先比较'a' 和 'b', ‘a' 与 ‘b'不等,则直接得出结果 212 | "abc" > "b" //fasle 213 | // 先比较'a' 和'a',两者相等,继续比较第二个字符'b'与‘a',得出结果 a.charCodeA = 97 b.charCodeB=98 214 | "abc" > "aad" // true 215 | // 3.特殊情况:如果数据类型是undefined与null,得出固定的结果 216 | undefined == undefined // true 217 | undefined === undefined // true 218 | undefined == null // true 219 | null == null // ture 220 | null === null // ture 221 | // 4.NaN与任何数据比较都是NaN 222 | NaN == NaN // false 223 | 224 | //四、复杂数据类型在隐式转换时, 225 | // 如果是转换成number 226 | //1、如果输入的值已经是一个原始值,则直接返回它 227 | // 2、否则,如果输入的值是一个对象,则调用该对象的valueOf()方法, 228 | // 如果valueOf()方法的返回值是一个原始值,则返回这个原始值。 229 | // 3、否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。 230 | // 4、否则,抛出TypeError异常。 231 | 232 | // 如果是转换成string 233 | // 1、如果输入的值已经是一个原始值,则直接返回它 234 | // 2、否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。 235 | // 3、否则,如果输入的值是一个对象,则调用该对象的valueOf()方法, 236 | // 如果valueOf()方法的返回值是一个原始值,则返回这个原始值。 237 | // 4、否则,抛出TypeError异常。 238 | 239 | //复杂数据类型转number顺序如下 240 | // 1.先使用valueOf()方法获取其原始值,如果原始值不是number类型,则使用toString()方法转换成string 241 | // 2.再将string 转换成 number运算。 242 | [1,2] == '1,2' // true [1,2]转换成string [1,2].valueOf() -> [1,2] [1,2].toString() -> '1,2' 243 | 244 | var a = {} 245 | a == "[object Object]" // true a.valueOf().toString() -> "[object Object]" 246 | 247 | // 一道题 248 | var a = ??? 249 | if(a==1 && a==2 &&a==3) { 250 | console.log(1) 251 | } 252 | // 如何完善a,使其正确打印1 253 | 254 | var a = { 255 | i: 0, //声明一个属性i 256 | valueOf: function() { 257 | return ++ a.i //每次调用一次,让对象a的i属性自增一次并且返回 258 | } 259 | } 260 | if(a==1 && a==2 &&a==3) { // 每次运算时都会调用一次a的valueOf方法 261 | console.log(1) 262 | } 263 | 264 | // 五、逻辑非隐形转换与关系元算符隐式转换搞混淆 265 | // 空数组的toString()方法会得到空字符串,而空对象的toString()方法会得到字符串'[object Object]' 266 | 267 | // 1.关系运算法:将其它数据类型转成数字 268 | // 2.逻辑非:将其它数据类型使用Boolean()转成布尔类型 269 | // 以下八种情况转换为布尔类型会得到false 270 | // 0、-0、NaN、undefined、null、空字符、false、document.all() 271 | 272 | // [].valueOf().toString()得到空字符串 Number('') ==0 273 | [] == 0 // ture 274 | 275 | //本质是 ![] 逻辑非与表达式结果与0比较关系 276 | // 1.逻辑非优先级高于关系运算法 ![] = false(空数组转布尔得到true,然后取反得到fasle) 277 | // 2.false == 0 成立 278 | ![] == 0 // true 279 | 280 | // 本质其实是空对象{} 与 !{} 这个逻辑非表达式结果做比较 281 | //1.{}.valueOf().toString()得到字符串'[object Object]' 282 | //2.!{} = false 283 | //3.Number('[object Object]') -> NaN == Number(false) -> 0 284 | {} == !{} 285 | 286 | //引用类型数据存在堆中,栈中存储的是地址,所以他们的结果false 287 | {} == {} // false 288 | 289 | // 本质是空数组[] 与 ![] 这个逻辑表达式结果做比较 290 | //1. [].valueOf().toString()得到空字符串 291 | //2. ![] = false 292 | //3.Number('') == Number(false) 成立 都是0 293 | [] == ![] // true 294 | 295 | [] == [] // false 引用类型数据在堆中,栈中存储的是地址,所以他们的结果都是fasle 296 | 297 | {}.valueOf().toString() // [obejct Obejct] 298 | [].valueOf().toString() //空字符串 299 | ``` 300 | 301 | -------------------------------------------------------------------------------- /其他/blog/javascript/04.执行上下文.md: -------------------------------------------------------------------------------- 1 | ### 4.深入理解执行上下文 2 | 3 | 执行上下文是当前 JavaScript 代码被解析和执行时所在环境的抽象概念。 4 | 5 | #### 4.1执行上下文的类型 6 | 7 | 当执行 JS 代码时,会产生三种执行上下文 8 | 9 | - **全局执行上下文**:只有一个,浏览器中的全局对象就是 window 对象,`this` 指向这个全局对象。 10 | - **函数执行上下文**:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文。 11 | - **Eval 函数执行上下文**: 指的是运行在 `eval` 函数中的代码,很少用而且不建议使用。 12 | 13 | #### 4.2执行上下文的创建 14 | 15 | 执行上下文分两个阶段创建:**1)创建阶段;** **2)执行阶段** 16 | 17 | ##### 4.2.1创建阶段 18 | 19 | - 1、确定 **this** 的值,也被称为 **This Binding**。 20 | - 2、**LexicalEnvironment(词法环境)** 组件被创建。 21 | - 3、**VariableEnvironment(变量环境)** 组件被创建。 22 | 23 | ```js 24 | // 伪代码 25 | ExecutionContext = { 26 | ThisBinding = , // 确定this 27 | LexicalEnvironment = { ... }, // 词法环境 28 | VariableEnvironment = { ... }, // 变量环境 29 | } 30 | ``` 31 | 32 | ###### This Binding 33 | 34 | - **全局**执行上下文中,`this` 的值指向全局对象,在浏览器中`this` 的值指向 `window`对象,而在`nodejs`中指向这个文件的`module`对象。 35 | 36 | - **函数**执行上下文中,`this` 的值取决于函数的调用方式。具体有:默认绑定、隐式绑定、显式绑定(硬绑定)、`new`绑定、箭头函数 37 | 38 | 39 | 40 | ###### 词法环境LexicalEnvironment 41 | 42 | 词法环境有两个**组成部分** 43 | 44 | - 1、**环境记录**:存储变量和函数声明的实际位置 45 | - 2、**对外部环境的引用**:可以访问其外部词法环境 46 | 47 | 词法环境有两种**类型** 48 | 49 | - 1、**全局环境**:是一个没有外部环境的词法环境,其外部环境引用为 **null**。拥有一个全局对象(window 对象)及其关联的方法和属性(例如数组方法)以及任何用户自定义的全局变量,`this` 的值指向这个全局对象。 50 | - 2、**函数环境**:用户在函数中定义的变量被存储在**环境记录**中,包含了`arguments` 对象。对外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境。 51 | 52 | ```js 53 | // 伪代码 54 | GlobalExectionContext = { // 全局执行上下文 55 | LexicalEnvironment: { // 词法环境 56 | EnvironmentRecord: { // 环境记录 57 | Type: "Object", // 全局环境 58 | // 标识符绑定在这里 59 | outer: // 对外部环境的引用 60 | } 61 | } 62 | 63 | FunctionExectionContext = { // 函数执行上下文 64 | LexicalEnvironment: { // 词法环境 65 | EnvironmentRecord: { // 环境记录 66 | Type: "Declarative", // 函数环境 67 | // 标识符绑定在这里 // 对外部环境的引用 68 | outer: 69 | } 70 | } 71 | ``` 72 | 73 | 74 | 75 | ###### 变量环境 VariableEnvironment 76 | 77 | - 变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性。 78 | 79 | - 在 ES6 中,**词法** 环境和 **变量** 环境的区别在于前者用于存储**函数声明和变量( `let` 和 `const` )**绑定,而后者仅用于存储**变量( `var` )**绑定。 80 | 81 | 82 | 83 | ```js 84 | // eg: 85 | 86 | let a = 20; 87 | const b = 30; 88 | var c; 89 | 90 | function multiply(e, f) { 91 | var g = 20; 92 | return e * f * g; 93 | } 94 | 95 | c = multiply(20, 30); 96 | 97 | // 执行上下文如下所示 98 | GlobalExectionContext = { 99 | 100 | ThisBinding: , 101 | 102 | LexicalEnvironment: { 103 | EnvironmentRecord: { 104 | Type: "Object", 105 | // 标识符绑定在这里 106 | a: < uninitialized >, 107 | b: < uninitialized >, 108 | multiply: < func > 109 | } 110 | outer: 111 | }, 112 | 113 | VariableEnvironment: { 114 | EnvironmentRecord: { 115 | Type: "Object", 116 | // 标识符绑定在这里 117 | c: undefined, 118 | } 119 | outer: 120 | } 121 | } 122 | 123 | FunctionExectionContext = { 124 | 125 | ThisBinding: , 126 | 127 | LexicalEnvironment: { 128 | EnvironmentRecord: { 129 | Type: "Declarative", 130 | // 标识符绑定在这里 131 | Arguments: {0: 20, 1: 30, length: 2}, 132 | }, 133 | outer: 134 | }, 135 | 136 | VariableEnvironment: { 137 | EnvironmentRecord: { 138 | Type: "Declarative", 139 | // 标识符绑定在这里 140 | g: undefined 141 | }, 142 | outer: 143 | } 144 | } 145 | ``` 146 | 147 | 148 | 149 | ###### **变量提升的原因** 150 | 151 | 在创建阶段,函数声明存储在环境中,而变量会被设置为 `undefined`(在 `var` 的情况下)或保持未初始化(在 `let` 和 `const` 的情况下)。所以这就是为什么可以在声明之前访问 `var` 定义的变量(尽管是 `undefined` ),但如果在声明之前访问 `let` 和 `const` 定义的变量就会提示引用错误的原因。这就是所谓的变量提升。 152 | 153 | 154 | 155 | #### 4.2.2执行阶段 156 | 157 | 此阶段,完成对所有变量的分配,最后执行代码。 158 | 159 | 如果 Javascript 引擎在源代码中声明的实际位置找不到 `let` 变量的值,那么将为其分配 `undefined` 值。 160 | 161 | ------ 162 | 163 | 164 | 165 | #### 4.3.函数上下文 166 | 167 | 在函数上下文中,用活动对象(activation object, **AO**)来表示变量对象。 168 | 169 | 活动对象和变量对象的区别在于 170 | 171 | - 1、变量对象(**VO**)是规范上或者是JS引擎上实现的,并不能在JS环境中直接访问。 172 | - 2、当进入到一个执行上下文后,这个变量对象才会被**激活**,所以叫活动对象(**AO**),这时候活动对象上的各种属性才能被访问。 173 | 174 | 调用函数时,会为其创建一个**Arguments对象**,并自动初始化局部变量arguments,指代该Arguments对象。所有作为参数传入的值都会成为Arguments对象的数组元素。 175 | 176 | ##### 执行阶段 177 | 178 | 执行上下文的代码会分成两个阶段进行处理 179 | 180 | - 1、**进入**执行上下文 181 | - 2、代码**执行** 182 | 183 | ##### 4.3.1进入执行上下文 184 | 185 | 很明显,这个时候还没有执行代码 186 | 187 | 此时的变量对象会包括(如下顺序初始化): 188 | 189 | - 1、函数的所有形参 (only函数上下文):没有实参,属性值设为undefined。 190 | - 2、函数声明:如果变量对象已经存在相同名称的属性,则完全**替换**这个属性。 191 | - 3、变量声明:如果变量名称跟已经声明的形参或函数相同,则变量声明**不会干扰**已经存在的这类属性。 192 | 193 | ```js 194 | function foo(a) { 195 | var b = 2; 196 | function c() {} 197 | var d = function() {}; 198 | 199 | b = 3; 200 | } 201 | 202 | foo(1); 203 | ``` 204 | 205 | 对于上面的代码,这个时候的AO是 206 | 207 | ```js 208 | AO = { 209 | arguments: { 210 | 0: 1, 211 | length: 1 212 | }, 213 | a: 1, 214 | b: undefined, 215 | c: reference to function c(){}, 216 | d: undefined 217 | } 218 | ``` 219 | 220 | 形参arguments这时候已经有赋值了,但是变量还是undefined,只是初始化的值 221 | 222 | ##### 4.3.2 代码执行 223 | 224 | 这个阶段会顺序执行代码,修改变量对象的值,执行完成后AO如下 225 | 226 | ```js 227 | AO = { 228 | arguments: { 229 | 0: 1, 230 | length: 1 231 | }, 232 | a: 1, 233 | b: 3, 234 | c: reference to function c(){}, 235 | d: reference to FunctionExpression "d" 236 | } 237 | ``` 238 | 239 | ##### 总结如下: 240 | 241 | - 1、全局上下文的变量对象初始化是全局对象 242 | - 2、函数上下文的变量对象初始化只包括 Arguments 对象 243 | - 3、在进入执行上下文时会给变量对象**添加形参、函数声明、变量声明**等初始的属性值 244 | - 4、在代码执行阶段,会再次修改变量对象的属性值 -------------------------------------------------------------------------------- /其他/blog/javascript/05.作用域和作用域链.md: -------------------------------------------------------------------------------- 1 | ### 5.作用域和作用域链 2 | 3 | - 作用域: 作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找 4 | - 作用域链: 作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,我们可以访问到外层环境的变量和 函数。 5 | 6 | > 作用域链的本质上是一个指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。作用域链的前 端始终都是当前执行上下文的变量对象。全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最后一个对象。 7 | 8 | - 当我们查找一个变量时,如果当前执行环境中没有找到,我们可以沿着作用域链向后查找 9 | 10 | - 作用域链的创建过程跟执行上下文的建立有关.... 11 | 12 | ## 13 | 14 | #### 5.1作用域 15 | 16 | 作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找 17 | 18 | 19 | 20 | #### 5.2作用域链 21 | 22 | Javascript中有一个执行上下文(execution context)的概念,它定义了变量或函数有权访问的其它数据,决定了他们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。 23 | 24 | **作用域链**:当访问一个变量时,解释器会首先在当前作用域查找标示符,如果没有找到,就去父作用域找,直到找到该变量的标示符或者不在父作用域中,这就是作用域链。 25 | 26 | 作用域链和原型继承查找时的区别:如果去查找一个普通对象的属性,但是在当前对象和其原型中都找不到时,会返回undefined;但查找的属性在作用域链中不存在的话就会抛出**ReferenceError**。 27 | 28 | 作用域链的顶端是全局对象,在全局环境中定义的变量就会绑定到全局对象中。 29 | 30 | #### 无嵌套函数 31 | 32 | ```js 33 | "use strict"; 34 | 35 | var foo = 1; 36 | var bar = 2; 37 | 38 | function myFunc() { 39 | 40 | var a = 1; 41 | var b = 2; 42 | var foo = 3; 43 | console.log("inside myFunc"); 44 | 45 | } 46 | 47 | console.log("outside"); 48 | myFunc(); 49 | ``` 50 | 51 | 52 | 53 | **定义时**:当myFunc被定义的时候,myFunc的标识符(identifier)就被加到了全局对象中,这个标识符所引用的是一个函数对象(myFunc function object)。 54 | 55 | 内部属性[[scope]]指向当前的作用域对象,也就是函数的标识符被创建的时候,我们所能够直接访问的那个作用域对象(即全局对象)。 56 | 57 | ![img](https://gitee.com/vr2/images/raw/master/2019-07-24-060243.png) 58 | 59 | myFunc所引用的函数对象,其本身不仅仅含有函数的代码,并且还含有指向其被创建的时候的作用域对象。 60 | 61 | **调用时**:当myFunc函数被调用的时候,一个新的作用域对象被创建了。新的作用域对象中包含myFunc函数所定义的本地变量,以及其参数(arguments)。这个新的作用域对象的父作用域对象就是在运行myFunc时能直接访问的那个作用域对象(即全局对象)。 62 | 63 | ![img](https://gitee.com/vr2/images/raw/master/2019-07-24-60244-20220510223233788.png) 64 | 65 | #### 有嵌套的函数 66 | 67 | 当函数返回没有被引用的时候,就会被垃圾回收器回收。但是对于闭包,即使外部函数返回了,函数对象仍会引用它被**创建时**的作用域对象。 68 | 69 | ```js 70 | "use strict"; 71 | function createCounter(initial) { 72 | var counter = initial; 73 | 74 | function increment(value) { 75 | counter += value; 76 | } 77 | 78 | function get() { 79 | return counter; 80 | } 81 | 82 | return { 83 | increment: increment, 84 | get: get 85 | }; 86 | } 87 | 88 | var myCounter = createCounter(100); 89 | console.log(myCounter.get()); // 返回 100 90 | 91 | myCounter.increment(5); 92 | console.log(myCounter.get()); // 返回 105 93 | ``` 94 | 95 | 当调用 createCounter(100) 时,内嵌函数increment和get都有指向createCounter(100) scope的引用。**假设**createCounter(100)没有任何返回值,那么createCounter(100) scope不再被引用,于是就可以被垃圾回收。 96 | 97 | ![img](https://gitee.com/vr2/images/raw/master/2019-07-24-060244.png) 98 | 99 | 但是createCounter(100)实际上是有返回值的,并且返回值被存储在了myCounter中,所以对象之间的引用关系如下图: 100 | 101 | ![img](https://gitee.com/vr2/images/raw/master/2019-07-24-060246.png) 102 | 103 | 即使createCounter(100)已经返回,但是其作用域仍在,并且只能被内联函数访问。可以通过调用myCounter.increment() 或 myCounter.get()来直接访问createCounter(100)的作用域。 104 | 105 | 当myCounter.increment() 或 myCounter.get()被调用时,新的作用域对象会被创建,并且该作用域对象的父作用域对象会是当前可以直接访问的作用域对象。 106 | 107 | 调用`get()`时,当执行到`return counter`时,在get()所在的作用域并没有找到对应的标示符,就会沿着作用域链往上找,直到找到变量`counter`,然后返回该变量。 108 | 109 | ![img](https://gitee.com/vr2/images/raw/master/2019-07-24-060247.png) 110 | 111 | 单独调用increment(5)时,参数value保存在当前的作用域对象。当函数要访问counter时,没有找到,于是沿着作用域链向上查找,在createCounter(100)的作用域找到了对应的标示符,increment()就会修改counter的值。除此之外,没有其他方式来修改这个变量。闭包的强大也在于此,能够存贮私有数据。 112 | 113 | ![img](https://gitee.com/vr2/images/raw/master/2019-07-24-060248.png) 114 | 115 | 创建两个函数:`myCounter1`和`myCounter2` 116 | 117 | ```text 118 | //my_script.js 119 | "use strict"; 120 | function createCounter(initial) { 121 | /* ... see the code from previous example ... */ 122 | } 123 | 124 | //-- create counter objects 125 | var myCounter1 = createCounter(100); 126 | var myCounter2 = createCounter(200); 127 | ``` 128 | 129 | 关系图如下 ![img](https://gitee.com/vr2/images/raw/master/2019-07-24-060249.png) 130 | 131 | myCounter1.increment和myCounter2.increment的函数对象拥有着一样的代码以及一样的属性值(name,length等等),但是它们的[[scope]]指向的是不一样的作用域对象。 132 | 133 | 134 | 135 | #### 参考 136 | 137 | [从作用域链谈闭包]( -------------------------------------------------------------------------------- /其他/blog/javascript/06.闭包.md: -------------------------------------------------------------------------------- 1 | ### 6.闭包 2 | 3 | #### 6.1定义: 4 | 5 | 红宝书(p178)上对于闭包的定义:**闭包是指有权访问另外一个函数作用域中的变量的函数** 6 | 7 | MDN 对闭包的定义为:**闭包是指那些能够访问自由变量的函数**。 8 | 9 | 其中**自由变量**,指在函数中使用的,但既不是函数参数`arguments`也不是函数的局部变量的变量,其实就是另外一个函数作用域中的变量。 10 | 11 | #### 6.2闭包分析 12 | 13 | ```js 14 | var scope = "global scope"; 15 | function checkscope(){ 16 | var scope = "local scope"; 17 | function f(){ 18 | return scope; 19 | } 20 | return f; 21 | } 22 | 23 | var foo = checkscope(); // foo指向函数f 24 | foo(); // 调用函数f() 25 | ``` 26 | 27 | ```js 28 | // console.dir(foo) 29 | ``` 30 | 31 | ![image-20220512202002603](https://gitee.com/vr2/images/raw/master/image-20220512202002603.png) 32 | 33 | 简要的执行过程如下: 34 | 35 | 1. 进入全局代码,创建全局执行上下文,全局执行上下文**压入执行上下文栈** 36 | 2. 全局执行**上下文初始化** 37 | 3. 执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 执行上下文被压入执行上下文栈 38 | 4. checkscope 执行**上下文初始化**,创建变量对象、作用域链、this等 39 | 5. checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出 40 | 6. 执行 f 函数,创建 f 函数执行上下文,f 执行上下文被压入执行上下文栈 41 | 7. f 执行**上下文初始化**,创建变量对象、作用域链、this等 42 | 8. f 函数执行完毕,f 函数上下文从执行上下文栈中弹出 43 | 44 | ![img](http://resource.muyiy.cn/image/2019-07-24-060256.jpg) 45 | 46 | 那么**问题**来了, 函数f 执行的时候,checkscope 函数上下文已经被销毁了,那函数f是如何获取到scope变量的呢? 47 | 48 | 函数f 执行上下文维护了一个作用域链,会指向指向`checkscope`作用域,作用域链是一个数组,结构如下。 49 | 50 | ```js 51 | fContext = { 52 | Scope: [AO, checkscopeContext.AO, globalContext.VO], 53 | } 54 | ``` 55 | 56 | 所以指向关系是当前作用域 --> `checkscope`作用域--> 全局作用域,即使 checkscopeContext 被销毁了,但是 JavaScript 依然会让 checkscopeContext.AO(活动对象) 活在内存中,f 函数依然可以通过 f 函数的作用域链找到它,这就是闭包实现的**关键**。 57 | 58 | 59 | 60 | #### 6.3总结 61 | 62 | - 使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念 63 | - 闭包 的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中 64 | - 闭包的另一个用处,是封装对象的私有属性和私有方法 65 | - **好处**:能够实现封装和缓存等; 66 | - **坏处**:就是消耗内存、不正当使用会造成内存溢出的问题 67 | 68 | **使用闭包的注意点** 69 | 70 | - 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露 71 | - 解决方法是,在退出函数之前,将不使用的局部变量全部删除 72 | 73 | 74 | 75 | #### 6.4闭包面试题 76 | 77 | ##### 6.4.1 78 | 79 | ```js 80 | var data = []; 81 | 82 | for (var i = 0; i < 3; i++) { 83 | data[i] = function () { 84 | console.log(i); 85 | }; 86 | } 87 | 88 | data[0](); 89 | data[1](); 90 | data[2](); 91 | ``` 92 | 93 | 输出都是3 94 | 95 | **分析:**循环结束后,全局执行上下文的VO是 96 | 97 | ```js 98 | globalContext = { 99 | VO: { 100 | data: [...], 101 | i: 3 102 | } 103 | } 104 | ``` 105 | 106 | 执行 data[0] 函数的时候,data[0] 函数的作用域链为: 107 | 108 | ```js 109 | data[0]Context = { 110 | Scope: [AO, globalContext.VO] 111 | } 112 | ``` 113 | 114 | 由于其自身没有i变量,就会向上查找,所有从全局上下文查找到i为3,data[1] 和 data[2] 是一样的。 115 | 116 | **解决办法** 117 | 118 | 改成闭包,方法就是`data[i]`返回一个函数,并访问变量`i` 119 | 120 | ```js 121 | var data = []; 122 | 123 | for (var i = 0; i < 3; i++) { 124 | data[i] = (function (i) { 125 | return function(){ 126 | console.log(i); 127 | } 128 | })(i); 129 | } 130 | 131 | data[0](); // 0 132 | data[1](); // 1 133 | data[2](); // 2 134 | ``` 135 | 136 | 循环结束后的全局执行上下文没有变化。 137 | 138 | 执行 data[0] 函数的时候,data[0] 函数的作用域链发生了改变: 139 | 140 | ```js 141 | data[0]Context = { 142 | Scope: [AO, 匿名函数Context.AO, globalContext.VO] 143 | } 144 | ``` 145 | 146 | 匿名函数执行上下文的AO为: 147 | 148 | ```js 149 | 匿名函数Context = { 150 | AO: { 151 | arguments: { 152 | 0: 0, 153 | length: 1 154 | }, 155 | i: 0 156 | } 157 | } 158 | ``` 159 | 160 | 因为闭包执行上下文中贮存了变量`i`,所以根据作用域链会在`globalContext.VO`中查找到变量`i`,并输出0。 161 | 162 | 163 | 164 | **解决办法** 165 | 166 | **方法1: 立即执行函数** 167 | 168 | ```js 169 | for (var i = 0; i < 3; i++) { 170 | (function(num) { 171 | setTimeout(function() { 172 | console.log(num); 173 | }, 1000); 174 | })(i); 175 | } 176 | // 0 177 | // 1 178 | // 2 179 | ``` 180 | 181 | **方法2:返回一个匿名函数** 182 | 183 | ```js 184 | var data = []; 185 | 186 | for (var i = 0; i < 3; i++) { 187 | data[i] = (function (num) { 188 | return function(){ 189 | console.log(num); 190 | } 191 | })(i); 192 | } 193 | 194 | data[0](); // 0 195 | data[1](); // 1 196 | data[2](); // 2 197 | ``` 198 | 199 | 无论是**立即执行函数**还是**返回一个匿名函数赋值**,原理上都是因为变量的按值传递,所以会将变量`i`的值复制给实参`num`,在匿名函数的内部又创建了一个用于访问`num`的匿名函数,这样每个函数都有了一个`num`的副本,互不影响了。 200 | 201 | **方法3: 使用es6 的let** 202 | 203 | 上题改动一个地方,把for循环中的`var i = 0`,改成`let i = 0`。结果是什么,为什么? 204 | 205 | ```js 206 | var data = []; 207 | 208 | for (let i = 0; i < 3; i++) { 209 | data[i] = function () { 210 | console.log(i); 211 | }; 212 | } 213 | 214 | data[0](); 215 | data[1](); 216 | data[2](); 217 | ``` 218 | 219 | 解释下**原理**: 220 | 221 | ```js 222 | var data = [];// 创建一个数组data; 223 | 224 | // 进入第一次循环 225 | { 226 | let i = 0; // 注意:因为使用let使得for循环为块级作用域 227 | // 此次 let i = 0 在这个块级作用域中,而不是在全局环境中 228 | data[0] = function() { 229 | console.log(i); 230 | }; 231 | } 232 | ``` 233 | 234 | 循环时,`let`声明`i`,所以整个块是块级作用域,那么data[0]这个函数就成了一个闭包。这里用{}表达并不符合语法,只是希望通过它来说明let存在时,这个for循环块是块级作用域,而不是全局作用域。 235 | 236 | 上面的块级作用域,就像函数作用域一样,函数执行完毕,其中的变量会被销毁,但是因为这个代码块中存在一个闭包,闭包的作用域链中引用着块级作用域,所以在闭包被调用之前,这个块级作用域内部的变量不会被销毁。 237 | 238 | ```js 239 | // 进入第二次循环 240 | { 241 | let i = 1; // 因为 let i = 1 和上面的 let i = 0 242 | // 在不同的作用域中,所以不会相互影响 243 | data[1] = function(){ 244 | console.log(i); 245 | }; 246 | } 247 | ``` 248 | 249 | 当执行`data[1]()`时,进入下面的执行环境。 250 | 251 | ```js 252 | { 253 | let i = 1; 254 | data[1] = function(){ 255 | console.log(i); 256 | }; 257 | } 258 | ``` 259 | 260 | 在上面这个执行环境中,它会首先寻找该执行环境中是否存在`i`,没有找到,就沿着作用域链继续向上到了其所在的块作用域执行环境,找到了`i = 1`,于是输出了`1`。 261 | 262 | 263 | 264 | ##### 6.4.2 265 | 266 | 代码1: 267 | 268 | ```js 269 | var scope = "global scope"; 270 | function checkscope(){ 271 | var scope = "local scope"; 272 | function f(){ 273 | return scope; 274 | } 275 | return f; 276 | } 277 | 278 | checkscope()(); 279 | ``` 280 | 281 | 代码2: 282 | 283 | ```js 284 | var scope = "global scope"; 285 | function checkscope(){ 286 | var scope = "local scope"; 287 | function f(){ 288 | return scope; 289 | } 290 | return f; 291 | } 292 | 293 | var foo = checkscope(); 294 | foo(); 295 | ``` 296 | 297 | 上面的两个代码中,`checkscope()`执行完成后,闭包`f`所引用的自由变量`scope`会被垃圾回收吗?为什么? 298 | 299 | 300 | 301 | **解答**: 302 | 303 | `checkscope()`执行完成后,代码1中自由变量特定时间之后**回收**,代码2中自由变量**不回收**。 304 | 305 | 首先要说明的是,现在主流浏览器的垃圾回收算法是**标记清除**,标记清除并非是标记执行栈的进出,而是**从根开始遍历**,也是一个找引用关系的过程,但是因为从根开始,相互引用的情况不会被计入。所以当垃圾回收开始时,从**Root**(全局对象)开始寻找这个对象的引用是否可达,如果引用链断裂,那么这个对象就会回收。 306 | 307 | 闭包中的作用域链中 parentContext.vo 是对象,被放在**堆**中,**栈**中的变量会随着执行环境进出而销毁,**堆**中需要垃圾回收,闭包内的自由变量会被分配到堆上,所以当外部方法执行完毕后,对其的引用并没有丢。 308 | 309 | 每次进入函数执行时,会重新创建可执行环境和活动对象,但函数的`[[Scope]]`是函数定义时就已经定义好的(**词法作用域规则**),不可更改。 310 | 311 | - 对于代码1: 312 | 313 | `checkscope()`执行时,将`checkscope`对象指针压入栈中,其执行环境变量如下 314 | 315 | ```js 316 | checkscopeContext:{ 317 | AO:{ 318 | arguments: 319 | scope: 320 | f: 321 | }, 322 | this, 323 | [[Scope]]:[AO, globalContext.VO] 324 | } 325 | ``` 326 | 327 | 执行完毕后**出栈**,该对象没有绑定给谁,从**Root**开始查找无法可达,此活动对象一段时间后会被回收 328 | 329 | - 对于代码2: 330 | 331 | `checkscope()`执行后,返回的是`f`对象,其执行环境变量如下 332 | 333 | ```js 334 | fContext:{ 335 | AO:{ 336 | arguments: 337 | }, 338 | this, 339 | [[Scope]]:[AO, checkscopeContext.AO, globalContext.VO] 340 | } 341 | ``` 342 | 343 | 此对象赋值给`var foo = checkscope();`,将`foo`压入栈中,`foo`指向堆中的`f`活动对象,对于`Root`来说可达,不会被回收。 344 | 345 | 如果一定要自由变量`scope`回收,那么该怎么办??? 346 | 347 | 很简单,`foo = null;`,把引用断开就可以了。 348 | 349 | ------ 350 | 351 | -------------------------------------------------------------------------------- /其他/blog/javascript/07.深入理解this.md: -------------------------------------------------------------------------------- 1 | ### 7.深入理解this 2 | 3 | `this`的五种绑定 4 | 5 | - 1、默认绑定(严格/非严格模式) 6 | - 2、隐式绑定 7 | - 3、显式绑定 8 | - 4、new绑定 9 | - 5、箭头函数绑定 10 | 11 | 其实大部分情况下可以用一句话来概括,**this总是指向调用该函数的对象**。 12 | 13 | 但是对于箭头函数并不是这样,是根据外层(函数或者全局)作用域(**词法作用域**)来决定this。 14 | 15 | 对于箭头函数的this总结如下: 16 | 17 | 1. 箭头函数不绑定this,箭头函数中的this相当于普通变量。 18 | 2. 箭头函数的this寻值行为与普通变量相同,在作用域中逐级寻找。 19 | 3. 箭头函数的this无法通过bind,call,apply来**直接**修改(可以间接修改)。 20 | 4. 改变作用域中this的指向可以改变箭头函数的this。 21 | 5. eg. `function closure(){()=>{//code }}`,在此例中,我们通过改变封包环境`closure.bind(another)()`,来改变箭头函数this的指向。 22 | 23 | 24 | 25 | #### 7.1调用位置 26 | 27 | 调用位置就是函数在代码中**被调用的位置**(而不是声明的位置)。 28 | 29 | 查找方法: 30 | 31 | - 分析调用栈:调用位置就是当前正在执行的函数的**前一个调用**中 32 | 33 | ```js 34 | function baz() { 35 | // 当前调用栈是:baz 36 | // 因此,当前调用位置是全局作用域 37 | 38 | console.log( "baz" ); 39 | bar(); // <-- bar的调用位置 40 | } 41 | 42 | function bar() { 43 | // 当前调用栈是:baz --> bar 44 | // 因此,当前调用位置在baz中 45 | 46 | console.log( "bar" ); 47 | foo(); // <-- foo的调用位置 48 | } 49 | 50 | function foo() { 51 | // 当前调用栈是:baz --> bar --> foo 52 | // 因此,当前调用位置在bar中 53 | 54 | console.log( "foo" ); 55 | } 56 | 57 | baz(); // <-- baz的调用位置 58 | ``` 59 | 60 | 61 | 62 | #### 7.2绑定规则 63 | 64 | ##### 7.2.1 默认绑定 默认绑定 65 | 66 | - **独立函数调用**,可以把默认绑定看作是无法应用其他规则时的默认规则,this指向**全局对象**。 67 | - **严格模式**下,不能将全局对象用于默认绑定,this会绑定到`undefined`。只有函数**运行**在非严格模式下,默认绑定才能绑定到全局对象。在严格模式下**调用**函数则不影响默认绑定。 68 | 69 | ```js 70 | function foo() { // 运行在严格模式下,this会绑定到undefined 71 | "use strict"; 72 | 73 | console.log( this.a ); 74 | } 75 | 76 | var a = 2; 77 | 78 | // 调用 79 | foo(); // TypeError: Cannot read property 'a' of undefined 80 | 81 | // -------------------------------------- 82 | 83 | function foo() { // 运行 84 | console.log( this.a ); 85 | } 86 | 87 | var a = 2; 88 | 89 | (function() { // 严格模式下调用函数则不影响默认绑定 90 | "use strict"; 91 | 92 | foo(); // 2 93 | })(); 94 | ``` 95 | 96 | ##### 7.2.2 隐式绑定 97 | 98 | 当函数引用有**上下文对象**时,隐式绑定规则会把函数中的this绑定到这个上下文对象。对象属性引用链中只有上一层或者说最后一层在调用中起作用。 99 | 100 | ```js 101 | function foo() { 102 | console.log( this.a ); 103 | } 104 | 105 | var obj = { 106 | a: 2, 107 | foo: foo 108 | }; 109 | 110 | obj.foo(); // 2 111 | ``` 112 | 113 | > 隐式丢失 114 | 115 | 被隐式绑定的函数特定情况下会丢失绑定对象,应用默认绑定,把this绑定到全局对象或者undefined上。 116 | 117 | ```js 118 | // 虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身。 119 | // bar()是一个不带任何修饰的函数调用,应用默认绑定。 120 | function foo() { 121 | console.log( this.a ); 122 | } 123 | 124 | var obj = { 125 | a: 2, 126 | foo: foo 127 | }; 128 | 129 | var bar = obj.foo; // 函数别名 130 | 131 | var a = "oops, global"; // a是全局对象的属性 132 | 133 | bar(); // "oops, global" 134 | ``` 135 | 136 | 参数传递就是一种隐式赋值,传入函数时也会被隐式赋值。回调函数丢失this绑定是非常常见的。 137 | 138 | ```js 139 | function foo() { 140 | console.log( this.a ); 141 | } 142 | 143 | function doFoo(fn) { 144 | // fn其实引用的是foo 145 | 146 | fn(); // <-- 调用位置! 147 | } 148 | 149 | var obj = { 150 | a: 2, 151 | foo: foo 152 | }; 153 | 154 | var a = "oops, global"; // a是全局对象的属性 155 | 156 | doFoo( obj.foo ); // "oops, global" 157 | 158 | // ---------------------------------------- 159 | 160 | // JS环境中内置的setTimeout()函数实现和下面的伪代码类似: 161 | function setTimeout(fn, delay) { 162 | // 等待delay毫秒 163 | fn(); // <-- 调用位置! 164 | } 165 | 166 | ``` 167 | 168 | ##### 7.2.3 显式绑定 169 | 170 | 通过`call(..)` 或者 `apply(..)`方法。第一个参数是一个对象,在调用函数时将这个对象绑定到this。因为直接指定this的绑定对象,称之为显示绑定。 171 | 172 | ```js 173 | function foo() { 174 | console.log( this.a ); 175 | } 176 | 177 | var obj = { 178 | a: 2 179 | }; 180 | 181 | foo.call( obj ); // 2 调用foo时强制把foo的this绑定到obj上 182 | ``` 183 | 184 | 显示绑定无法解决丢失绑定问题。 185 | 186 | 解决方案: 187 | 188 | - 1、硬绑定 189 | 190 | 创建函数bar(),并在它的内部手动调用foo.call(obj),强制把foo的this绑定到了obj。 191 | 192 | ````js 193 | function foo() { 194 | console.log( this.a ); 195 | } 196 | 197 | var obj = { 198 | a: 2 199 | }; 200 | 201 | var bar = function() { 202 | foo.call( obj ); 203 | }; 204 | 205 | bar(); // 2 206 | setTimeout( bar, 100 ); // 2 207 | 208 | // 硬绑定的bar不可能再修改它的this 209 | bar.call( window ); // 2 210 | ```` 211 | 212 | 典型应用场景是创建一个包裹函数,负责接收参数并返回值。 213 | 214 | ```js 215 | function foo(something) { 216 | console.log( this.a, something ); 217 | return this.a + something; 218 | } 219 | 220 | var obj = { 221 | a: 2 222 | }; 223 | 224 | var bar = function() { 225 | return foo.apply( obj, arguments ); 226 | }; 227 | 228 | var b = bar( 3 ); // 2 3 229 | console.log( b ); // 5 230 | ``` 231 | 232 | 创建一个可以重复使用的辅助函数。 233 | 234 | ```js 235 | function foo(something) { 236 | console.log( this.a, something ); 237 | return this.a + something; 238 | } 239 | 240 | // 简单的辅助绑定函数 241 | function bind(fn, obj) { 242 | return function() { 243 | return fn.apply( obj, arguments ); 244 | } 245 | } 246 | 247 | var obj = { 248 | a: 2 249 | }; 250 | 251 | var bar = bind( foo, obj ); 252 | 253 | var b = bar( 3 ); // 2 3 254 | console.log( b ); // 5 255 | ``` 256 | 257 | ES5内置了`Function.prototype.bind`,bind会返回一个硬绑定的新函数,用法如下。 258 | 259 | ```js 260 | function foo(something) { 261 | console.log( this.a, something ); 262 | return this.a + something; 263 | } 264 | 265 | var obj = { 266 | a: 2 267 | }; 268 | 269 | var bar = foo.bind( obj ); 270 | 271 | var b = bar( 3 ); // 2 3 272 | console.log( b ); // 5 273 | ``` 274 | 275 | - 2、API调用的“上下文” 276 | 277 | JS许多内置函数提供了一个可选参数,被称之为“上下文”(context),其作用和`bind(..)`一样,确保回调函数使用指定的this。这些函数实际上通过`call(..)`和`apply(..)`实现了显式绑定。 278 | 279 | ```js 280 | function foo(el) { 281 | console.log( el, this.id ); 282 | } 283 | 284 | var obj = { 285 | id: "awesome" 286 | } 287 | 288 | var myArray = [1, 2, 3] 289 | // 调用foo(..)时把this绑定到obj 290 | myArray.forEach( foo, obj ); 291 | // 1 awesome 2 awesome 3 awesome 292 | ``` 293 | 294 | ##### 7.2.4 new绑定 295 | 296 | - 在JS中,`构造函数`只是使用`new`操作符时被调用的`普通`函数,他们不属于某个类,也不会实例化一个类。 297 | - 包括内置对象函数(比如`Number(..)`)在内的所有函数都可以用`new`来调用,这种函数调用被称为构造函数调用。 298 | - 实际上并不存在所谓的“构造函数”,只有对于函数的“**构造调用**”。 299 | 300 | 使用`new`来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。 301 | 302 | - 1、创建(或者说构造)一个新对象。 303 | - 2、这个新对象会被执行`[[Prototype]]`连接。 304 | - 3、这个新对象会绑定到函数调用的`this`。 305 | - 4、如果函数没有返回其他对象,那么`new`表达式中的函数调用会自动返回这个新对象。 306 | 307 | 使用`new`来调用`foo(..)`时,会构造一个新对象并把它(`bar`)绑定到`foo(..)`调用中的this。 308 | 309 | ```js 310 | function foo(a) { 311 | this.a = a; 312 | } 313 | 314 | var bar = new foo(2); // bar和foo(..)调用中的this进行绑定 315 | console.log( bar.a ); // 2 316 | ``` 317 | 318 | **手写一个new实现** 319 | 320 | ```js 321 | function create() { 322 | // 创建一个空的对象 323 | var obj = new Object(), 324 | // 获得构造函数,arguments中去除第一个参数 325 | Con = [].shift.call(arguments); 326 | // 链接到原型,obj 可以访问到构造函数原型中的属性 327 | obj.__proto__ = Con.prototype; 328 | // 绑定 this 实现继承,obj 可以访问到构造函数中的属性 329 | var ret = Con.apply(obj, arguments); 330 | // 优先返回构造函数返回的对象 331 | return ret instanceof Object ? ret : obj; 332 | }; 333 | ``` 334 | 335 | 使用这个手写的new 336 | 337 | ```js 338 | function Person() {...} 339 | 340 | // 使用内置函数new 341 | var person = new Person(...) 342 | 343 | // 使用手写的new,即create 344 | var person = create(Person, ...) 345 | ``` 346 | 347 | **代码原理解析**: 348 | 349 | - 1、用`new Object()`的方式新建了一个对象`obj` 350 | - 2、取出第一个参数,就是我们要传入的构造函数。此外因为 shift 会修改原数组,所以 `arguments`会被去除第一个参数 351 | - 3、将 `obj`的原型指向构造函数,这样`obj`就可以访问到构造函数原型中的属性 352 | - 4、使用`apply`,改变构造函数`this` 的指向到新建的对象,这样 `obj`就可以访问到构造函数中的属性 353 | - 5、返回 `obj` 354 | 355 | 356 | 357 | ##### 7.2.5 箭头函数绑定 358 | 359 | 1. 箭头函数不绑定this,箭头函数中的this相当于普通变量。 360 | 2. 箭头函数的this寻值行为与普通变量相同,在作用域中逐级寻找。 361 | 3. 箭头函数的this无法通过bind,call,apply来**直接**修改(可以间接修改)。 362 | 4. 改变作用域中this的指向可以改变箭头函数的this。 363 | 5. eg. `function closure(){()=>{//code }}`,在此例中,我们通过改变封包环境`closure.bind(another)()`,来改变箭头函数this的指向。 364 | 365 | ```js 366 | /** 367 | * 非严格模式 368 | */ 369 | 370 | var name = 'window' 371 | 372 | var person1 = { 373 | name: 'person1', 374 | show1: function () { 375 | console.log(this.name) 376 | }, 377 | show2: () => console.log(this.name), 378 | show3: function () { 379 | return function () { 380 | console.log(this.name) 381 | } 382 | }, 383 | show4: function () { 384 | return () => console.log(this.name) 385 | } 386 | } 387 | var person2 = { name: 'person2' } 388 | 389 | 390 | person1.show1() // person1,隐式绑定,this指向调用者 person1 391 | person1.show1.call(person2) // person2,显式绑定,this指向 person2 392 | 393 | person1.show2() // window,箭头函数绑定,this指向外层作用域,即全局作用域 394 | person1.show2.call(person2) // window,箭头函数绑定,this指向外层作用域,即全局作用域 395 | 396 | person1.show3()() // window,默认绑定,这是一个高阶函数,调用者是window 397 | // 类似于`var func = person1.show3()` 执行`func()` 398 | person1.show3().call(person2) // person2,显式绑定,this指向 person2 399 | person1.show3.call(person2)() // window,默认绑定,调用者是window 400 | 401 | person1.show4()() // person1,箭头函数绑定,this指向外层作用域,即person1函数作用域 402 | person1.show4().call(person2) // person1,箭头函数绑定, 403 | // this指向外层作用域,即person1函数作用域 404 | person1.show4.call(person2)() // person2 405 | ``` 406 | 407 | 最后一个`person1.show4.call(person2)()`有点复杂,我们来一层一层的剥开。 408 | 409 | - 1、首先是`var func1 = person1.show4.call(person2)`,这是显式绑定,调用者是`person2`,`show4`函数指向的是`person2`。 410 | - 2、然后是`func1()`,箭头函数绑定,this指向外层作用域,即`person2`函数作用域 411 | 412 | 首先要说明的是,箭头函数绑定中,this指向外层作用域,并不一定是第一层,也不一定是第二层。 413 | 414 | 因为没有自身的this,所以只能根据作用域链往上层查找,直到找到一个绑定了this的函数作用域,并指向调用该普通函数的对象。 415 | 416 | ```js 417 | /** 418 | * 非严格模式 419 | */ 420 | 421 | var name = 'window' 422 | 423 | function Person (name) { 424 | this.name = name; 425 | this.show1 = function () { 426 | console.log(this.name) 427 | } 428 | this.show2 = () => console.log(this.name) 429 | this.show3 = function () { 430 | return function () { 431 | console.log(this.name) 432 | } 433 | } 434 | this.show4 = function () { 435 | return () => console.log(this.name) 436 | } 437 | } 438 | 439 | var personA = new Person('personA') 440 | var personB = new Person('personB') 441 | 442 | personA.show1() // personA,隐式绑定,调用者是 personA 443 | personA.show1.call(personB) // personB,显式绑定,调用者是 personB 444 | 445 | personA.show2() // personA,首先personA是new绑定,产生了新的构造函数作用域, 446 | // 然后是箭头函数绑定,this指向外层作用域,即personA函数作用域 447 | personA.show2.call(personB) // personA,同上 448 | 449 | personA.show3()() // window,默认绑定,调用者是window 450 | personA.show3().call(personB) // personB,显式绑定,调用者是personB 451 | personA.show3.call(personB)() // window,默认绑定,调用者是window 452 | 453 | personA.show4()() // personA,箭头函数绑定,this指向外层作用域,即personA函数作用域 454 | personA.show4().call(personB) // personA,箭头函数绑定,call并没有改变外层作用域, 455 | // this指向外层作用域,即personA函数作用域 456 | personA.show4.call(personB)() // personB,解析同题目1,最后是箭头函数绑定, 457 | // this指向外层作用域,即改变后的person2函数作用域 458 | ``` 459 | 460 | 题目一和题目二的区别在于题目二使用了new操作符。 461 | 462 | > 使用 new 操作符调用构造函数,实际上会经历一下4个步骤: 463 | > 464 | > 1. 创建一个新对象; 465 | > 2. 将构造函数的作用域赋给新对象(因此this就指向了这个新对象); 466 | > 3. 执行构造函数中的代码(为这个新对象添加属性); 467 | > 4. 返回新对象。 468 | 469 | #### 7.3优先级 470 | 471 | ```flow 472 | st=>start: Start 473 | e=>end: End 474 | cond1=>condition: new绑定 475 | op1=>operation: this绑定新创建的对象, 476 | var bar = new foo() 477 | 478 | cond2=>condition: 显示绑定 479 | op2=>operation: this绑定指定的对象, 480 | var bar = foo.call(obj2) 481 | 482 | cond3=>condition: 隐式绑定 483 | op3=>operation: this绑定上下文对象, 484 | var bar = obj1.foo() 485 | 486 | op4=>operation: 默认绑定 487 | op5=>operation: 函数体严格模式下绑定到undefined, 488 | 否则绑定到全局对象, 489 | var bar = foo() 490 | 491 | st->cond1 492 | cond1(yes)->op1->e 493 | cond1(no)->cond2 494 | cond2(yes)->op2->e 495 | cond2(no)->cond3 496 | cond3(yes)->op3->e 497 | cond3(no)->op4->op5->e 498 | ``` 499 | 500 | 在`new`中使用硬绑定函数的目的是预先设置函数的一些参数,这样在使用`new`进行初始化时就可以只传入其余的参数(**柯里化**)。 501 | 502 | ```js 503 | function foo(p1, p2) { 504 | this.val = p1 + p2; 505 | } 506 | 507 | // 之所以使用null是因为在本例中我们并不关心硬绑定的this是什么 508 | // 反正使用new时this会被修改 509 | var bar = foo.bind( null, "p1" ); 510 | 511 | var baz = new bar( "p2" ); 512 | 513 | baz.val; // p1p2 514 | ``` 515 | 516 | 517 | 518 | #### 7.4 绑定例外 519 | 520 | ##### 7.4.1 被忽略的this 521 | 522 | 把`null`或者`undefined`作为`this`的绑定对象传入`call`、`apply`或者`bind`,这些值在调用时会被忽略,实际应用的是默认规则。 523 | 524 | 下面两种情况下会传入`null` 525 | 526 | - 使用`apply(..)`来“展开”一个数组,并当作参数传入一个函数 527 | - `bind(..)`可以对参数进行柯里化(预先设置一些参数) 528 | 529 | ```js 530 | function foo(a, b) { 531 | console.log( "a:" + a + ",b:" + b ); 532 | } 533 | 534 | // 把数组”展开“成参数 535 | foo.apply( null, [2, 3] ); // a:2,b:3 536 | 537 | // 使用bind(..)进行柯里化 538 | var bar = foo.bind( null, 2 ); 539 | bar( 3 ); // a:2,b:3 540 | ``` 541 | 542 | 总是传入`null`来忽略this绑定可能产生一些副作用。如果某个函数确实使用了this,那默认绑定规则会把this绑定到全局对象中。 543 | 544 | > 更安全的this 545 | 546 | 安全的做法就是传入一个特殊的对象(空对象),把this绑定到这个对象不会对你的程序产生任何副作用。 547 | 548 | JS中创建一个空对象最简单的方法是**`Object.create(null)`**,这个和`{}`很像,但是并不会创建`Object.prototype`这个委托,所以比`{}`更空。 549 | 550 | ```js 551 | function foo(a, b) { 552 | console.log( "a:" + a + ",b:" + b ); 553 | } 554 | 555 | // 我们的空对象 556 | var ø = Object.create( null ); 557 | 558 | // 把数组”展开“成参数 559 | foo.apply( ø, [2, 3] ); // a:2,b:3 560 | 561 | // 使用bind(..)进行柯里化 562 | var bar = foo.bind( ø, 2 ); 563 | bar( 3 ); // a:2,b:3 564 | ``` 565 | 566 | ##### 7.4.2 间接引用 567 | 568 | 间接引用下,调用这个函数会应用默认绑定规则。间接引用最容易在**赋值**时发生。 569 | 570 | ```js 571 | // p.foo = o.foo的返回值是目标函数的引用,所以调用位置是foo()而不是p.foo()或者o.foo() 572 | function foo() { 573 | console.log( this.a ); 574 | } 575 | 576 | var a = 2; 577 | var o = { a: 3, foo: foo }; 578 | var p = { a: 4}; 579 | 580 | o.foo(); // 3 581 | (p.foo = o.foo)(); // 2 582 | ``` 583 | 584 | ##### 7.4.3软绑定 585 | 586 | - 硬绑定可以把this强制绑定到指定的对象(`new`除外),防止函数调用应用默认绑定规则。但是会降低函数的灵活性,使用**硬绑定之后就无法使用隐式绑定或者显式绑定来修改this**。 587 | - **如果给默认绑定指定一个全局对象和undefined以外的值**,那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显示绑定修改this的能力。 588 | 589 | ```js 590 | // 默认绑定规则,优先级排最后 591 | // 如果this绑定到全局对象或者undefined,那就把指定的默认对象obj绑定到this,否则不会修改this 592 | if(!Function.prototype.softBind) { 593 | Function.prototype.softBind = function(obj) { 594 | var fn = this; 595 | // 捕获所有curried参数 596 | var curried = [].slice.call( arguments, 1 ); 597 | var bound = function() { 598 | return fn.apply( 599 | (!this || this === (window || global)) ? 600 | obj : this, 601 | curried.concat.apply( curried, arguments ) 602 | ); 603 | }; 604 | bound.prototype = Object.create( fn.prototype ); 605 | return bound; 606 | }; 607 | } 608 | ``` 609 | 610 | 使用:软绑定版本的foo()可以手动将this绑定到obj2或者obj3上,但如果应用默认绑定,则会将this绑定到obj。 611 | 612 | ```js 613 | function foo() { 614 | console.log("name:" + this.name); 615 | } 616 | 617 | var obj = { name: "obj" }, 618 | obj2 = { name: "obj2" }, 619 | obj3 = { name: "obj3" }; 620 | 621 | // 默认绑定,应用软绑定,软绑定把this绑定到默认对象obj 622 | var fooOBJ = foo.softBind( obj ); 623 | fooOBJ(); // name: obj 624 | 625 | // 隐式绑定规则 626 | obj2.foo = foo.softBind( obj ); 627 | obj2.foo(); // name: obj2 <---- 看!!! 628 | 629 | // 显式绑定规则 630 | fooOBJ.call( obj3 ); // name: obj3 <---- 看!!! 631 | 632 | // 绑定丢失,应用软绑定 633 | setTimeout( obj2.foo, 10 ); // name: obj 634 | ``` 635 | 636 | 637 | 638 | #### 7.5this词法 639 | 640 | ES6新增一种特殊函数类型:箭头函数,箭头函数无法使用上述四条规则,而是根据外层(函数或者全局)作用域(**词法作用域**)来决定this。 641 | 642 | - `foo()`内部创建的箭头函数会捕获调用时`foo()`的this。由于`foo()`的this绑定到`obj1`,`bar`(引用箭头函数)的this也会绑定到`obj1`,**箭头函数的绑定无法被修改**(`new`也不行)。 643 | 644 | ```js 645 | function foo() { 646 | // 返回一个箭头函数 647 | return (a) => { 648 | // this继承自foo() 649 | console.log( this.a ); 650 | }; 651 | } 652 | 653 | var obj1 = { 654 | a: 2 655 | }; 656 | 657 | var obj2 = { 658 | a: 3 659 | } 660 | 661 | var bar = foo.call( obj1 ); 662 | bar.call( obj2 ); // 2,不是3! 663 | ``` 664 | 665 | ES6之前和箭头函数类似的模式,采用的是词法作用域取代了传统的this机制。 666 | 667 | ```js 668 | function foo() { 669 | var self = this; // lexical capture of this 670 | setTimeout( function() { 671 | console.log( self.a ); // self只是继承了foo()函数的this绑定 672 | }, 100 ); 673 | } 674 | 675 | var obj = { 676 | a: 2 677 | }; 678 | 679 | foo.call(obj); // 2 680 | ``` 681 | 682 | 代码风格统一问题:如果既有this风格的代码,还会使用 `seft = this` 或者箭头函数来否定this机制。 683 | 684 | - 只使用词法作用域并完全抛弃错误this风格的代码; 685 | - 完全采用this风格,在必要时使用`bind(..)`,尽量避免使用 `self = this` 和箭头函数。 686 | 687 | 688 | 689 | #### 7.6this面试题 690 | 691 | ```js 692 | var num = 1; 693 | var myObject = { 694 | num: 2, 695 | add: function() { 696 | this.num = 3; 697 | (function() { 698 | console.log(this.num); 699 | this.num = 4; 700 | })(); 701 | console.log(this.num); 702 | }, 703 | sub: function() { 704 | console.log(this.num) 705 | } 706 | } 707 | myObject.add(); 708 | console.log(myObject.num); 709 | console.log(num); 710 | var sub = myObject.sub; 711 | sub(); 712 | ``` 713 | 714 | 答案有两种情况,分为严格模式和非严格模式。 715 | 716 | - 严格模式下,报错。`TypeError: Cannot read property 'num' of undefined` 717 | - 非严格模式下,输出:1、3、3、4、4 718 | 719 | 解答: 720 | 721 | ```js 722 | var num = 1; 723 | var myObject = { 724 | num: 2, 725 | add: function() { 726 | this.num = 3; // 隐式绑定 修改 myObject.num = 3 727 | (function() { 728 | console.log(this.num); // 默认绑定 输出 1 729 | this.num = 4; // 默认绑定 修改 window.num = 4 730 | })(); 731 | console.log(this.num); // 隐式绑定 输出 3 732 | }, 733 | sub: function() { 734 | console.log(this.num) // 因为丢失了隐式绑定的myObject,所以使用默认绑定 输出 4 735 | } 736 | } 737 | myObject.add(); // 1 3 738 | console.log(myObject.num); // 3 739 | console.log(num); // 4 740 | var sub = myObject.sub;// 丢失了隐式绑定的myObject 741 | sub(); // 4 742 | ``` 743 | 744 | -------------------------------------------------------------------------------- /其他/code/1.vue响应式.js: -------------------------------------------------------------------------------- 1 | 2 | var uid = 0; 3 | class Dep { 4 | constructor() { 5 | this.id = uid++ 6 | // 用数组存订阅者 这个数组里面方的是Watcher的实例 7 | this.subs = [] 8 | } 9 | // 添加订阅 10 | addSub(sub) { 11 | this.subs.push(sub) 12 | } 13 | // 添加依赖 14 | depend() { 15 | // Dep.target 实际上就是我们指定的全局位置,你用window.target也行,重要是全局唯一,没有歧义就行 16 | if (Dep.target) { 17 | this.addSub(Dep.target); 18 | } 19 | } 20 | notify() { 21 | console.log('dep.notify被执行了') 22 | // 浅克隆一份 23 | const subs = this.subs.slice(); 24 | // 遍历 25 | for (let i = 0, l = subs.length; i < l; i++) { 26 | subs[i].update() 27 | } 28 | } 29 | } 30 | 31 | function parsePath(str) { 32 | var segments = str.split('.') 33 | return (obj) => { 34 | for (let i = 0; i < segments.length; i++) { 35 | obj = obj[segments[i]] 36 | } 37 | return obj; 38 | } 39 | } 40 | $vm.$watch('a.b.c.d', ()=> {}) 41 | var uuid = 0; 42 | class Watcher { 43 | constructor(target, expression, callback) { 44 | console.log('我是Watcher类的构造函数'); 45 | this.id = uuid ++; 46 | this.target = this.target; 47 | this.getter = parsePath(expression); 48 | this.callback = callback 49 | this.value = this.get() 50 | } 51 | update() { 52 | this.run() 53 | } 54 | get() { 55 | // 进入依赖收集阶段.让全局的Dep.target设置为Watcher本身,那么就是进入依赖收集阶段 56 | Dep.target = this; 57 | const obj = this.target; 58 | var value; 59 | // 只要能找就一只找 60 | try { 61 | value = this.getter(obj) 62 | } finally{ 63 | Dep.target = null 64 | } 65 | 66 | } 67 | run() { 68 | this.getAndInvoke(this.callback) 69 | } 70 | 71 | getAndInvoke(cb) { 72 | const value = this.get(); 73 | if (value !== this.value || typeof value === 'object') { 74 | const oldValue = this.value; 75 | this.value = value; 76 | cb.call(this.target, value, oldValue) 77 | } 78 | } 79 | 80 | 81 | 82 | } 83 | 84 | function defineReactive(data, key, val) { 85 | const dep = new Dep() 86 | // 子元素要进行observe,至此形成了递归。这个递归不是函数自己调用自己,而是多个函数、类循环调用。 87 | if (arguments.length === 2) { 88 | val = data[key] 89 | } 90 | let childOb = obvserve(val) 91 | Object.defineProperty(data, key, { 92 | configurable: true, 93 | enumerable: true, 94 | get() { 95 | console.log('get值',val) 96 | // 如果处于处于依赖收集阶段 97 | if (Dep.target) { 98 | dep.depend() 99 | if (childOb) { 100 | childOb.dep.depend() 101 | } 102 | } 103 | return val 104 | }, 105 | set(newValue) { 106 | if (val === newValue) return; 107 | console.log('set值',newValue) 108 | val = newValue; 109 | // 当设置了新值,这个新值也要被observe 110 | childOb = obvserve(newValue) 111 | // 发布订阅模式,通知dep 112 | dep.notify() 113 | } 114 | }) 115 | } 116 | 117 | const def = function(obj, key, value, enumerable) { 118 | Object.defineProperty(obj, key, { 119 | value: value, 120 | enumerable, 121 | writable: true, 122 | configurable: true 123 | }) 124 | } 125 | 126 | // [push,pop,shift,unshift,splice,sort,reserve ] 127 | 128 | // 得到数组的原型 129 | const arrayPrototype = Array.prototype; 130 | // 以数组为原型创造一个对象 131 | // 以Array.propotype为原型创建arrayMthods对象 132 | const arrayMethods = Object.create(arrayPrototype); 133 | const methodsNeedChange = ['push','pop','shift','unshift','splice','sort','reserve']; 134 | 135 | methodsNeedChange.forEach(methodName => { 136 | // 备份原来的方法 137 | const original = arrayPrototype[methodName]; 138 | 139 | // 定义新的方法 140 | def(arrayMethods, methodName, function() { 141 | // 恢复之前的方法(原有的功能) 142 | let result = original.apply(this, arguments) 143 | 144 | // 把类数组变数组 145 | let args = [...arguments] 146 | // 把这个数组身上的__ob__取出来,__ob__已经被添加了,为什么已经被添加了? 147 | // 因为数组肯定不是最高,比如obi.g属性是数组,obj不能是数组,第一遍历obj这个对象的第一层的时候,已经给g属性(就是一个数组)添加了__ob__属性 148 | const ob = this.__ob__; 149 | 150 | // 有三种方法 push\unshift\splice能够插入新项,现在要把插入的新项变更为observe的 151 | let inserted = []; 152 | switch(methodName) { 153 | case 'push': 154 | case 'unshift': 155 | inserted = args; 156 | break; 157 | case 'splice': 158 | // splice格式是splice(下标,数量,插入的新项) 159 | inserted = args.slice(2); 160 | break; 161 | 162 | } 163 | // 判断有没有要插入的新项,让新项变为响应式的 164 | if (inserted) { 165 | ob.observeArray(inserted) 166 | } 167 | // 判断有没有要插入的新项目 168 | console.log('a-a-a') 169 | ob.dep.notify() 170 | return result; 171 | }, false) 172 | }) 173 | 174 | // 将一个正常的object转换成为每个层级的属性都是响应式(可以被侦测的)object 175 | class Observer { // Observer单词的意思是观察 176 | constructor(value) { 177 | // 每一个Observer的实例身上,都有一个dep 每一次都有一个dep 178 | this.dep = new Dep(); 179 | console.log('构造器', value) 180 | // 给实例,构造函数的this是指向实例,不是指向类。给实例添加了一个__ob__属性,值是这次new的实例 181 | def(value, '__ob__', this, false) 182 | // Observer的目的将一个正常的object转换成为每个层级的属性都是响应式(可以被侦测的)object 183 | if (Array.isArray(value)) { 184 | // 如果是数组,要非常强行的蛮干:将这个数组的原型指向arrayMethods 185 | Object.setPrototypeOf(value, arrayMethods) 186 | // 让这个数组变成observe 187 | this.observeArray(value) 188 | } else { 189 | this.walk(value) 190 | } 191 | 192 | } 193 | // 遍历value的key属性,把每个key都设置成defineProperty 194 | walk(obj) { 195 | const keys = Object.keys(obj) 196 | /*walk方法会遍历对象的每一个属性进行defineReactive绑定*/ 197 | for (let i = 0; i < keys.length; i++) { 198 | defineReactive(obj, keys[i], obj[keys[i]]) 199 | } 200 | } 201 | // 数组的特殊遍历 202 | observeArray(arr) { 203 | for (let i = 0, l = arr.length; i < l;i++) { 204 | obvserve(arr[i]) 205 | } 206 | 207 | } 208 | } 209 | 210 | 211 | // 创建observe函数,注意函数的名字没有r,辅助函数 212 | function obvserve(value) { 213 | // 如果不是对象 214 | if (typeof value !== 'object') return 215 | let ob; // 存Observer的实例 216 | 217 | // __x__不希望和其他变量重名 218 | if (typeof value.__ob__ !== 'undefined') { 219 | ob = value.__ob__; 220 | } else { 221 | ob = new Observer(value) 222 | } 223 | return ob; 224 | } 225 | 226 | 227 | /* Observer 将一个正常的Object转换成每个层级的属性都是响应式(可以被侦测的)object */ 228 | /* 229 | observe(obj) -> 看obj身上有没有__ob__ -> new Observer(),将产生的实例添加__ob__上 230 | -> 遍历下一层属性,逐个defineReactive 231 | -> 当设置某个属性时,会触发set,里面有newValue。这个newValue也得被observe()一下 232 | */ 233 | 234 | 235 | // 4个dep 分别obj,a,b,f 236 | var obj = { 237 | a: { 238 | b: { 239 | c: 1 240 | } 241 | }, 242 | d: 20, 243 | f: [1,2] 244 | }; 245 | obvserve(obj); 246 | 247 | obj.f.push('123') 248 | console.log(obj ) 249 | 250 | // 观察数组 push、pop、shift、unshift、splice、sort、reserve 七种方法被改写 251 | 252 | /** 253 | * ### 什么是依赖? ### 254 | * - 需要用到数据的地方,称为依赖 255 | * - vue1.x,细颗粒依赖,用到数据的DOM都是依赖 256 | * - vue2.x,中等颗粒依赖,用到数据的组件就是依赖 257 | * 258 | * - 在getter中收集依赖,在setter中触发依赖 259 | * 260 | */ 261 | 262 | /** 263 | * ### Dep类和Watch类 264 | * - 把依赖收集的代码封装成一个Dep类,它专门用来管理依赖,每个Observer的实例,成员中都有一个Dep实例; 265 | * - Watcher中一个中介,数据发生变化时通过Watcher中转,通知组件 266 | * - 依赖就是Watcher。只有Watcher触发的getter才会收集依赖,哪个Watcher出发了getter,就把哪个Watcher收集到Dep中。 267 | * - Dep使用发布订阅模式,当数据发生变化时,会循环依赖列表,把所有的Watcher都通知一遍。 268 | * - 代码实现的巧妙之处:Watcher把自己设置到全局的一个指定位置,然后读取数据,因为读取了数据,所以会触发这个数据的getter。 269 | * - 在getter中就能得到当前正在读取的数据的Watcher,并把这个Watcher收集到Dep中 270 | * 271 | */ -------------------------------------------------------------------------------- /其他/code/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /其他/git rebase vs git merge.md: -------------------------------------------------------------------------------- 1 | ## git rebase vs git merge 2 | 3 | ## git rebase 4 | 5 | - rebase:基变,即改变分支的根基 6 | - 从某种程度上来说,rebase与merge可以完成类似的工作,不过二者的工作方式有着显著的差异 7 | - git rebase 是将一个分支上面的修改应用到另外一个分支上面。git rebase会修改提交历史(commit_id) 8 | 9 | ### 分支合并 10 | 11 | - git merge 和 git rebase 都可以进行分支的合并 12 | - git merge合并后保留两个分支的记录 13 | - git rebase合并后会展示一个分支的记录,另外一个分支的提交实际生成一个副本 14 | 15 | 16 | 17 | ### git merge 18 | 19 | 合并之前 20 | 21 | ![2uJDuB](https://gitee.com/vr2/images/raw/master/uPic/2uJDuB.png) 22 | 23 | 合并之后 24 | 25 | ![gA1xRQ](https://gitee.com/vr2/images/raw/master/uPic/gA1xRQ.png) 26 | 27 | ### git rebase 28 | 29 | 合并之前 30 | 31 | ![2uJDuB](https://gitee.com/vr2/images/raw/master/uPic/2uJDuB.png) 32 | 33 | 合并之后 34 | 35 | ![ZlbDax](https://gitee.com/vr2/images/raw/master/uPic/ZlbDax.png) 36 | 37 | ### rebase注意事项 38 | 39 | - rebase过程中也会出现冲突 40 | - 解决冲突后,使用git add添加,然后执行 41 | - git rebase --continue 42 | - 接下来git会继续应用余下的补丁 43 | - 任何时候都可以通过如下命令终止rebase,分支会恢复到rebase开始前到状态 44 | - git rebase --abort 45 | - 丢弃掉当前的变基,保留要合并分支的提交 46 | - git rebase --skip 47 | 48 | - 不要对master分支执行rebase,否则会引起很多问题 49 | - 一般来说,执行rebase的分支都是自己的本地分支,没有推送到远程版本库 50 | - 如果一个分支被多个人使用,不使用rebase,因为会影响多个的commit历史 -------------------------------------------------------------------------------- /其他/interview.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | 4 | 5 | ### 简历 6 | 7 | - [《大厂面试》面试官看了直呼想要的简历](https://juejin.cn/post/6844904034218803214) 8 | - [面试官到底想看什么样的简历?](https://juejin.cn/post/6844903879973273607) 9 | 10 | ### HTMl+CSS 11 | 12 | - [实现水平垂直居中(所有方法)](https://www.cnblogs.com/xiaohuochai/p/5441210.html) 13 | 14 | - margin塌陷 15 | 16 | - [flex布局](https://juejin.cn/post/6844904004728668168) 17 | 18 | - [不怕你用不上!CSS 列表项布局技巧](https://juejin.cn/post/6844903741494296590) 19 | 20 | - [【CSS】Grid 布局总结](https://juejin.cn/post/6844903777888108557) 21 | 22 | - [2019年了,你还不会CSS动画?](https://juejin.cn/post/6844903845470945294) 23 | 24 | - [你未必知道的CSS知识点(第二季)](https://juejin.cn/post/6844903960386469895) 25 | 26 | - 伪类和伪元素的区别 27 | 28 | 伪类和伪元素是用来修饰不在文档树中的部分,比如,一句话中的第一个字母,或者是列表中的第一个元素。下面分别对伪类和伪元素进行解释: 29 | 30 | 伪类用于当已有元素处于的某个状态时,为其添加对应的样式,这个状态是根据用户行为而动态变化的。比如说,当用户悬停在指定的元素时,我们可以通过:hover来描述这个元素的状态。虽然它和普通的css类相似,可以为已有的元素添加样式,但是它只有处于dom树无法描述的状态下才能为元素添加样式,所以将其称为伪类。 31 | 32 | 伪元素用于创建一些不在文档树中的元素,并为其添加样式。比如说,我们可以通过:before来在一个元素前增加一些文本,并为这些文本添加样式。虽然用户可以看到这些文本,但是这些文本实际上不在文档树中。 33 | 34 | 区别: 35 | 36 | åΩΩΩΩ伪类的操作对象是文档树中已有的元素,而伪元素则创建了一个文档树外的元素。因此,伪类与伪元素的区别在于:**有没有创建一个文档树之外的元素。** 37 | 38 | CSS3规范中的要求使用双冒号(::)表示伪元素,以此来区分伪元素和伪类,比如::before和::after等伪元素使用双冒号(::),:hover和:active等伪类使用单冒号(:)。除了一些低于IE8版本的浏览器外,大部分浏览器都支持伪元素的双冒号(::)表示方法。 39 | 40 | - BFC 41 | - AFC 42 | - GFC 43 | 44 | ### Javascript 45 | 46 | - [类型与变量](https://github.com/charmJiang/front-end-knowledge-systems/blob/main/javascript/1.%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B.md) 47 | 48 | - 基本数据类型和复杂数据类型 49 | - 基本数据类型和复杂数据类型区别 50 | - `null`和`undefined`区别 51 | - 类型识别 52 | - [`Obejct.prototype.call()`的原理](https://zhuanlan.zhihu.com/p/118793721) 53 | - `js`对象的底层数据结构是什么 [参考链接1](https://www.mdeditor.tw/jump/aHR0cHM6Ly93d3cuamlhbnNodS5jb20vcC83ZDNhYjlhMjJiMTE=) [参考链接2](https://www.mdeditor.tw/jump/aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vemhvdWx1anVuL3AvMTA4ODE2MzkuaHRtbA==) 54 | - `Symbol`类型在实际开发中的应用、可手动实现一个简单的 `Symbol` [ES6 的 Symbol 类型及使用案例](https://my.oschina.net/u/2903254/blog/818796) 55 | - 隐性转换 56 | - 转 boolean 57 | - 四则运算 58 | - [== 和 ===](https://github.com/charmJiang/front-end-knowledge-systems/blob/main/javascript/1.%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B.md#7-%E5%92%8C) 59 | - 01 + 0.2 === 0.3? [详细讲解](https://juejin.im/post/5b90e00e6fb9a05cf9080dff) 60 | - 如何让01 + 0.2 === 0.3 [详细讲解](https://juejin.im/post/5b90e00e6fb9a05cf9080dff) 61 | - `var`, `let`,`const` 的区别 62 | - `var`,`let`,`const`原理 63 | 64 | - 对象 65 | 66 | - 数组的方法 [js 数组详细操作方法及解析合集](https://juejin.cn/post/6844903614918459406) 67 | - [判断2个对象是否相等](https://github.com/mqyqingfeng/Blog/issues/41) [参考链接2](https://juejin.cn/post/6844903802298974221) 68 | 69 | - 原型链 70 | 71 | - instanceof原理 72 | - `new`原理 73 | - [实现new](https://github.com/mqyqingfeng/Blog/issues/13) 74 | - [创建对象的多种方式以及优缺点](https://github.com/mqyqingfeng/Blog/issues/15) 75 | - [继承](https://github.com/yygmind/blog/issues/7) 76 | - 原型链 77 | 78 | - 调用栈以及深入 79 | 80 | - [面试官:说说作用域和闭包吧](https://juejin.cn/post/6844904165672484871) 81 | 82 | - 变量对象 83 | 84 | - 作用域链 85 | 86 | - 执行上下文栈 87 | 88 | - [闭包](https://www.mdeditor.tw/pl/pjQR) [掌握JavaScript面试:什么是闭包?](https://juejin.cn/post/6874829017006997511) 89 | 90 | ```javascript 91 | 闭包优点: 92 | 1.可以放一个变量保存在内存中,不被js的垃圾回收机制清除 93 | 2.可以避免变量的全局污染 94 | 3.可以定义模块,将操作函数暴露在外部,细节隐藏在模块内部 95 | 96 | 闭包的缺点: 97 | 1.容易造成内存泄露 98 | 2.闭包对性能会产生负面影响,包括处理速度和内存消耗 99 | 100 | ``` 101 | 102 | ``` 103 | 104 | 105 | - `this` 106 | 107 | - [`V8`垃圾回收机制 ](https://my.oschina.net/LuckyWinty/blog/4662910) [深入解读 V8 引擎的「并发标记」技术](https://www.oschina.net/translate/v8-javascript-engine) 108 | ``` 109 | 110 | - 函数 111 | 112 | - `call`,`apply`,`bind`的实现原理 113 | - 手写`call`,`apply`,`bind` [call,apply](https://muyiy.cn/blog/3/3.3.html#call-%E5%92%8C-apply) [bind](https://github.com/mqyqingfeng/Blog/issues/12) 114 | - 类数组对象与`arguments` 115 | - 高阶函数 116 | - 防抖和节流 117 | - [请实现一个 add 函数,满足以下功能 add(1)(2)(3) // 6 add(1,2)(3) // 6](https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/134) 118 | 119 | - 正则 120 | 121 | - [正则表达式不要背](https://juejin.cn/post/6844903845227659271) 122 | 123 | - 深浅拷贝的区别 124 | 125 | - [深拷贝(多种方法)](https://muyiy.cn/blog/4/4.3.html#%E5%BC%95%E8%A8%80) 126 | - 使用广度和深度 127 | - [如何写出一个惊艳面试官的深拷贝?](https://juejin.cn/post/6844903929705136141) 128 | 129 | - 异步 130 | 131 | - 回调地狱 132 | - `promise`, `promise.all`,`promise.race`原理 133 | - 手写`promise`, `promise.all`,`promise.race` 134 | - [BAT前端经典面试问题:史上最最最详细的手写Promise教程](https://juejin.cn/post/6844903625769091079) 135 | - [9k字 | Promise/async/Generator实现原理解析](https://juejin.cn/post/6844904096525189128#heading-11) 136 | - [Promise.all处理多次reject/最多n次reject](https://juejin.cn/post/6844903617246265357) 137 | - `gennnerator`原理 138 | - 手写`gennnerator` 139 | - `async...await`原理 140 | - [Async/Await 如何通过同步的方式实现异步](https://juejin.cn/post/6844903891021086734) 141 | - 手写`async...await` 142 | - `promise.all`错误处理,避免拿不到数据 143 | - [15道ES6 Promise实战练习题,助你快速理解Promise](https://mp.weixin.qq.com/s/EGqfImjSy39UExVYFusIIw) 144 | - [字节跳动面试官:请用JS实现Ajax并发请求控制](https://juejin.cn/post/6916317088521027598?utm_source=gold_browser_extension) 145 | - [细说 async/await 相较于 Promise 的优势](https://juejin.cn/post/6844903760217505805) 146 | 147 | - 事件 148 | 149 | - 事件代理 150 | - [20k的前端是这样写事件委托的🐹](https://juejin.cn/post/6844904097372438542) 151 | 152 | - 事件循环 153 | 154 | - 浏览器的事件循环(宏任务和微任务) 155 | 156 | 157 | 158 | - 模块化 159 | 160 | - [CommonJS 和 ES6 Module 究竟有什么区别?](https://juejin.cn/post/6844904080955932680) 161 | - [聊聊什么是CommonJs和Es Module及它们的区别](https://juejin.cn/post/6938581764432461854?utm_source=gold_browser_extension) 162 | - [JavaScript 模块的循环加载](http://www.ruanyifeng.com/blog/2015/11/circular-dependency.html) 163 | 164 | - `es6` 165 | 166 | - [1.5万字概括ES6全部特性(已更新ES2020)](https://juejin.cn/post/6844903959283367950) 167 | - [近一万字的ES6语法知识点补充](https://juejin.cn/post/6844903775329583112) 168 | - [使用ES6的新特性Proxy来实现一个数据绑定实例](https://juejin.cn/post/6844903757675921421) 169 | 170 | - DOM 171 | 172 | - 正则 173 | 174 | - 匹配邮箱 175 | - 匹配手机号码 176 | 177 | - 其他 178 | 179 | - url获取参数 180 | - [面试官:前端跨页面通信,你知道哪些方法?](https://juejin.cn/post/6844903811232825357) 181 | - [面试官:请用一句话描述 try catch 能捕获到哪些 JS 异常](https://juejin.cn/post/6844904143891464200) 182 | 183 | ### Vue 184 | 185 | - 手写一个mvvm 186 | - 虚拟dom原理 187 | - [面试官问: 如何理解Virtual DOM?](https://juejin.cn/post/6844903921442422791) 188 | - [面试官系列(2): Event Bus的实现](https://juejin.im/post/6844903587043082247) 189 | - [面试官: 你了解前端路由吗?](https://juejin.cn/post/6844903589123457031) 190 | - $emit如何触发父组件函数 191 | - 如何解决vuex刷新后数据不见 192 | - [字节跳动面试官:请说一下vuex工作原理(重点就几行代码而已啦)](https://juejin.cn/post/6844904062240948231) 193 | - vue-router原理 [手写vue-router核心原理](https://juejin.cn/post/6854573222231605256) [vue-router原理剖析](https://juejin.cn/post/6844903612930326541) 194 | - [vue-router源码解析 | 1.4w字 | 多图预警 - 【上】🏆 掘金技术征文|双节特别篇](https://juejin.cn/post/6880529850159874062) 195 | - [高级前端开发者必会的34道Vue面试题系列(一)](https://juejin.cn/post/6844904097544405000) 196 | - [高级前端开发者必会的34道Vue面试题系列(二)](https://juejin.cn/post/6844904100014866439) 197 | - [高级前端开发者必会的34道Vue面试题解析(三)](https://juejin.cn/post/6844904106612654088) 198 | - [高级前端开发者必会的34道Vue面试题解析(四)](https://juejin.cn/post/6844904117400240136) 199 | - 如何理解mvvm原理? 200 | - 响应式数据的原理是什么? 201 | - vue监听数组的变化 202 | - 为何vue才用异步渲染 203 | - nextTick作用和原理 204 | - vue生命周期,父子组件生命周期顺序 205 | - ajax放在哪个生命周期 206 | - 何时需要使用beforeDestroy 207 | - vue中computed的特点 208 | - watch中的deep:true是如何实现的 209 | - vue中事件绑定原理 210 | - vue中v-html会导致哪些问题 211 | - v-if和v-show的区别 212 | - 为什么v-for,v-if不能连用 213 | - v-model中的实现原理以及如何自定义v-model 214 | - 组件中data为什么是一个函数 215 | - vue组件通信 216 | - 什么是作用域插槽 217 | - 用vnode来秒速dom结构 218 | - diff算法的时间复杂度 219 | - 简述vue中diff算法原理 220 | - v-for为什么要用key 221 | - 描述组件的渲染和更新过程 222 | - vue中模板编译原理 223 | - vue中常见的性能优化 224 | - vue中相同逻辑如何抽离 225 | - 为什么要用异步组件? 226 | - 谈谈你对keep-alive的了解 227 | - 实现hash路由和history路由 228 | - vue-Router中导航守卫有哪些 229 | - action和mutation区别 230 | - 简述vuex的工作原理 231 | - vue3.0的改进? 232 | - [Vue 开发必须知道的 36 个技巧【近1W字】](https://juejin.cn/post/6844903959266590728) 233 | - [Vue源码: 关于vm.$set()内部原理](https://juejin.cn/post/6844903830837002253) 234 | - [Vue源码探究-虚拟DOM的渲染](https://juejin.cn/post/6844903830899916807) 235 | 236 | - [让虚拟DOM和DOM-diff不再成为你的绊脚石](https://juejin.cn/post/6844903806132568072) 237 | 238 | 239 | 240 | 241 | 242 | ### React 243 | 244 | - [React 灵魂 23 问,你能答对几个?](https://mp.weixin.qq.com/s/AGY6ZV4gIuNX1HLsUbXbAg) 245 | - [React 开发必须知道的 34 个技巧【近1W字】](https://juejin.cn/post/6844903993278201870) 246 | - [如何对 React 函数式组件进行优化](https://juejin.cn/post/6844904000043614222) 247 | - [「2021」前端面试题之React篇](https://blog.csdn.net/qq_42033567/article/details/113850143) 248 | 249 | ### Axios 250 | 251 | - [Axios 源码解析](https://juejin.cn/post/6844903824583294984) 252 | - [axios封装以及前端接口处理策略](https://juejin.cn/post/6844903827779354637) 253 | 254 | ### 性能优化 255 | 256 | - [Web 架构师如何做性能优化?](https://juejin.cn/post/6898235695245197325) 257 | - [还在看那些老掉牙的性能优化文章么?这些最新性能指标了解下](https://juejin.cn/post/6850037270729359367) 258 | - [【译】面试官:请你实现一个PWA 我:😭](https://juejin.cn/post/6844904052166230030) 259 | - [拯救你的年底KPI:前端性能优化](https://juejin.cn/post/6911472693405548557?utm_source=gold_browser_extension#heading-23) 260 | - [页面性能优化办法有哪些?](https://mp.weixin.qq.com/s/Gxp0NRFa3HliD6xpDoNgSQ) 261 | - [1.5W+字的全链路前端性能优化送给你](https://mp.weixin.qq.com/s/nqUMUoa2R5fIfzJV5kBBsg) 262 | - [jsliang 求职系列 - 23 - 性能优化](https://juejin.cn/post/6898475853581402120) 263 | - [前端黑科技:美团网页首帧优化实践](https://juejin.cn/post/6844903715262955533) 264 | - [浏览器原理](https://juejin.cn/post/6844903613278470152) 265 | 266 | ### 网络 267 | 268 | - 网络基础 269 | - OSI七层以及作用 (TCP四层) 270 | - 建立一个TCP需要三次握手四次挥手,为什么需要三次握手 271 | - TCP和UDP区别 272 | - TCP是如何保证传输的可靠性 273 | - https的握手过程 274 | - https的工作原理 275 | - websocket和tcp区别 276 | 277 | - [【前端词典】进阶必备的网络基础(上)](https://juejin.cn/post/6844903789078675469) 278 | - [【前端词典】进阶必备的网络基础(下)](https://juejin.cn/post/6844903790789967879) 279 | - [图解HTTPS基本原理](https://juejin.cn/post/6844903912932147207) 280 | 281 | - [前端基础篇之HTTP协议](https://juejin.cn/post/6844903844216832007) 282 | 283 | - [深入理解https工作原理](https://mp.weixin.qq.com/s/2DqMuTYYvacH6W0339-cUA) 284 | - 简单请求和非简单的请求的区别?如何绕过options请求 285 | - [GET 和 POST请求的本质区别是什么?](https://mp.weixin.qq.com/s/mmxSgjL5dZuV70c3ehYgGQ) 286 | - [面试官问:你了解HTTP2.0吗?](https://juejin.cn/post/6844903734670000142) 287 | - [面试官问到TCP/IP怎么回答才过关](https://juejin.cn/post/6844903617732804622) 288 | - [3000字说说跨域!面试官听完之后露出了满意的笑容😎](https://juejin.cn/post/6844903972742889480) 289 | - [面试官,不要再问我三次握手和四次挥手](https://juejin.cn/post/6844903958624878606) 290 | - [关于三次握手与四次挥手面试官想考我们什么?--- 不看后悔系列](https://juejin.cn/post/6844903834708344840) 291 | - [WebSocket协议以及ws源码分析](https://juejin.cn/post/6844903850667671560) 292 | 293 | ### web安全 294 | 295 | - [【面试篇】寒冬求职之你必须要懂的Web安全](https://juejin.cn/post/6844903842635579405) 296 | 297 | - [常见六大 Web 安全攻防解析](https://mp.weixin.qq.com/s/up35Zd8EmEfDVnvxOX9EmA) 298 | - [非对称加密技术- RSA算法数学原理分析](https://juejin.cn/post/6844903511768072199) 299 | - [使用 RSA 和 AES 加密传输数据 js 到 php(前端非对称加密)](http://polande.com/?p=297) 300 | 301 | ### 数据结构和算法 302 | 303 | - [JS版数据结构第二篇(队列)](https://juejin.cn/post/6844903830220439559) 304 | 305 | - [重温数据结构系列--树](https://juejin.im/post/6844903825694785550) 306 | - [重温数据结构系列--二叉树、堆](https://juejin.cn/post/6844903827351535624) 307 | 308 | - [面试官: 100万个成员的数组取第一个和最后一个有性能差距吗?](https://juejin.cn/post/6844903938068578311) 309 | - [前端该如何准备数据结构和算法?](https://juejin.cn/post/6844903919722692621#heading-18) 310 | - [别再翻了,面试二叉树看这 11 个就够了~](https://juejin.cn/post/6844903938072772616) 311 | - [十大经典排序算法总结(JavaScript描述)](https://juejin.cn/post/6844903444365443080) 312 | - [「算法与数据结构」你可能需要的一份前端算法总结](https://juejin.cn/post/6900698814093459463) 313 | - [前端跳槽面试算法——动态规划](https://juejin.cn/post/6844903846334971918) 314 | 315 | ### 移动端H5开发 316 | 317 | - [关于移动端适配,你必须要知道的](https://juejin.cn/post/6844903845617729549) 318 | 319 | - [H5软键盘兼容方案](https://mp.weixin.qq.com/s/wO4pD1bqaS5a2onttGEmHA) 320 | - [移动端适配总结](https://juejin.cn/post/6844903734347055118) 321 | - [移动端开发一些常见问题的解决方案](https://juejin.cn/post/6897937643372937224) 322 | - [移动端网页调试](https://juejin.cn/post/6844903854111195143) 323 | - [移动端1px像素问题的根本原因 | 优质解决方](https://juejin.cn/post/6850418109522640910) 324 | - [移动端布局方案探究](https://juejin.cn/post/6844903565454999560) 325 | - [移动端常见bug汇总001](https://juejin.cn/post/6844903605502214157) 326 | - [移动端常见bug汇总002](https://juejin.cn/post/6844903605502214157) 327 | - [吃透移动端 H5 与 Hybrid|实践踩坑12种问题汇总](https://juejin.cn/post/6844904024790007815) 328 | - [Hybrid App 应用 开发中 9 个必备知识点复习(WebView / 调试 等)](https://juejin.cn/post/6844903888735174670) 329 | - [【移动端适配】用vw、vh+媒体查询打造最完美的移动端适配方案](https://juejin.cn/post/6844903857454055438) 330 | - [移动端适配解决方案-vw布局](https://www.xidaren.cn/archives/39/) 331 | - [H5页面监听Android物理返回键]() 332 | 333 | ### webpack 334 | 335 | - [jsliang 求职系列 - 31 - Webpack](https://juejin.cn/post/6901807555316547597) 336 | - [关于webpack4的14个知识点,童叟无欺](https://juejin.cn/post/6844903853905674248) 337 | - [带你深度解锁Webpack系列(进阶篇)](https://juejin.cn/post/6844904084927938567) 338 | - [Webpack 源码研究](https://juejin.cn/post/6844903903675285511) 339 | - [面试官:webpack原理都不会?](https://juejin.cn/post/6859538537830858759) 340 | - [面试官:你使用webpack时手写过loader,分离过模块吗?](https://juejin.cn/post/6844903824356802567) 341 | - [Webpack揭秘——走向高阶前端的必经之路](https://juejin.cn/post/6844903685407916039) 342 | - [📚免费的渐进式教程:Webpack4的16篇讲解和16份代码](https://juejin.cn/post/6844903748783996942) 343 | - [看完这篇,面试再也不怕被问 Webpack 热更新](https://juejin.cn/post/6844903953092591630) 344 | 345 | ### 浏览器 346 | 347 | - [浏览器原理系列10篇正式完结](https://juejin.cn/post/6844903780140466189) 348 | 349 | - [浏览器渲染原理(性能优化之如何减少重排和重绘)](https://juejin.cn/post/6844903759378645006) 350 | 351 | - [浏览器是如何解析html的?](https://juejin.cn/post/6844903745730396174) 352 | 353 | - [浏览器事件循环](https://juejin.cn/post/6844903638238756878) 354 | - [node事件循环](https://juejin.cn/post/6844904007270563848) 355 | - [浏览器事件循环和node事件循环区别](https://juejin.cn/post/6844903761949753352#heading-11) 356 | - 宏任务和微任务哪个性能更好? 357 | - [面试官问我:说说你对浏览器缓存的理解](https://juejin.cn/post/6854573208444600333) 358 | - [一文读懂前端缓存](https://juejin.cn/post/6844903747357769742) 359 | - [微信面试官不讲武德,一上来就问我Chrome原理和HTTP协议](https://juejin.cn/post/6902556452721229837) 360 | - [「导航渲染流程」你真的知道从输入URL到页面展示发生了什么吗?(内附思维导图)](https://juejin.cn/post/6902032954034225159) 361 | - [史上最详细的经典面试题 从输入URL到看到页面发生了什么?](https://juejin.cn/post/6844903832435032072) 362 | 363 | ### 项目总结 364 | 365 | - 在开发中遇到什么问题?是怎么解决的? 366 | - [前端海报生成的不同方案和优劣](https://mp.weixin.qq.com/s/eUsl-efq78dCJYy7vrROzA) 367 | - [字节跳动面试官:请你实现一个大文件上传和断点续传](https://juejin.cn/post/6844904046436843527) 368 | - [刚教完我司一个面试官OAuth 2.0为什么要先获取授权码code](https://juejin.cn/post/6884934088931688462) 369 | - [深入了解前端监控原理](https://juejin.cn/post/6899430989404045320) 370 | - [正确姿势开发vue后台管理系统](https://juejin.cn/post/6844903928761417741) 371 | - [vue中如何实现后台管理系统的权限控制](https://juejin.cn/post/6844903934545362952) 372 | - [前端大文件上传](https://juejin.cn/post/6844903860327186445) 373 | 374 | ### 设计模式 375 | 376 | - [JS设计模式一:工厂模式](https://juejin.im/post/6844903731239059470) 377 | - [jS设计模式二:单例模式](https://juejin.im/post/6844903731239075853) 378 | - [JS设计模式三:模块模式](https://juejin.im/post/6844903731239059464) 379 | - [JS设计模式四:代理模式](https://juejin.im/post/6844903731243253767) 380 | - [JS设计模式五:职责链模式](https://juejin.im/post/6844903731243253774) 381 | - [JS设计模式六:策略模式](https://juejin.im/post/6844903731243270158) 382 | - [JS设计模式七:发布-订阅模式](https://juejin.im/post/6844903731247448071) 383 | - [前端面试之JavaScript设计模式](https://juejin.cn/post/6844903861606416397) 384 | - [《JavaScript设计模式与开发实践》最全知识点汇总大全](https://juejin.cn/post/6844903751870840839) 385 | 386 | ### babel 387 | 388 | - [Babel 插件原理的理解与深入](https://github.com/frontend9/fe9-library/issues/154) 389 | - [starkwang](https://github.com/starkwang)/**[the-super-tiny-compiler-cn](https://github.com/starkwang/the-super-tiny-compiler-cn)** 这是一个只用了百来行代码的简单编译器开源项目 390 | 391 | ### Nginx 392 | 393 | - [前端必备 Nginx 配置](https://juejin.cn/post/6844903942262882318) 394 | - [前端Nginx那些事](https://juejin.cn/post/6844904102447546382) 395 | - [nginx从入门到实践][https://juejin.cn/post/6844903519003099149] 396 | - [前端的Nginx知识梳理](https://juejin.cn/post/6914160814152744973) 397 | 398 | ### 脚手架 399 | 400 | - [【中高级前端必备】手摸手教你撸一个脚手架](https://juejin.cn/post/6844903896163303438) 401 | 402 | ### Node 403 | 404 | - [面试官问你关于node的那些事(基础篇)](https://juejin.cn/post/6844904196265754638) 405 | - [面试官问你关于node的那些事(进阶篇)](https://juejin.cn/post/6844904177466867726) 406 | - [Node.js + Nginx 部署 HTTPS 服务](https://juejin.cn/post/6844903618013822984) 407 | - [想学Node.js,stream先有必要搞清楚](https://juejin.cn/post/6844903891083984910) 408 | - [Node.js的进程管理](https://juejin.cn/post/6844903757126303758) 409 | - [小马的大前端之路——Node.js初探](https://juejin.cn/post/6844903586384576520) 410 | - [如何在vscode里面调试js和node.js](https://juejin.cn/post/6844903744304316429) 411 | 412 | ### Koa2 413 | 414 | - Koa的核心机制 415 | - [逐行分析Koa中间件机制](https://juejin.cn/post/6844903790185807885) 416 | - [玩转Koa -- koa-router原理解析](https://juejin.cn/post/6844903748423122957) 417 | - 为什么要有洋葱模型 418 | - 中间的使用和理解 419 | - express,koa,egg区别 420 | - compose的实现 421 | - 某些接口允许跨域,某些不允许,如何实现?能不能使用koa2中间件的方式实现一下? 422 | - koa2中ctx.set的等价写法 423 | - koa-compose实现 424 | - [KOA2 compose 串联中间件实现(洋葱模型)](https://juejin.cn/post/6844903688985657357) 425 | 426 | ### Mysql 427 | 428 | - [MySQL常见语句汇总](https://juejin.cn/post/6844904197305925639) 429 | - [MySQL 三万字精华总结 + 面试100 问,和面试官扯皮绰绰有余(收藏系列)](https://juejin.cn/post/6850037271233331208) 430 | - [MySQL优化面试](https://juejin.cn/post/6844903779284975630) 431 | 432 | ### 面向未来 433 | 434 | 435 | 436 | ### 前端博客推荐 437 | 438 | - [木易杨](https://muyiy.cn/) 439 | - [冴羽的博客](https://github.com/mqyqingfeng/Blog) 440 | - [前端基础知识](https://juejin.cn/post/6844903891021086734) 441 | - [前端进阶之道](https://yuchengkai.cn/) 442 | - [浪里行舟](https://juejin.cn/user/4283353031252967/posts) 可以做一个回顾 443 | - [前端工匠](https://github.com/ljianshu/Blog) 可以做之前知识的补充 444 | - [nodejs-learning-guide](https://github.com/chyingp/nodejs-learning-guide) 445 | 446 | ### 面试题推荐 447 | 448 | - [达达前端个人web分享92道JavaScript面试题附加回答](https://juejin.cn/post/6913480482638266382?utm_source=gold_browser_extension) 449 | - [木易杨每天一题](https://github.com/Advanced-Frontend/Daily-Interview-Question) 450 | - [震惊!前端300基础面试题+答案、分类学习整理(良心制作)持续更新](https://juejin.cn/post/6914831351271292936) 451 | - [【周刊-2】三年大厂面试官-前端面试题(偏难)](https://juejin.cn/post/6844903821429178382) 452 | - [【周刊-1】三年大厂面试官-面试题精选及答案](https://juejin.cn/post/6844903814638600205) 453 | - [【周刊-3】三年大厂面试官-十道前端面试题(欢迎挑战)](https://juejin.cn/post/6844903842585247751) 454 | 455 | ### 其他 456 | 457 | - [前端进阶必备,github 优质资源整理分享!](https://juejin.cn/post/6844903902299553806) 458 | - [【Vue进阶】青铜选手,如何自研一套UI库?](https://juejin.cn/post/6844903862931832846) 459 | - [解密初、中、高级程序员的进化之路(前端)](https://juejin.cn/post/6844903897593544718) 460 | - [三个很不错的 Vue 资料](https://juejin.cn/post/6844903860289421326) 461 | - [if 我是前端团队 Leader,怎么制定前端协作规范?](https://juejin.cn/post/6844903897610321934) 462 | - [前端代码质量管理(一)](https://juejin.cn/post/6844903824512008205) 463 | 464 | -------------------------------------------------------------------------------- /其他/interview/DataStructureAndAlgorithm/leetcode/Array数组/desc.md: -------------------------------------------------------------------------------- 1 | # 数组 2 | 3 | 数组是在程序设计中,为了处理方便, 把具有相同类型的若干元素按有序的形式组织起来的一种形式。 4 | 5 | 首先,数组会利用 **索引** 来记录每个元素在数组中的位置,且在大多数编程语言中,索引是从 `0` 算起的。我们可以根据数组中的索引,快速访问数组中的元素。事实上,这里的索引其实就是内存地址。 6 | 7 | ![4.png](https://gitee.com/vr2/images/raw/master/images/628b6f699aa49ffcc9d3c75806457c4a1a66ffe025bb651d9f8e78b4242249b9-4.png) 8 | 9 | 其次,作为线性表的实现方式之一,数组中的元素在内存中是 **连续** 存储的,且每个元素占用相同大小的内存。 10 | 11 | 例如对于一个数组 `['oranges', 'apples', 'bananas', 'pears', 'tomatoes']`,为了方便起见,我们假设每个元素只占用一个字节,它的索引与内存地址的关系如下图所示。 12 | 13 | ![2.png](https://gitee.com/vr2/images/raw/master/images/65312e6dff56fc0c2ffad8752d6ca08da9bb7ec03211619754abd407e05147e8-2.png) 14 | 15 | 在具体的编程语言中,数组的实现方式具有一定差别。比如 C++ 和 Java 中,数组中的元素类型必须保持一致,而 Python 中则可以不同。相比之下,Python 中的数组(称为 list)具有更多的高级功能。 16 | 17 | 以下是数组的相关练习,可以帮助你进一步了解数组,快来试试吧! 18 | 19 | 20 | 21 | #### 二分查找 22 | 23 | - 如果该题目暴力解决的话需要 O(n)O(n) 的时间复杂度,但是如果二分的话则可以降低到 O(logn)O(logn) 的时间复杂度 24 | - 整体思路和普通的二分查找几乎没有区别,先设定左侧下标 left 和右侧下标 right,再计算中间下标 mid 25 | - 每次根据 nums[mid] 和 target 之间的大小进行判断,相等则直接返回下标,nums[mid] < target 则 left 右移,nums[mid] > target 则 right 左移 26 | - 查找结束如果没有相等值则返回 left,该值为插入位置 27 | - 时间复杂度:O(logn)O(logn) 28 | 29 | 二分查找的思路不难理解,但是边界条件容易出错,比如 循环结束条件中 left 和 right 的关系,更新 left 和 right 位置时要不要加 1 减 1。 30 | 31 | 下面给出两个可以直接套用的模板,记住就好了,免除边界条件出错 32 | 33 | ```java 34 | class Solution { 35 | public int searchInsert(int[] nums, int target) { 36 | int left = 0, right = nums.length - 1; // 注意 37 | while(left <= right) { // 注意 38 | int mid = (left + right) / 2; // 注意 39 | if(nums[mid] == target) { // 注意 40 | // 相关逻辑 41 | } else if(nums[mid] < target) { 42 | left = mid + 1; // 注意 43 | } else { 44 | right = mid - 1; // 注意 45 | } 46 | } 47 | // 相关返回值 48 | return 0; 49 | } 50 | } 51 | ``` 52 | 53 | ```java 54 | class Solution { 55 | public int searchInsert(int[] nums, int target) { 56 | int left = 0, right = nums.length; // 注意 57 | while(left < right) { // 注意 58 | int mid = (left + right) / 2; // 注意 59 | if(nums[mid] == target) { 60 | // 相关逻辑 61 | } else if(nums[mid] < target) { 62 | left = mid + 1; // 注意 63 | } else { 64 | right = mid; // 注意 65 | } 66 | } 67 | // 相关返回值 68 | return 0; 69 | } 70 | } 71 | 72 | ``` 73 | 74 | -------------------------------------------------------------------------------- /其他/interview/DataStructureAndAlgorithm/leetcode/README.md: -------------------------------------------------------------------------------- 1 | https://github.com/Alex660/Algorithms-and-data-structures 2 | 3 | -------------------------------------------------------------------------------- /其他/interview/DataStructureAndAlgorithm/leetcode/String字符串/1.反转.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | ## 反转字符串 4 | 5 | #### #344 [ 反转字符串](https://leetcode-cn.com/problems/reverse-string/) easy 6 | 7 | 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 8 | 9 | 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 10 | 11 | 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 12 | 13 | **示例 1:** 14 | 15 | ``` 16 | 输入:["h","e","l","l","o"] 17 | 输出:["o","l","l","e","h"] 18 | ``` 19 | 20 | **示例 2:** 21 | 22 | ``` 23 | 输入:["H","a","n","n","a","h"] 24 | 输出:["h","a","n","n","a","H"] 25 | ``` 26 | 27 | ![YKxWxW](https://gitee.com/vr2/images/raw/master/uPic/YKxWxW.png) 28 | 29 | ```js 30 | // 官方 双指针 31 | 方法一:双指针 32 | 33 | 思路与算法 34 | 35 | 对于长度为 N 的待被反转的字符数组,我们可以观察反转前后下标的变化,假设反转前字符数组为 s[0] s[1] s[2] ... s[N - 1],那么反转后字符数组为 s[N - 1] s[N - 2] ... s[0]。比较反转前后下标变化很容易得出 s[i] 的字符与 s[N - 1 - i] 的字符发生了交换的规律,因此我们可以得出如下双指针的解法: 36 | 37 | 将 left 指向字符数组首元素,right 指向字符数组尾元素。 38 | 当 left < right: 39 | 交换 s[left] 和 s[right]; 40 | left 指针右移一位,即 left = left + 1; 41 | right 指针左移一位,即 right = right - 1。 42 | 当 left >= right,反转结束,返回字符数组即可。 43 | 44 | var reverseString = function(s) { 45 | const n = s.length; 46 | for (let left = 0, right = n - 1; left < right; ++left, --right) { 47 | [s[left], s[right]] = [s[right], s[left]]; 48 | } 49 | }; 50 | 51 | 52 | 53 | // 双指针 或者 使用库函数 reverse 54 | var reverseString = function(s) { 55 | if(s.length === 0 || s === null) return s 56 | var i = 0, j = s.length - 1; 57 | while (i < j) { // ++i < --j 58 | [s[i], s[j]] = [s[j], s[i]] 59 | i++; 60 | j--; 61 | } 62 | return s 63 | // return s.reverse() 64 | }; 65 | ``` 66 | 67 | 复杂度分析 68 | 69 | - 时间复杂度:O(N),其中 N 为字符数组的长度。一共执行了 N/2 次的交换。 70 | - 空间复杂度:O(1)。只使用了常数空间来存放若干变量。 71 | 72 | 73 | 74 | #### #541.反转字符串II easy 75 | 76 | 给定一个字符串 s 和一个整数 k,你需要对从字符串开头算起的每隔 2k 个字符的前 k 个字符进行反转。 77 | 78 | - 如果剩余字符少于 k 个,则将剩余字符全部反转。 79 | - 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。 80 | 81 | **示例** 82 | 83 | ``` 84 | 输入: s = "abcdefg", k = 2 85 | 输出: "bacdfeg" 86 | ``` 87 | 88 | **提示:** 89 | 90 | 1. 该字符串只包含小写英文字母。 91 | 2. 给定字符串的长度和 `k` 在 `[1, 10000]` 范围内。 92 | 93 | ```javascript 94 | // 使用标记来确定是否需要反转 95 | /** 96 | * @param {string} s 97 | * @param {number} k 98 | * @return {string} 99 | */ 100 | var reverseStr = function(s, k) { 101 | var result = ''; 102 | var isReverse = false; 103 | for (var i = 0, len = s.length; i < len; i += k) { 104 | var str = s.slice(i, i + k); 105 | result += isReverse ? str : str.split('').reverse().join(''); 106 | isReverse = !isReverse 107 | } 108 | return result; 109 | }; 110 | ``` 111 | 112 | ```js 113 | 时间复杂度为O(N) 114 | 115 | 1 如果看等于1 就是没有翻转 直接返回 116 | 2 根据题目可以得出 位数取余 小于k的时候 就是翻转字符串,大于等于k的时候就是正常加上字符串 117 | 3 最后 因为判断余数等于零 所以最后一次可能加不上,所以返回的时候加上 118 | 119 | 120 | /** 121 | * @param {string} s 122 | * @param {number} k 123 | * @return {string} 124 | */ 125 | var reverseStr = function(s, k) { 126 | if(k == 1) return s 127 | let result = '' 128 | let temp = '' 129 | let dobulek = 2 * k 130 | for (let i = 0; i < s.length; i++) { 131 | const element = s[i]; 132 | let kyu = i % dobulek 133 | if(kyu == 0){ 134 | result += temp 135 | temp = '' 136 | } 137 | if(kyu < k){ 138 | temp = element + temp 139 | }else { 140 | temp = temp + element 141 | } 142 | } 143 | return result + temp 144 | }; 145 | 146 | ``` 147 | 148 | 149 | 150 | #### #557[反转字符串中的单词 III](https://leetcode-cn.com/problems/reverse-words-in-a-string-iii/) easy 151 | 152 | 给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。 153 | 154 | **示例:** 155 | 156 | ``` 157 | 输入:"Let's take LeetCode contest" 158 | 输出:"s'teL ekat edoCteeL tsetnoc" 159 | ``` 160 | 161 | **提示:** 162 | 163 | - 在字符串中,每个单词由单个空格分隔,并且字符串中不会有任何额外的空格。 164 | 165 | ```js 166 | /** 167 | * @param {string} s 168 | * @return {string} 169 | */ 170 | var reverseWords = function(s) { 171 | let arr=s.split("").reverse().join("") 172 | return arr.split(" ").reverse().join(" ") 173 | }; 174 | ``` 175 | 176 | ```js 177 | 官方 178 | 思路与算法 179 | 180 | 开辟一个新字符串。然后从头到尾遍历原字符串,直到找到空格为止,此时找到了一个单词,并能得到单词的起止位置。随后,根据单词的起止位置,可以将该单词逆序放到新字符串当中。如此循环多次,直到遍历完原字符串,就能得到翻转后的结果。 181 | 182 | var reverseWords = function(s) { 183 | const ret = []; 184 | const length = s.length; 185 | let i = 0; 186 | while (i < length) { 187 | let start = i; 188 | while (i < length && s.charAt(i) != ' ') { 189 | i++; 190 | } 191 | for (let p = start; p < i; p++) { 192 | ret.push(s.charAt(start + i - 1 - p)); 193 | } 194 | while (i < length && s.charAt(i) == ' ') { 195 | i++; 196 | ret.push(' '); 197 | } 198 | } 199 | return ret.join(''); 200 | }; 201 | 复杂度分析 202 | 203 | 时间复杂度:O(N)其中 为字符串的长度。原字符串中的每个字符都会在 O(1) 的时间内放入新字符串中。 204 | 空间复杂度O(N)。我们开辟了与原字符串等大的空间。 205 | ``` 206 | 207 | ```js 208 | 解题思路 209 | 210 | 双指针 211 | 内置函数 split, reverse 212 | 超easyfor循环 213 | /** 214 | * @param {string} s 215 | * @return {string} 216 | */ 217 | 218 | // 双指针 219 | var reverseWords = function(s) { 220 | let i = 0, 221 | j = s.length - 1, 222 | resL = '', 223 | resR = '', 224 | left = '', 225 | right = ''; 226 | while( i < j ){ 227 | if( s[i] == ' ' ){ 228 | resL += left + ' ' 229 | left = '' 230 | }else { 231 | left = s[i] + left; 232 | } 233 | if( s[j] == ' ' ){ 234 | resR = ' ' + right + resR 235 | right = '' 236 | }else { 237 | right += s[j] 238 | } 239 | i++; 240 | j--; 241 | } 242 | if( s[i] == ' '){ 243 | resL += left + s[i] 244 | resR = right + resR 245 | }else { 246 | resL += right + (i == j ? s[i] : '') 247 | resR = left + resR 248 | } 249 | return resL + resR 250 | } 251 | 252 | // ''分割 -> 反转 -> ''拼接 -> ' '分割 -> 反转 -> ' '拼接 253 | var reverseWords = function(s) { 254 | return s.split('').reverse().join('').split(' ').reverse().join(' ') 255 | } 256 | 257 | // easy的for循环 258 | var reverseWords = function(s) { 259 | let res = '', temp = ''; 260 | for( let i=0,len=s.length; i= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'); 62 | }; 63 | ``` 64 | 65 | 解题思路 66 | 67 | - `\W`匹配,非数字、非字母和非下划线,`[\W|_]`,加上下划线 68 | - 如果`匹配到`,左指针左移,右指针右移。只要左右指针指到不等的数字或字母,返回`false` 69 | 70 | ```javascript 71 | var isPalindrome = function(s, l = 0, r = s.length - 1) { 72 | while(l < r) { 73 | if (/[\W|_]/.test(s[l]) && ++l) continue 74 | if (/[\W|_]/.test(s[r]) && r--) continue 75 | if (s[l].toLowerCase() !== s[r].toLowerCase()) return false 76 | l++, r-- 77 | } 78 | return true 79 | }; 80 | ``` 81 | 82 | 83 | 84 | ### #680[验证回文字符串 Ⅱ](https://leetcode-cn.com/problems/valid-palindrome-ii/) 简单 85 | 86 | 给定一个非空字符串 `s`,**最多**删除一个字符。判断是否能成为回文字符串。 87 | 88 | **示例 1:** 89 | 90 | ``` 91 | 输入: "aba" 92 | 输出: True 93 | ``` 94 | 95 | **示例 2:** 96 | 97 | ``` 98 | 输入: "abca" 99 | 输出: True 100 | 解释: 你可以删除c字符。 101 | ``` 102 | 103 | 思路 104 | 105 | - 判断是否是回文串,用「双指针」的思路肯定没错: 106 | - 设置头尾指针,如果指向的字符相同,指针往中间移,继续检查。 107 | - 如果指向的字符不同,还不能判死刑,看看能否通过删除一个字符(要么删左指针指向的字符,要么删右指针指向的字符),使得剩下的字串是回文串 108 | - 写一个判断回文串的辅助函数,去判断这个删去一个字符后的子串,是否是回文串。 109 | 110 | ```js 111 | function isPali(str, l, r) { // 判断s是否回文 112 | while (l < r) { 113 | if (str[l] !== str[r]) { // 指向的字符不一样,不是回文串 114 | return false; 115 | } 116 | l++; // 指针相互逼近 117 | r--; 118 | } 119 | return true; // 始终没有不一样,返回true 120 | } 121 | var validPalindrome = function (str) { 122 | let l = 0, r = str.length - 1; // 头尾指针 123 | while (l < r) { 124 | if (str[l] !== str[r]) { // 指向的字符不一样,还不能死刑 125 | return isPali(str, l + 1, r) || isPali(str, l, r - 1); //转为判断删掉一个字符后,是否回文 126 | } 127 | l++; 128 | r--; 129 | } 130 | return true; 131 | }; 132 | ``` 133 | 134 | 135 | 136 | 方法一:贪心算法 137 | 138 | 考虑最朴素的方法:首先判断原串是否是回文串,如果是,就返回 true;如果不是,则枚举每一个位置作为被删除的位置,再判断剩下的字符串是否是回文串。这种做法的渐进时间复杂度是 O(n^2)的,会超出时间限制。 139 | 140 | 我们换一种想法。首先考虑如果不允许删除字符,如何判断一个字符串是否是回文串。常见的做法是使用双指针。定义左右指针,初始时分别指向字符串的第一个字符和最后一个字符,每次判断左右指针指向的字符是否相同,如果不相同,则不是回文串;如果相同,则将左右指针都往中间移动一位,直到左右指针相遇,则字符串是回文串。 141 | 142 | 在允许最多删除一个字符的情况下,同样可以使用双指针,通过贪心算法实现。初始化两个指针 low 和 high 分别指向字符串的第一个字符和最后一个字符。每次判断两个指针指向的字符是否相同,如果相同,则更新指针,令 low = low + 1 和 high = high - 1,然后判断更新后的指针范围内的子串是否是回文字符串。如果两个指针指向的字符不同,则两个字符中必须有一个被删除,此时我们就分成两种情况:即删除左指针对应的字符,留下子串 s[low + 1], s[low + 1], ..., s[high],或者删除右指针对应的字符,留下子串 s[low], s[low + 1], ..., s[high - 1]。当这两个子串中至少有一个是回文串时,就说明原始字符串删除一个字符之后就以成为回文串。 143 | 144 | ![cjEUBJ](https://gitee.com/vr2/images/raw/master/uPic/cjEUBJ.png) 145 | 146 | ```java 147 | class Solution { 148 | public boolean validPalindrome(String s) { 149 | int low = 0, high = s.length() - 1; 150 | while (low < high) { 151 | char c1 = s.charAt(low), c2 = s.charAt(high); 152 | if (c1 == c2) { 153 | low++; 154 | high--; 155 | } else { 156 | boolean flag1 = true, flag2 = true; 157 | for (int i = low, j = high - 1; i < j; i++, j--) { 158 | char c3 = s.charAt(i), c4 = s.charAt(j); 159 | if (c3 != c4) { 160 | flag1 = false; 161 | break; 162 | } 163 | } 164 | for (int i = low + 1, j = high; i < j; i++, j--) { 165 | char c3 = s.charAt(i), c4 = s.charAt(j); 166 | if (c3 != c4) { 167 | flag2 = false; 168 | break; 169 | } 170 | } 171 | return flag1 || flag2; 172 | } 173 | } 174 | return true; 175 | } 176 | } 177 | ``` 178 | 179 | 复杂度分析 180 | 181 | - 时间复杂度:O(n),其中 n 是字符串的长度。判断整个字符串是否是回文字符串的时间复杂度是 O(n),遇到不同字符时,判断两个子串是否是回文字符串的时间复杂度也都是 O(n)。 182 | - 空间复杂度:O(1)。只需要维护有限的常量空间。 183 | 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /其他/interview/DataStructureAndAlgorithm/leetcode/String字符串/3,前缀问题.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | 4 | 5 | #### # [14. 最长公共前缀](https://leetcode-cn.com/problems/longest-common-prefix/) 简单 6 | 7 | 8 | 9 | 编写一个函数来查找字符串数组中的最长公共前缀。 10 | 11 | 如果不存在公共前缀,返回空字符串 ""。 12 | 13 | 示例 1: 14 | 15 | ```javascript 16 | 输入: ["flower","flow","flight"] 17 | 输出: "fl" 18 | ``` 19 | 20 | 示例 2: 21 | 22 | ```javascript 23 | 输入: ["dog","racecar","car"] 24 | 输出: ""解释: 输入不存在公共前缀。 25 | 说明: 26 | ``` 27 | 28 | 所有输入只包含小写字母 a-z 。 29 | 30 | 31 | 32 | 思路 33 | 34 | - 标签:链表 35 | - 当字符串数组长度为 0 时则公共前缀为空,直接返回 36 | - 令最长公共前缀 ans 的值为第一个字符串,进行初始化 37 | - 遍历后面的字符串,依次将其与 ans 进行比较,两两找出公共前缀,最终结果即为最长公共前缀 38 | - 如果查找过程中出现了 ans 为空的情况,则公共前缀不存在直接返回 39 | - 时间复杂度:O(s),s 为所有字符串的长度之和 40 | 41 | ```javascript 42 | /** 43 | * @param {string[]} strs 44 | * @return {string} 45 | */ 46 | var longestCommonPrefix = function(strs) { 47 | if(strs.length == 0) 48 | return ""; 49 | let ans = strs[0]; 50 | for(let i =1;i= 0 && s[end] == ' ') end--; 307 | if(end < 0) return 0; 308 | let start = end; 309 | // start 拿到' '的索引 310 | while(start >= 0 && s[start] != ' ') start--; 311 | return end - start; 312 | }; 313 | ``` 314 | 315 | 316 | 317 | #### # [67. 二进制求和](https://leetcode-cn.com/problems/add-binary/) easy 318 | 319 | 给你两个二进制字符串,返回它们的和(用二进制表示)。 320 | 321 | 输入为 非空 字符串且只包含数字 1 和 0。 322 | 323 | ``` 324 | 示例 1: 325 | 326 | 输入: a = "11", b = "1" 327 | 输出: "100" 328 | 示例 2: 329 | 330 | 输入: a = "1010", b = "1011" 331 | 输出: "10101" 332 | ``` 333 | 334 | 提示: 335 | 336 | - 每个字符串仅由字符 '0' 或 '1' 组成。 337 | - 1 <= a.length, b.length <= 10^4 338 | - 字符串如果不是 "0" ,就都不含前导零。 339 | 340 | ```javascript 341 | var addBinary = function(a, b) { 342 | let res = '', carry = 0; 343 | for (let i = a.length - 1, j = b.length -1;i >=0||j>=0; i--, j-- ) { 344 | const n1 = a[i] >= 0 ? +a[i] : 0; 345 | const n2 = b[j] >= 0 ? +b[j] : 0; 346 | console.log(n1, n2); 347 | const sum = n1 + n2 + carry; 348 | res = sum % 2 + res; 349 | carry = parseInt(sum / 2 ) 350 | 351 | } 352 | carry && (res = '1' + res) 353 | return res 354 | }; 355 | ``` 356 | 357 | 358 | 359 | #### # [415. 字符串相加](https://leetcode-cn.com/problems/add-strings/) easy 360 | 361 | 给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和 362 | 363 | 提示: 364 | 365 | - num1 和num2 的长度都小于 5100 366 | - num1 和num2 都只包含数字 0-9 367 | - num1 和num2 都不包含任何前导零 368 | - 你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式 369 | 370 | ```javascript 371 | /** 372 | * @param {string} num1 373 | * @param {string} num2 374 | * @return {string} 375 | */ 376 | var addStrings = function(num1, num2) { 377 | // 小学的竖形加法 378 | let res = '';// 保存结果 379 | let carry = 0; // 每次相加的值超过2位,保存十位数 12 -> 1 380 | for (let i = num1.length -1, j = num2.length -1; i >= 0 || j>=0; i--, j--) { 381 | const n1 = num1[i] >= 0 ? +num1[i] : 0; 382 | const n2 = num2[j] >= 0 ? +num2[j] : 0; 383 | const sum = carry + n1 + n2; 384 | // 取个位数 15 ->5 每次要加上上次的值 385 | res = sum % 10 + res; 386 | // 21 -> 2 取十位数 387 | carry = sum / 10 | 0; 388 | } 389 | 390 | // 从左到右遍历的时候,最左边的两个数加起来可能大于9 391 | carry && (res = '1' + res); 392 | return res 393 | }; 394 | ``` 395 | 396 | 思路 [链接](https://leetcode-cn.com/problems/add-binary/solution/hua-jie-suan-fa-67-er-jin-zhi-qiu-he-by-guanpengch/) 397 | 398 | - 整体思路是将两个字符串较短的用 00 补齐,使得两个字符串长度一致,然后从末尾进行遍历计算,得到最终结果。 399 | 400 | - 本题解中大致思路与上述一致,但由于字符串操作原因,不确定最后的结果是否会多出一位进位,所以会有 2 种处理方式: 401 | - 第一种,在进行计算时直接拼接字符串,会得到一个反向字符,需要最后再进行翻转 402 | - 第二种,按照位置给结果字符赋值,最后如果有进位,则在前方进行字符串拼接添加进位 403 | 时间复杂度:O(n)O(n) 404 | 405 | ```javascript 406 | var addBinary = function(a, b) { 407 | let ans = ""; 408 | let ca = 0; 409 | for(let i = a.length - 1, j = b.length - 1;i >= 0 || j >= 0; i--, j--) { 410 | let sum = ca; 411 | sum += i >= 0 ? parseInt(a[i]) : 0; 412 | sum += j >= 0 ? parseInt(b[j]) : 0; 413 | ans += sum % 2; 414 | ca = Math.floor(sum / 2); 415 | } 416 | ans += ca == 1 ? ca : ""; 417 | return ans.split('').reverse().join(''); 418 | }; 419 | ``` 420 | 421 | #### # [387. 字符串中的第一个唯一字符](https://leetcode-cn.com/problems/first-unique-character-in-a-string/) easy 422 | 423 | 给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。 424 | 425 | ``` 426 | 示例: 427 | 428 | s = "leetcode" 429 | 返回 0 430 | 431 | s = "loveleetcode" 432 | 返回 2 433 | ``` 434 | 435 | 提示:你可以假定该字符串只包含小写字母。 436 | 437 | 解一: 438 | 439 | - 我们可以对方法一进行修改,使得第二次遍历的对象从字符串变为哈希映射。 440 | - 具体地,对于哈希映射中的每一个键值对,键表示一个字符,值表示它的首次出现的索引(如果该字符只出现一次)或者 -1−1(如果该字符出现多次)。当我们第一次遍历字符串时,设当前遍历到的字符为 cc,如果 cc 不在哈希映射中,我们就将 cc 与它的索引作为一个键值对加入哈希映射中,否则我们将 cc 在哈希映射中对应的值修改为 -1−1。 441 | - 在第一次遍历结束后,我们只需要再遍历一次哈希映射中的所有值,找出其中不为 -1−1 的最小值,即为第一个不重复字符的索引。如果哈希映射中的所有值均为 -1−1,我们就返回 -1−1。 442 | 443 | 444 | 445 | ```javascript 446 | /** 447 | * @param {string} s 448 | * @return {number} 449 | */ 450 | var firstUniqChar = function(s) { 451 | const map = new Map(); 452 | for (let i = 0; i < s.length; i++) { 453 | if (map.has(s[i])) { 454 | map.set(s[i], -1) 455 | } else { 456 | map.set(s[i], i) 457 | } 458 | } 459 | 460 | let first = s.length; 461 | for (let p of map.values()) { 462 | if(p != -1 && p < first) { 463 | first = p 464 | } 465 | } 466 | if (first === s.length) { 467 | return -1 468 | } 469 | return first 470 | }; 471 | 472 | // 使用原生api 473 | var firstUniqChar = function(s) { 474 | for(let i = 0; i < s.length; i++) 475 | if (s.indexOf(s[i]) === s.lastIndexOf(s[i])) 476 | return i 477 | return -1 478 | }; 479 | 480 | ``` 481 | 482 | -------------------------------------------------------------------------------- /其他/interview/DataStructureAndAlgorithm/动态规划.md: -------------------------------------------------------------------------------- 1 | 动态规划组成部分一:确定状态 2 | 3 | - 状态在动态规划中的作用属于定海神针 4 | - 简单的说,解动态规划的时候需要开一个数组,数组的每个元素`f[i]`或者`f[i][j]`代表什么 5 | - 类似于解数学题中的`X`,`Y`,`Z` 6 | - 确定状态需要两个意识: 7 | - 最后一步 8 | - 子问题 9 | 10 | 最后一步 11 | 12 | - 虽然我们不知道最优策略是什么,但是最优策略肯定是K枚硬币a1,a2,...ak面值加起来是27 13 | - 所以一定有一枚最后的硬币:ak 14 | 15 | 16 | 17 | 最后一步 18 | 19 | - 关键1: 我们不关心前面的K-1枚硬币是怎么拼出27-ak的(可能有1种拼法,可能有100种拼法),而且我们现在甚至还不知道ak和K,但是我们确定前面的硬币拼出了27-ak 20 | - 关键2:因为是最优策略,所有拼出27-ak的硬币一定要最少,否则这就不是最优策略了 21 | 22 | ![uXvGT8](https://gitee.com/vr2/images/raw/master/uPic/uXvGT8.png) 23 | 24 | 25 | 26 | 子问题 27 | 28 | - 所以我们就要求:最少用多少枚硬币可以拼出27-ak 29 | 30 | - 原问题是最少用多少枚硬币拼出27 31 | 32 | - 我们将原来问题转化成一个子问题,而且规模更小: 27-ak 33 | 34 | - 为了简化定义,我们设状态f(x)=最少用多少枚硬币拼出x 35 | 36 | - 我们还不知道最后那枚硬币ak是多少 37 | 38 | - 最后那枚硬币ak只可能是2,5,7 39 | 40 | - 如果ak是2, f(27) 应该是f(27-2) + 1 (加上最后这一枚硬币2) 41 | 42 | - 如果ak是5, f(27) 应该是f(27-5) + 1 (加上最后这一枚硬币5) 43 | 44 | - 如果ak是7, f(27) 应该是f(27-7) + 1 (加上最后这一枚硬币7) 45 | 46 | - 除此以外,没有其他的可能了 47 | 48 | ![78h86L](https://gitee.com/vr2/images/raw/master/uPic/78h86L.png) 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /其他/interview/DataStructureAndAlgorithm/动态规划1.md: -------------------------------------------------------------------------------- 1 | [121. 买卖股票的最佳时机](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/) 2 | 3 | 给定一个数组 `prices` ,它的第 `i` 个元素 `prices[i]` 表示一支给定股票第 `i` 天的价格。 4 | 5 | 你只能选择 **某一天** 买入这只股票,并选择在 **未来的某一个不同的日子** 卖出该股票。设计一个算法来计算你所能获取的最大利润。 6 | 7 | 返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 `0` 。 8 | 9 | 10 | 11 | **示例 1:** 12 | 13 | ``` 14 | 输入:[7,1,5,3,6,4] 15 | 输出:5 16 | 解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 17 | 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。 18 | ``` 19 | 20 | **示例 2:** 21 | 22 | ``` 23 | 输入:prices = [7,6,4,3,1] 24 | 输出:0 25 | 解释:在这种情况下, 没有交易完成, 所以最大利润为 0。 26 | ``` 27 | 28 | 29 | 30 | 31 | 32 | 动态规划(Dynamic Programming,DP)是一种将复杂问题分解成小问题求解的策略,但与分治算法不同的是,分治算法要求各子问题是相互独立的,而动态规划各子问题是相互关联的。 33 | 34 | > 分治,顾名思义,就是分而治之,将一个复杂的问题,分成两个或多个相似的子问题,在把子问题分成更小的子问题,直到更小的子问题可以简单求解,求解子问题,则原问题的解则为子问题解的合并。 35 | 36 | 我们使用动态规划求解问题时,需要遵循以下几个重要步骤: 37 | 38 | - 定义子问题 39 | - 实现需要反复执行解决的子子问题部分 40 | - 识别并求解出边界条件 41 | 42 | ##### 第一步:定义子问题 43 | 44 | 动态规划是将整个数组归纳考虑,假设我们已经知道了 i-1 个股票的最大利润为 dp[i-1],显然 i 个连续股票的最大利润为 dp[i-1] ,要么就是就是 prices[i] - minprice ( minprice 为前 i-1 支股票的最小值 ),在这两个数中我们取最大值 45 | 46 | ##### 第二步:实现需要反复执行解决的子子问题部分 47 | 48 | ```javascript 49 | dp[i] = Math.max(dp[i−1], prices[i] - minprice) 50 | ``` 51 | 52 | ##### 第三步:识别并求解出边界条件 53 | 54 | ```javascript 55 | dp[0]=0 56 | ``` 57 | 58 | **最后一步:把尾码翻译成代码,处理一些边界情况** 59 | 60 | 因为我们在计算 dp[i] 的时候,只关心 dp[i-1] 与 prices[i],因此不用把整个 dp 数组保存下来,只需设置一个 max 保存 dp[i-1] 就好了。 61 | 62 | **代码实现(优化):** 63 | 64 | ```javascript 65 | let maxProfit = function(prices) { 66 | let max = 0, minprice = prices[0] 67 | for(let i = 1; i < prices.length; i++) { 68 | minprice = Math.min(prices[i], minprice) 69 | max = Math.max(max, prices[i] - minprice) 70 | } 71 | return max 72 | } 73 | ``` 74 | 75 | ##### 复杂度分析: 76 | 77 | - 时间复杂度:O(n) 78 | - 空间复杂度:O(1) -------------------------------------------------------------------------------- /其他/interview/MicFront/single-spa源码分析.md: -------------------------------------------------------------------------------- 1 | ## Single-spa源码分析 2 | 3 | [single-spa源码仓库](https://github.com/single-spa/single-spa) 4 | 5 | -------------------------------------------------------------------------------- /其他/interview/MicFront/微前端的介绍和single-spa应用.md: -------------------------------------------------------------------------------- 1 | ```js 2 | function render(){ 3 | instance = new Vue({ 4 | router, 5 | render: h => h(App) 6 | }).$mount('#app') // 这里是去挂载到自己的html中--基座会拿到这个挂载后的html,将其插入进去 7 | } 8 | 9 | 10 | if(window.__POWERED_BY_QIANKUN__){ // 如果主应用上有挂载,就不独立运行 (动态添加publicPath) 11 | __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; 12 | } 13 | 14 | if(!window.__POWERED_BY_QIANKUN__){ // 默认独立运行 15 | render() 16 | } 17 | ``` 18 | 19 | 20 | 21 | // 子应用是react 要重新配置webpack(yarn add react-app-rewired) 22 | 23 | ![c2eRNJ](https://gitee.com/vr2/images/raw/master/uPic/c2eRNJ.png) 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ## 微前端 32 | 33 | ### 1.业务背景介绍 34 | 35 | ### 2.技术选型 36 | 37 | ![image-20210227152944452](/Users/tom/Library/Application Support/typora-user-images/image-20210227152944452.png) 38 | 39 | 40 | 41 | ![inaGCk](https://gitee.com/vr2/images/raw/master/uPic/inaGCk.png) 42 | 43 | #### 2.1、巨石应用 44 | 45 | 巨石应用也就是 SPA/MPA 的技术架构。这里想单独提一下,针对产品体验的场景,一个简单的 SPA 应用、肯定是能够给到一个整体的系统体验,并且能够管控系统的技术复杂度。但如果系统越来越臃肿,就会遇到上面到的技术纬度的问题。 46 | 47 | #### 2.2、iframe 48 | 49 | iframe在微前端方案流行前,是一个比较好的方案。不管是一些二方或是三方的接入,它都能够很好地满足需求。 50 | 51 | ##### 优点: 52 | 53 | - 实现简单 54 | - 天然具备隔离性 55 | 56 | ##### 缺点: 57 | 58 | - 用户体验不好:iframe 如果不去做一些特殊处理,嵌入的页面双滚动条问题、路由无法同步问题、包括 iframe 内部有一些弹出遮罩交互 59 | - 主页面和 iframe 共享最大允许的 HTTP 链接数。 60 | - iframe 阻塞主页面加载。(每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程) 61 | - url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。 62 | - UI 不同步,DOM 结构不共享。想象一下屏幕右下角 1/4 的 iframe 里来一个带遮罩层的弹框,同时我们要求这个弹框要浏览器居中显示,还要浏览器 resize 时自动居中.(在iframe创建一个水平垂直居中的弹窗无法实现) 63 | 64 | #### 2.3、框架组件 65 | 66 | ​ 将常用的逻辑封装成一个组件,以npm包的方式引入。 67 | 68 | ​ 这种方式存在的问题比较明显的,它本质上并没有去解决技术架构的升级、用户体验的优化。比如通用 layout 组件,可能保证了系统拥有一个统一的交互视觉 layout 层。虽然说看上去两个系统长一样,但它实际上还是不同的系统,跨系统操作还是需要跳转。 69 | 70 | ![0M31Br](https://gitee.com/vr2/images/raw/master/uPic/0M31Br.png) 71 | 72 | ​ 73 | 74 | #### 微前端的设计理念 75 | 76 | - 第一与技术无关 77 | - 第二设计理念就是开发体验一致,今天技术架构上引入一套微前端方案,并不会意味着要有很多新概念去学习。 78 | - 第三点其实是微前端方案中核心的一个能力 - 路由能力 79 | - 80 | 81 | ### 介绍 82 | 83 | #### 什么是微前端? 84 | 85 | > [微前端](https://www.thoughtworks.com/radar/techniques/micro-frontends)由ThoughtWorks 2016年提出,将后端微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。 86 | 87 | > 微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。换句话说微前端就是将不同的功能按照不同的维度拆分成多个子应用。通过主应用来加载这些子应用。 88 | 89 | 微前端的核心在于**拆**,拆完后再**合**。 90 | 91 | OGicDm 92 | 93 | #### 为什么要去使用微前端? 94 | 95 | - **技术栈无关**:主框架不限制接入应用的技术栈,微应用具备完全自主权 96 | - **独立开发、独立部署**:微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新 97 | - **增量升级**:在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略 98 | - **独立运行时**:每个微应用之间状态隔离,运行时状态不共享。 99 | 100 | > 微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用([Frontend Monolith](https://www.youtube.com/watch?v=pU1gXA0rfwc))后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。 101 | 102 | 103 | 104 | #### 怎样落地微前端? 105 | 106 | ##### Single-spa 107 | 108 | `single-spa` 一个基于JavaScript的 **微前端** 框架,他可以用于构建可共存的微前端应用,每个前端应用都可以用自己的框架编写,完美支持 Vue React Angular。可以实现 **服务注册** **事件监听** **子父组件通信** 等功能。 109 | 110 | ###### **主项目(基座)搭建:** 111 | 112 | 主应用注册子应用的路由: 113 | 114 | ```javascript 115 | // 主应用的route/index.js 116 | { 117 | path: '/vue', 118 | name: 'vue', 119 | } 120 | ``` 121 | 122 | ```vue 123 | 137 | 159 | ``` 160 | 161 | ###### **主应用注册子应用** 162 | 163 | `registerApplication`注册子项目 164 | 165 | - appName: 子项目名称 166 | - applicationOrLoadingFn: 子项目注册函数,用户需要返回 single-spa 的生命周期对象 167 | - activityFn: 回调函数入参 `location` 对象,可以写自定义匹配路由加载规则 168 | - customProps:传递给子应用的对象 169 | 170 | `start`启动函数 171 | 172 | ```javascript 173 | // main.js 174 | import './single-spa-config.js' 175 | ``` 176 | 177 | 这里采用远程加载`manifest.json` 178 | 179 | ```javascript 180 | import { registerApplication, start } from "single-spa"; 181 | import axios from 'axios'; 182 | 183 | 184 | /** 185 | * loadScript:一个promise同步方法。可以代替创建一个script标签,然后加载服务 186 | */ 187 | const loadScript = async (url) => { 188 | await new Promise((resolve, reject) => { 189 | const script = document.createElement("script"); 190 | script.src = url; 191 | script.onload = resolve; 192 | script.setAttribute('name', 'main-application'); 193 | script.onerror = reject; 194 | // const firstScript = document.getElementsByTagName("script")[0]; 195 | // firstScript.parentNode.insertBefore(script, firstScript); 196 | document.head.appendChild(script); 197 | }); 198 | }; 199 | 200 | 201 | /* 202 | * getManifest:远程加载manifest.json 文件,解析需要加载的js 203 | * */ 204 | const getManifest = (url, bundle) => new Promise(async (resolve) => { 205 | const { data } = await axios.get(url); 206 | const { entrypoints, publicPath } = data; 207 | const assets = entrypoints[bundle].assets; 208 | for (let i = 0; i < assets.length; i++) { 209 | await loadScript(publicPath + assets[i]).then(() => { 210 | if (i === assets.length - 1) { 211 | resolve() 212 | } 213 | }) 214 | } 215 | }); 216 | 217 | // 注册应用 218 | registerApplication( 219 | 'singleDemo', 220 | async () => { 221 | // 注册用函数, 222 | let singleVue = null 223 | await getManifest('http://127.0.0.1:3000/manifest.json', 'app').then(() => { 224 | singleVue = window.singleVue; 225 | }); 226 | return singleVue; 227 | }, 228 | // 当路由满足条件时(返回true),激活(挂载)子应用 229 | location => location.pathname.startsWith('/vue') 230 | ) 231 | 232 | // 在调用 start 之前, 应用会被加载, 但不会初始化,挂载或卸载。 233 | // start 的原因是让你更好的控制你单页应用的性能。 234 | start() 235 | ``` 236 | 237 | ###### **子项目(应用搭建):**(vue) 238 | 239 | ```shell 240 | npm install single-spa-vue --save -d 241 | ``` 242 | 243 | 在`main.js`中引入 `single-spa-vue`,传入Vue对象和vue.js挂载参数,就可以实现注册。它会返回一个对象,里面有single-spa 需要的生命周期函数。使用`export`导出即可 244 | 245 | ```javascript 246 | import singleSpaVue from "single-spa-vue"; 247 | import Vue from 'vue' 248 | 249 | const vueOptions = { 250 | el: "#vue", 251 | router, 252 | store, 253 | render: h => h(App) 254 | }; 255 | 256 | // singleSpaVue包装一个vue微前端服务对象 257 | const vueLifecycles = singleSpaVue({ 258 | Vue, 259 | appOptions: vueOptions 260 | }); 261 | 262 | // 导出生命周期对象 263 | export const bootstrap = vueLifecycles.bootstrap; // 启动时 264 | export const mount = vueLifecycles.mount; // 挂载时 265 | export const unmount = vueLifecycles.unmount; // 卸载时 266 | 267 | export default vueLifecycles; 268 | 269 | ``` 270 | 271 | ###### webpack的处理 272 | 273 | 只是导出了,还需要挂载到`window`。 274 | 275 | 在项目目录下新建 `vue.config.js`, 修改我们的webpack配置。我们修改webpack `output`内的 `library` 和 `libraryTarget` 字段。 276 | 277 | - output.library: 导出的对象名 278 | - output.libraryTarget: 导出后要挂载到哪里 279 | 280 | 同时,因为我们是远程调用,还需要设置 `publicPath` 字段为你的真实服务地址。否则加载子`chunk`时,会去**当前浏览器域名的根路径**寻找,有404问题。 因为我们本地的服务启动是`localhost:3000`,所以我们就设置 `//localhost:3000`。 281 | 282 | ```javascript 283 | module.exports = { 284 | publicPath: "//localhost:3000/", 285 | // css在所有环境下,都不单独打包为文件。这样是为了保证最小引入(只引入js) 286 | css: { 287 | extract: false 288 | }, 289 | configureWebpack: { 290 | devtool: 'none', // 不打包sourcemap 291 | output: { 292 | library: "singleVue", // 导出名称 293 | libraryTarget: "window", //挂载目标 294 | } 295 | }, 296 | devServer: { 297 | contentBase: './', 298 | compress: true, 299 | } 300 | }; 301 | 302 | ``` 303 | 304 | ###### **样式隔离** 305 | 306 | 样式隔离这块,我们使用`postcss`的一个插件:postcss-selector-namespace。 **他会把你项目里的所有css都会添加一个类名前缀。这样就可以实现命名空间隔离**。 307 | 308 | 首先,我们先安装这个插件:`npm install postcss-selector-namespace --save -d` 309 | 310 | 项目目录下新建 `postcss.config.js`,使用插件: 311 | 312 | ```javascript 313 | // postcss.config.js 314 | 315 | module.exports = { 316 | plugins: { 317 | // postcss-selector-namespace: 给所有css添加统一前缀,然后父项目添加命名空间 318 | 'postcss-selector-namespace': { 319 | namespace(css) { 320 | // element-ui的样式不需要添加命名空间 321 | if (css.includes('element-variables.scss')) return ''; 322 | return '.single-spa-vue' // 返回要添加的类名 323 | } 324 | }, 325 | } 326 | } 327 | 328 | ``` 329 | 330 | 在父应用要挂载的区块,添加我们的命名空间 331 | 332 | ###### **子项目独立运行** 333 | 334 | 大家可能会发现,我们的子服务现在是无法独立运行的,现在我们改造为可以独立 + 集成双模式运行。 335 | 336 | `single-spa` 有个属性,叫做 `window.singleSpaNavigate`。如果为true,代表就是single-spa模式。如果false,就可以独立渲染。 337 | 338 | 我们改造一下子项目的`main.js` : 339 | 340 | ```javascript 341 | // main.js 342 | import Vue from 'vue' 343 | import App from './App.vue' 344 | import router from './router' 345 | import store from './store' 346 | import singleSpaVue from "single-spa-vue"; 347 | 348 | Vue.config.productionTip = false 349 | 350 | const vueOptions = { 351 | el: "#vue", 352 | router, 353 | store, 354 | render: h => h(App) 355 | }; 356 | 357 | // 判断当前页面使用singleSpa应用,不是就渲染 358 | if (!window.singleSpaNavigate) { 359 | delete vueOptions.el; 360 | new Vue(vueOptions).$mount('#app'); 361 | } 362 | 363 | // singleSpaVue包装一个vue微前端服务对象 364 | const vueLifecycles = singleSpaVue({ 365 | Vue, 366 | appOptions: vueOptions 367 | }); 368 | 369 | export const bootstrap = vueLifecycles.bootstrap; // 启动时 370 | export const mount = vueLifecycles.mount; // 挂载时 371 | export const unmount = vueLifecycles.unmount; // 卸载时 372 | 373 | export default vueLifecycles; 374 | 375 | ``` 376 | 377 | 这样,我们就可以独立访问子服务的 `index.html` 。不要忘记在`public/index.html`里面添加命名空间,否则会丢失样式。 378 | 379 | ```javascript 380 |
381 |
382 |
383 | ``` 384 | 385 | ###### 远程加载 386 | 387 | 在这里,我们的远程加载使用的是`async await`构建一个同步执行任务。 388 | 389 | 创建一个`script`标签,等`script`加载后,返回`script`加载到`window`上面的对象。 390 | 391 | ```javascript 392 | /* 393 | * loadScript:一个promise同步方法。可以代替创建一个script标签,然后加载服务 394 | * */ 395 | const loadScript = async (url) => { 396 | return new Promise((resolve, reject) => { 397 | const script = document.createElement('script'); 398 | script.src = url; 399 | script.onload = resolve; 400 | script.onerror = reject; 401 | const firstScript = document.getElementsByTagName('script')[0]; 402 | firstScript.parentNode.insertBefore(script, firstScript); 403 | }); 404 | }; 405 | 406 | ``` 407 | 408 | ##### vue 和 react/angular 挂载的区别 409 | 410 | Vue 2.x的dom挂载,采取的是 **覆盖Dom挂载** 的方式。例如,组件要挂载到`#app`上,那么它会用组件覆盖掉`#app`元素。 411 | 412 | 但是React/Angular不同,它们的挂载方式是在目标挂载元素的内部`添加元素`,而不是直接覆盖掉。 例如组件要挂载到`#app`上,那么他会在`#app`内部挂载组件,`#app`还存在。 413 | 414 | 这样就造成了一个问题,当我从 vue子项目 => react项目 => vue子项目时,就会找不到要挂载的dom元素,从而抛出错误。 415 | 416 | 解决这个问题的方案是,让 **vue项目组件的根元素类名/ID名和要挂载的元素一致** 就可以。 417 | 418 | 例如我们要挂载到 `#app` 这个dom上,那么我们子项目内部的app.vue,最顶部的dom元素id名也应该叫 `#app`。 419 | 420 | ```vue 421 | 430 | 431 | ``` 432 | 433 | ###### manifest 自动加载 bundle和chunk.vendor 434 | 435 | 在上面父项目加载子项目的代码中,我们可以看到。我们要注册一个子服务,需要一次性加载2个JS文件。**如果需要加载的JS更多,甚至生产环境的 bundle 有唯一hash,** 那我们还能写死文件名和列表吗? 436 | 437 | ```javascript 438 | singleSpa.registerApplication( 439 | 'singleVue', 440 | async () => { 441 | await runScript('http://127.0.0.1:3000/js/chunk-vendors.js'); // 写死的文件列表 442 | await runScript('http://127.0.0.1:3000/js/app.js'); 443 | return window.singleVue; 444 | }, 445 | location => location.pathname.startsWith('/vue') 446 | ); 447 | 448 | ``` 449 | 450 | 我们的实现思路,就是让子项目使用 `stats-webpack-plugin` 插件,每次打包后都输出一个 只包含重要信息的`manifest.json`文件。父项目先ajax 请求 这个json文件,从中读取出需要加载的js目录,然后同步加载。 451 | 452 | ![image-20210227212611877](/Users/tom/Library/Application Support/typora-user-images/image-20210227212611877.png) 453 | 454 | ###### stats-webpack-plugin 455 | 456 | 这里就不得不提到这个webpack plugin了。它可以在你每次打包结束后,都生成一个`manifest.json` 文件,里面存放着本次打包的 `public_path` `bundle list` `chunk list` 文件大小依赖等等信息。 457 | 458 | ```json 459 | { 460 | "errors": [], 461 | "warnings": [], 462 | "version": "4.41.4", 463 | "hash": "d0601ce74a7b9821751e", 464 | "publicPath": "//localhost:3000/", 465 | "outputPath": "/Users/janlay/juejin-single/vue-chlid/dist", 466 | "entrypoints": { // 只使用这个字段 467 | "app": { 468 | "chunks": [ 469 | "chunk-vendors", 470 | "app" 471 | ], 472 | "assets": [ 473 | "js/chunk-vendors.75fba470.js", 474 | "js/app.3249afbe.js" 475 | ], 476 | "children": {}, 477 | "childAssets": {} 478 | } 479 | ... ... 480 | } 481 | 482 | 483 | ``` 484 | 485 | ```shell 486 | npm install stats-webpack-plugin --save -d 487 | ``` 488 | 489 | ```javascript 490 | { 491 | configureWebpack: { 492 | devtool: 'none', 493 | output: { 494 | library: "singleVue", 495 | libraryTarget: "window", 496 | }, 497 | /**** 添加开头 ****/ 498 | plugins: [ 499 | new StatsPlugin('manifest.json', { 500 | chunkModules: false, 501 | entrypoints: true, 502 | source: false, 503 | chunks: false, 504 | modules: false, 505 | assets: false, 506 | children: false, 507 | exclude: [/node_modules/] 508 | }), 509 | ] 510 | /**** 添加结尾 ****/ 511 | } 512 | } 513 | 514 | ``` 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | **参考文章:** 523 | 524 | - [微前端连载 3/7:淘宝大型应用架构中的微前端方案](https://juejin.cn/post/6844904202389438478#heading-18) 525 | - [Single-Spa + Vue Cli 微前端落地指南 + 视频 (项目隔离远程加载,自动引入)](https://juejin.cn/post/6844904025565954055#heading-14) 526 | - [Single-Spa微前端落地(含nginx部署)](https://juejin.cn/post/6844904158349246477#heading-12) 527 | - [微前端框架 之 single-spa 从入门到精通](https://juejin.cn/post/6862661545592111111) 528 | 529 | -------------------------------------------------------------------------------- /其他/interview/Node/目录.md: -------------------------------------------------------------------------------- 1 | ## Node 2 | 3 | - node单线程容易崩溃,怎么维护服务的 4 | - pm2内部机制了解吗 5 | - cluster了解多少 6 | - 业务线如何用端口号区分(nginx http-proxy) 7 | - 用node如何实现一个带压缩和缓存的http-server? 8 | 9 | ## Koa 10 | 11 | - Koa的核心机制 12 | - 为什么要有洋葱模型 13 | - 中间的使用和理解 14 | - express,koa,egg区别 15 | - compose的实现 16 | - 某些接口允许跨域,某些不允许,如何实现?能不能使用koa2中间件的方式实现一下? 17 | - koa2中ctx.set的等价写法 18 | - koa-compose实现 19 | - -------------------------------------------------------------------------------- /其他/interview/browser/跨域.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | 4 | 5 | ## 跨域的几种形式 6 | 7 | 跨域造成的原因:浏览器的同源策略 8 | 9 | ### 同源策略 10 | 11 | 所谓同源是指,域名,协议,端口相同。 非同源的客户端脚本在没有明确授权的情况下,不能读写对方资源,在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。 12 | 13 | 14 | 15 | ### cors 16 | 17 | CORS:全称"跨域资源共享"(Cross-origin resource sharing)。 18 | 19 | cors是一种机制,它使用额外的 HTTP 头部告诉浏览器可以让一个web应用进行跨域资源请求。 20 | 21 | 浏览器将cors请求分为两类:简单请求和非简单请求 22 | 23 | #### 简单请求 24 | 25 | 一个请求同时满足以下所有的条件,则该请求为“简单请求” 26 | 27 | - 请求方法是以下之一 28 | - `HEAD` 29 | - `GET` 30 | - `POST` 31 | - HTTP的头信息不超出以下几种字段: 32 | - `Accept` 33 | - `Accept-Language` 34 | - `Content-Language` 35 | - `Last-Event-ID` 36 | - `Content-Type` 37 | - `Content-Type`只限于三个值 38 | - `application/x-www-form-urlencoded` 39 | - `mutipart/form-data` 40 | - `text/plain` 41 | 42 | 对于简单请求,浏览器直接发出`cors`请求。会在请求头信息中,增加一个`origin`字段。 43 | 44 | ```javascript 45 | Origin: http://127.0.0.1:8080 46 | ``` 47 | 48 | `Origin`说明本次请求来自于哪个源(协议 + 域名 + 端口),服务器根据这个值,决定是否同意这次请求。 49 | 50 | 如果`Origin`指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含`Access-Control-Allow-Origin`字段,就知道出错了,从而抛出一个错误,被`XMLHttpRequest`的`onerror`回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。 51 | 52 | 如果`Origin`指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段 53 | 54 | ```javascript 55 | Access-Control-Allow-Origin: http://127.0.0.1:8080 56 | ``` 57 | 58 | ##### `Access-Control-Allow-Origin` 59 | 60 | 该字段是必须的。它的值要么是请求时`Origin`字段的值,要么是一个`*`,表示接受任意域名的请求。 61 | 62 | ##### `Access-Control-Allow-Credentials` 63 | 64 | 该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为`true`,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为`true`,如果服务器不要浏览器发送Cookie,删除该字段即可。 65 | 66 | ##### `Access-Control-Expose-Headers` 67 | 68 | 该字段可选。CORS请求时,`XMLHttpRequest`对象的`getResponseHeader()`方法只能拿到6个基本字段:`Cache-Control`、`Content-Language`、`Content-Type`、`Expires`、`Last-Modified`、`Pragma`。如果想拿到其他字段,就必须在`Access-Control-Expose-Headers`里面指定。上面的例子指定,`getResponseHeader('FooBar')`可以返回`FooBar`字段的值。 69 | 70 | ##### **withCredentials 属性** 71 | 72 | 上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定`Access-Control-Allow-Credentials`字段。 73 | 74 | ```javascript 75 | Access-Control-Allow-Credentials: true 76 | ``` 77 | 78 | 另一方面,开发者必须在AJAX请求中打开`withCredentials`属性。 79 | 80 | ```javascript 81 | // axios 82 | axios.defaults.withCredentials = true 83 | 84 | var xhr = new XMLHttpRequest(); 85 | xhr.withCredentials = true; 86 | ``` 87 | 88 | 否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。 89 | 90 | 但是,如果省略`withCredentials`设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭`withCredentials`。 91 | 92 | ```javascript 93 | xhr.withCredentials = false; 94 | ``` 95 | 96 | 需要注意的是,如果要发送Cookie,`Access-Control-Allow-Origin`就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的`document.cookie`也无法读取服务器域名下的Cookie。 97 | 98 | 99 | 100 | koa跨域共享 101 | 102 | ```javascript 103 | cnpm install --save koa2-cors 104 | 105 | var cors = require('koa2-cors'); 106 | // 中间件最前面 107 | var app = new Koa(); 108 | app.use(cors()); 109 | ``` 110 | 111 | 112 | 113 | #### 非简单却请求(预检请求) 114 | 115 | 非简单请求是那种对服务器有特殊要求的请求,比如请求方法是`PUT`或`DELETE`,或者`Content-Type`字段的类型是`application/json`。 116 | 117 | 非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。 118 | 119 | 浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的`XMLHttpRequest`请求,否则就报错。 120 | 121 | ```javascript 122 | // General 123 | Request URL: http://localhost:3000/api/test 124 | Request Method: OPTIONS 125 | Status Code: 204 No Content 126 | Remote Address: [::1]:3000 127 | Referrer Policy: strict-origin-when-cross-origin 128 | 129 | // Request Headers 130 | Access-Control-Request-Headers: content-type 131 | Access-Control-Request-Method: POST 132 | 133 | // Response Headers 134 | Access-Control-Allow-Headers: content-type 135 | Access-Control-Allow-Methods: GET,PUT,POST,PATCH,DELETE,HEAD,OPTIONS 136 | Access-Control-Allow-Origin: http://127.0.0.1:8080 137 | ``` 138 | 139 | HTTP回应中,关键的是`Access-Control-Allow-Origin`字段,表示`http://127.0.0.1:8080`可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。 140 | 141 | ```javascript 142 | Access-Control-Allow-Origin: * 143 | ``` 144 | 145 | 如果服务器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被`XMLHttpRequest`对象的`onerror`回调函数捕获。 146 | 147 | **Access-Control-Allow-Methods** 148 | 149 | 该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。 150 | 151 | **Access-Control-Allow-Headers** 152 | 153 | 如果浏览器请求包括`Access-Control-Request-Headers`字段,则`Access-Control-Allow-Headers`字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。 154 | 155 | **Access-Control-Allow-Credentials** 156 | 157 | 该字段与简单请求时的含义相同。 158 | 159 | **Access-Control-Max-Age** 160 | 161 | 该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。 162 | 163 | 164 | 165 | ```javascript 166 | Access-Control-Allow-Origin:该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。 167 | Access-Control-Allow-Credentials:该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。 168 | Access-Control-Expose-Headers:该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control 169 | ``` 170 | 171 | 172 | 173 | ### nginx反向代理 174 | 175 | ```shell 176 | server { 177 | listen 80; 178 | server_name www.abc.com;# 服务器地址或绑定域名 179 | 180 | location ^~ /api { # ^~/api 表示匹配前缀为api的请求 181 | proxy_pass http://www.abc,com:9528/api/; # 注:proxy_pass的结尾有/, -> 效果:会在请求时将/api/*后面的路径直接拼接到后面 182 | 183 | # proxy_set_header作用:设置发送到后端服务器(上面proxy_pass)的请求头值 184 | # 【当Host设置为 $http_host 时,则不改变请求头的值; 185 | # 当Host设置为 $proxy_host 时,则会重新设置请求头中的Host信息; 186 | # 当为$host变量时,它的值在请求包含Host请求头时为Host字段的值,在请求未携带Host请求头时为虚拟主机的主域名; 187 | # 当为$host:$proxy_port时,即携带端口发送 ex: $host:8080 】 188 | proxy_set_header Host $host; 189 | 190 | proxy_set_header X-Real-IP $remote_addr; # 在web服务器端获得用户的真实ip 需配置条件① 【 $remote_addr值 = 用户ip 】 191 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;# 在web服务器端获得用户的真实ip 需配置条件② 192 | proxy_set_header REMOTE-HOST $remote_addr; 193 | # proxy_set_header X-Forwarded-For $http_x_forwarded_for; # $http_x_forwarded_for变量 = X-Forwarded-For变量 194 | } 195 | } 196 | ``` 197 | 198 | 199 | 200 | ### jsonp 201 | 202 | `Jsonp(JSON with Padding) `是 `json `的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据。 203 | 204 | ```javascript 205 | // koa2 206 | router.get('/api/jsonp', ctx => { 207 | let callbackName = ctx.query.cb || 'callback' 208 | let data = { 209 | code: 200, 210 | data: { 211 | text: 'jsonp api', 212 | time: new Date().getTime() 213 | } 214 | } 215 | let jsonpStr = `;${callbackName}(${JSON.stringify(data)})`; 216 | console.log('jsonpStr', jsonpStr) 217 | // 用text/javascript,让请求支持跨域获取 218 | ctx.type = 'text/javascript' 219 | ctx.body = jsonpStr 220 | }); 221 | // ctx.body 返回 222 | ;jsonpCB({"code":200,"data":{"text":"jsonp api","time":1609072661556}}) 223 | 224 | // 前端javascript 225 | const request = ({url, data}) => { 226 | return new Promise((resolve, reject) => { 227 | const handleData = (data) => { 228 | const keys = Object.keys(data); 229 | const keysLen = keys.length 230 | return keys.reduce((pre, cur, index) => { 231 | const value = data[cur]; 232 | const flag = index != keysLen - 1 ? '&' : ''; 233 | return `${pre}${cur}=${value}${flag}` 234 | }, '') 235 | } 236 | const script = document.createElement('script'); 237 | window.jsonpCB = (res) => { 238 | document.body.removeChild(script); 239 | delete window.jsonpCB 240 | resolve(res) 241 | } 242 | script.src = `${url}?${handleData(data)}&cb=jsonpCB` 243 | document.body.appendChild(script) 244 | }) 245 | } 246 | 247 | request({ 248 | url: 'http://localhost:3000/api/jsonp', 249 | data: { 250 | // 传参 251 | msg: 'helloJsonp' 252 | } 253 | }).then(res => { 254 | console.log(res) 255 | }) 256 | ``` 257 | 258 | `jsonp`只能发送`GET`请求,本质上`script`加载资源就是`GET`,如果要发送`POST`请求怎么办? 259 | 260 | #### 空iframe加from 261 | 262 | ```javascript 263 | // koa2 264 | router.post('/api/iframe_post', async (ctx, next) => { 265 | const data = ctx.request.body; 266 | console.log('data', data); 267 | let data = { 268 | code: 200, 269 | data: { 270 | text: 'iframe api', 271 | time: new Date().getTime() 272 | } 273 | } 274 | ctx.body = data 275 | }) 276 | 277 | 278 | const requestPost = ({ url, data = {} }) => { 279 | const iframe = document.createElement("iframe"); 280 | iframe.name = "iframePost"; 281 | iframe.style.display = "none"; 282 | document.body.appendChild(iframe); 283 | const form = document.createElement("form"); 284 | const node = document.createElement("input"); 285 | 286 | iframe.addEventListener("load", function () { 287 | console.log("post sucess"); 288 | }); 289 | 290 | form.action = url; 291 | // 在指定的iframe中执行form 292 | form.target = iframe.name; 293 | form.method = "post"; 294 | for (let name in data) { 295 | node.name = name; 296 | node.value = data[name].toString(); 297 | form.appendChild(node.cloneNode()); 298 | } 299 | // 表单元素需要添加到主文档中. 300 | form.style.display = "none"; 301 | document.body.appendChild(form); 302 | form.submit(); 303 | 304 | // 表单提交后,就可以删除这个表单,不影响下次的数据发送. 305 | document.body.removeChild(form); 306 | }; 307 | 308 | 309 | requestPost({ 310 | url: 'http://localhost:3000/api/iframe_post', 311 | data: { 312 | msg: 'helloIframePost' 313 | } 314 | }) 315 | ``` 316 | 317 | -------------------------------------------------------------------------------- /其他/interview/git/git_rebase.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsen1024/front-end-knowledge-systems/d5e4125399843b0ae59ad03cd55e5255423e01b9/其他/interview/git/git_rebase.md -------------------------------------------------------------------------------- /其他/interview/int/code_answer.md: -------------------------------------------------------------------------------- 1 | ### 1.闭包 2 | 3 | 闭包一个函数,能 -------------------------------------------------------------------------------- /其他/interview/int/question.md: -------------------------------------------------------------------------------- 1 | ### javascript 2 | 3 | #### 字节 4 | 5 | ###### 笔试 6 | 7 | - 写new\*** ***的执行过程 8 | 9 | - 写一个处理加法可能产生精度的函数,比如0.1 + 0.2 = 0.3 10 | 11 | - 1000000000 + 1000000000 ***允许返回字符串。处理大数。大数问题其实就是通过字符串来处理,从后往前加,然后处理进位的问题。\*** 12 | 13 | ###### 面试 (一面) 14 | 15 | - Cookie和Session有什么区别? 16 | 17 | - 浏览器如何做到 session 的功能的? 18 | 19 | - 说一下浏览器缓存; 20 | 21 | - 跨域的处理方案有哪些? 22 | 23 | - CORS 是如何做的? 24 | 25 | - 对于 CORS ,Get 和 POST 有区别吗? 26 | 27 | - 请解释一下csrf 和 xss; 28 | 29 | - 怎么防止 csrf 和 xss? 30 | 31 | - 了解 HTTPS 的过程吗? 32 | 33 | - webpack 如何做性能优化? 34 | 35 | - react 里如何做动态加载? 36 | 37 | - 动态加载的原理是什么? 38 | 39 | - es module 和 commonjs 的区别 40 | 41 | - 讲项目 42 | 43 | - es6新特性 44 | 45 | - vue响应式原理 46 | 47 | - 讲lazyloader的实现 48 | 49 | - 用了docker做了什么 50 | 51 | - 用了webpack做了什么 52 | 53 | - hooks和class Component的区别. 54 | 55 | - 虚拟滚动如何实现.(简历上有写) 56 | 57 | - 写防抖节流,节流两种实现都让写了. 58 | 59 | - 写快排. 60 | 61 | - 请求头有哪些 62 | 63 | - vue是怎么监听数组改变更新视图的 - 64 | 65 | - diff算法实现 66 | 67 | - 怎么禁止js读取到cookie 68 | 69 | - mysql的索引 70 | 71 | - 防抖和节流函数封装 72 | 73 | - web-push原理 74 | 75 | - web-push是一个协议,只是在草案中有( VAPID 协议保证用户信息的安全,只能通知到对应的浏览器) 76 | 77 | 授权:询问用户索取权限->生成公钥后验签的用户唯一标识信息->base64转码->ajax给后台数据库保存 78 | 79 | 是发布订阅模式,浏览器授权,去订阅服务器的消息通知,然后接收通知 80 | 81 | - web-push和websocket区别 82 | 83 | - 不用websocket,不用http2,不用worker,那么如何实现服务端推送消息 84 | 85 | - 骨架屏是什么,如何改善首屏优化和白屏 86 | 87 | - node中的require加载文件的顺序 88 | 89 | - node项目如何部署 90 | 91 | - ngix配置路径证书 92 | 93 | - http状态码, content-type有哪几种 94 | 95 | - x-www-urlecoded-form和application/json在post中的区别 96 | 97 | - vue-router有几种形式 98 | 99 | - 两种刷新的话有什么现象,history不会变化(已解决,已理清楚) 100 | 101 | ``` 102 | 浏览器在刷新的时候,会按照路径发送真实的资源请求,如果这个路径是前端通过 history API 设置的 URL,那么在服务端往往不存在这个资源,于是就返回 404 了。上面的参数的意思就是如果后端资源不存在就返回 history.html 的内容。 103 | 104 | 因此在线上部署基于 history API 的单页面应用的时候,一定要后端配合支持才行,否则会出现大量的 404。以最常用的 Nginx 为例,只需要在配置的 location / 中增加下面一行即可: 105 | 106 | try_files $uri /index.html; 107 | ``` 108 | 109 | - hash可能就会回到原始目录,因为hash的url在服务器端找不到对应的文件,这种问题怎么解决 110 | 111 | - reduce实现map 112 | 113 | - this 114 | 115 | ```javascript 116 | window.data=5 117 | var foo={ 118 | data:6, 119 | click(){ 120 | console.log(this.data) 121 | } 122 | } 123 | div.addEventListener('click',foo.click) 124 | // 点击div写出控制台的打印值 125 | // 如何输出5,如何输出6 126 | ``` 127 | 128 | - 写出一个正则匹配出图片的后缀,匹配以.jpg或者.png结尾的链接 129 | 130 | ```javascript 131 | var str='[https://happy.com/img/like.png](https://happy.com/img/like.png)' 132 | var reg=/\.(png|jpg)$/ 133 | ``` 134 | 135 | - webpack 的原理是什么?loader 和 plugin 的作用是什么? 136 | 137 | - docker 部署有什么好处? 138 | 139 | - docker 的底层原理是什么? 140 | 141 | - 那隔离环境主要隔离什么环境? 142 | 143 | - 有没有了解过 ufs? 144 | 145 | - 如果你要读取一个特别大的文件应该如何做 146 | 147 | - 你们有没有对服务端的异常进行监控[比如用 sentry 监控异常,elk 打日志,prometheus 监控性能并用 alertmanager 报警,再写一个webhook到钉钉] 148 | 149 | - 中间人劫持,怎么防止。x-frame-option?白屏的喔,怎么办?也不一定嵌入iframe啊,可以嵌入脚本、图片,怎么阻止? 150 | 151 | x-frame-option、重定向、https,请求前加密(https、加密代理)、请求中规避(请求拆包)、请求后弥补([前端]()做一些逻辑)。嵌入非iframe的,如果已经突破了前面两关,走[前端]()逻辑:触发DOMNodeInserted、DOMContentLoaded、DOMAttrModified事件。或者是给能src的标签加上自己的data-xx属性标记区分 152 | 153 | - hook缺点,hook代码难维护怎么解决【描述】 154 | 155 | - redux为什么每次reducer要返回一个新对象,面对大量节点如何优化【描述】 156 | 157 | immuatable和shouldupdate配合、immuatable数据一些对比问题【描述】 158 | 这是黄金搭配的方案了,用过的人应该能理解到。几个看代码判断 === 是否是true的问答题,原则:只要一个节点变了,那么从他开始回溯的父节点全都是变的 159 | 160 | - http缓存、离线包原理、移动端首屏幕加载速度优化、webview冷启动、预热【描述】 161 | 很基础的了,送分题 162 | - websocket和http2解释一下,socket是什么? 163 | - 网络七层协议 164 | - TCP和IP分别属于哪一层?TCP和UDP的区别?分别适用于什么情况? 165 | - vue组件通信怎么实现 父子和不父子 166 | - 167 | 168 | 169 | 170 | ###### 算法 171 | 172 | - 大到小数组排序算法 173 | 174 | - 蛇形二叉树组合算法 175 | 176 | - 斐波那契实现,复杂度, **斐波那契递归时间复杂度(O(2^N))** 177 | 178 | - 实现一个sleep函数 179 | 180 | ```js 181 | async function sleep(time){ 182 | // 这里是实现 183 | return new Promise((res)=>{ 184 | setTimeout(()=>{ 185 | res() 186 | },time) 187 | }) 188 | } 189 | console.log(1) 190 | await sleep(3000) 191 | console.log(2) 192 | ``` 193 | 194 | - 数组乱序(考察洗牌算法) 195 | - .[leetcode](https://www.nowcoder.com/jump/super-jump/word?word=leetcode) 找出数组中两个元素的和为target的组合 196 | - 场景题,写一个组件实现如下功能 197 | 198 | - ![组件](https://uploadfiles.nowcoder.com/images/20200911/586453626_1599810735019_270011980CF0F7A390E5CCB99CACC540) 199 | - 找出数组中最大的连续子数组和 200 | - 实现加法函数使得sum(2)(3)和sum(2,3)都输出5 201 | - 找出二叉树路径和为n的路径 202 | - 给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次。 203 | - sum(1)(2)(3)(...n) 204 | - 写一段匹配URL的正则,包括协议、域名、端口、path、hash、querystring 205 | - 寻找两个[二叉树](https://www.nowcoder.com/jump/super-jump/word?word=二叉树)节点的第一个公共父节点。 206 | - n级台阶,从0开始走起,一次可以走一步或者两步,那么走完n级台阶一共有多少种走法? 207 | - sort()是内部使用了什么[算法](https://www.nowcoder.com/jump/super-jump/word?word=算法) 时间复杂度是多少 indexOf()的时间复杂度是多少 208 | 209 | ###### 面试(二面) 210 | 211 | - 询问项目。项目的难点以及是怎么解决的?项目有哪些亮点? 212 | 213 | - 写一个es6继承 214 | 215 | - 写一个大数相乘的解决方案。传两个字符串进来,返回一个字符串。 216 | 217 | - js **异步编程原理** 218 | 219 | - 引擎负责解析,执行 [JavaScript]() 代码,但它并不能单独运行,通常都得有一个宿主环境,一般如浏览器或 Node 服务器,前文说到的单线程是指在这些宿主环境创建单一线程,提供一种机制,调用 [JavaScript]() 引擎完成多个 [JavaScript]() 代码块的调度,这种机制就称为事件循环( Event Loop )。 220 | - 关于事件循环流程分解如下: 221 | - 宿主环境为[JavaScript]() 创建线程时,会创建堆 (heap) 和栈 (stack) ,堆内存储 [JavaScript]() 对象,栈内存储执行上下文; 222 | - 栈内执行上下文的同步任务按序执行,执行完即退栈,而当异步任务执行时,该异步任务进入等待状态(不入栈),同时通知线程:当触发该事件时(或该异步操作响应返回时),需向消息队列插入一个事件消息; 223 | - 当事件触发或响应返回时,线程向消息队列插入该事件消息(包含事件及回调); 224 | - 当栈内同步任务执行完毕后,线程从消息队列取出一个事件消息,其对应异步任务(函数)入栈,执行回调函数,如果未绑定回调,这个消息会被丢弃,执行完任务后退栈; 225 | - 当线程空闲(即执行栈清空)时继续拉取消息队列下一轮消息(next tick ,事件循环流转一次称为一次 tick )。 226 | 227 | - 优化项目 228 | 229 | - vue原理,包括计算属性,依赖收集 230 | 231 | - 用`js`实现sleep函数 232 | 233 | - 304状态码,以及强缓存和协商缓存. 234 | 235 | - 实现一个antd modal组件.(提到了要用portal,但忘了语法) 236 | 237 | - 实现redux的connect.(没看过,不会) 238 | 239 | - 实现一个36进制加法.(这个比较简单,很快做好了) 240 | 241 | - 实现左边固定,右边自适应的布局.(这个也简单) 242 | 243 | - tcp如何保证安全连接 244 | 245 | - dns查询过程,使用的协议 246 | 247 | - 浏览器如何构建和渲染页面 248 | 249 | - cdn原理【描述】 250 | 251 | - 为什么多域名部署【描述】 252 | 253 | - event loop【描述】 254 | 255 | - `Babel`是怎么将`ES6`转换为`ES5`的? 256 | 257 | - 258 | 259 | 260 | 261 | ###### 三面 262 | 263 | - 手写快排,时间复杂度,优化 264 | - 手写实现 jsonp 265 | - 项目部署,线上问题等等 266 | - websocket 握手过程 267 | - 对 vuex 的理解,单向数据流 268 | - 设计一个单点登录的系统,类似阿里系那种 269 | - 手写一个算法,这道牛客题霸上有原题,大家可以去看看:NC66 两个链表的第一个公共结点 270 | - 项目有什么难点 271 | - 实现一个带并发限制的异步调度器Scheduler,保证同时运行的任务最多有两个.(这个之前没接触过过,做了20分钟左右,但也只做对90%吧,少了个判断条件,而且太紧张,忘了可以调试了) 272 | - js实现带并发限制的调度器,其实就是使用promise限制并发 273 | - 移动端适配方案 274 | - 如何提升移动端用户的使用体验,让用户能更快的看到页面 275 | - 如何设计权限系统,如何维护和定义、表的数据结构是怎样的【举例】【描述】 276 | 我们的[项目](https://www.nowcoder.com/jump/super-jump/word?word=项目)是rbac1类型的权限系统。展示是树形结构,但权限是扁平化的,只需要勾选权限,可以达到灵活修改权限 277 | - 权限系统业界内怎么设计,常见的几种【描述】 278 | - 从输入域名到页面展现之间发生了什么 -------------------------------------------------------------------------------- /其他/interview/int/手写.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | 4 | 5 | ### 1.实现call,apply 6 | 7 | - call() 方法调用一个函数, 其具有一个指定的 this 值和分别地提供的参数(参数的列表)。 8 | 9 | - call() 和 apply()的区别在于,call()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组 10 | 11 | ```javascript 12 | // es6实现call 13 | Function.prototype.call2 = function(ctx) { 14 | // 1.this 参数可以传 null 或者 undefined,此时 this 指向 window 15 | // 2.this 参数可以传基本类型数据,原生的 call 会自动用 Object() 转换 16 | // 3.函数是可以有返回值的 17 | ctx = ctx ? Object(ctx) : window 18 | ctx.fn = this; 19 | // 获取参数 20 | let args = [...arguments].slice(1) 21 | let result = ctx.fn(...args) 22 | delete ctx.fn; 23 | return result; 24 | } 25 | 26 | // es3 27 | Function.prototype.call2 = function (context) { 28 | context = context ? Object(context) : window; 29 | context.fn = this; 30 | 31 | var args = []; 32 | for(var i = 1, len = arguments.length; i < len; i++) { 33 | args.push('arguments[' + i + ']'); 34 | } 35 | var result = eval('context.fn(' + args +')'); 36 | 37 | delete context.fn 38 | return result; 39 | } 40 | ``` 41 | 42 | ```javascript 43 | // es6实现apply apply传递的是一个数组 44 | Function.prototype.apply = function (context, arr) { 45 | context = context ? Object(context) : window; 46 | context.fn = this; 47 | 48 | let result; 49 | if (!arr) { 50 | result = context.fn(); 51 | } else { 52 | result = context.fn(...arr); 53 | } 54 | 55 | delete context.fn 56 | return result; 57 | } 58 | 59 | // es3 60 | Function.prototype.apply2 = function (context, arr) { 61 | context = context ? Object(context) : window; 62 | context.fn = this; 63 | 64 | var result; 65 | // 判断是否存在第二个参数 66 | if (!arr) { 67 | result = context.fn(); 68 | } else { 69 | var args = []; 70 | for (var i = 0, len = arr.length; i < len; i++) { 71 | args.push('arr[' + i + ']'); 72 | } 73 | result = eval('context.fn(' + args + ')'); 74 | } 75 | 76 | delete context.fn 77 | return result; 78 | } 79 | ``` 80 | 81 | #### call 和 apply 的模拟实现有没有问题? 82 | 83 | 当然是有问题的,其实这里假设 `context` 对象本身没有 `fn` 属性,这样肯定不行,我们必须保证 `fn`属性的唯一性。 84 | 85 | 1.采用循环方案 86 | 87 | ```javascript 88 | function fnFactory(context) { 89 | var unique_fn = "fn"; 90 | while (context.hasOwnProperty(unique_fn)) { 91 | unique_fn = "fn" + Math.random(); // 循环判断并重新赋值 92 | } 93 | 94 | return unique_fn; 95 | } 96 | ``` 97 | 98 | 2.递归 99 | 100 | ```javascript 101 | function fnFactory(context) { 102 | var unique_fn = "fn" + Math.random(); 103 | if(context.hasOwnProperty(unique_fn)) { 104 | // return arguments.callee(context); ES5 开始禁止使用 105 | return fnFactory(context); // 必须 return 106 | } else { 107 | return unique_fn; 108 | } 109 | } 110 | ``` 111 | 112 | ```javascript 113 | // 判断唯一性 114 | function fnFactory(ctx) { 115 | let unique_fn = 'fn' 116 | while(ctx.hasOwnProperty(unique_fn)) { 117 | unique_fn = "fn" + Math.random() 118 | } 119 | return unique_fn; 120 | } 121 | 122 | 123 | // call 实现ctx的fn属性唯一性 124 | Function.prototype.call = function(ctx) { 125 | ctx = ctx ? Object(ctx) : window 126 | let fn = fnFactory(ctx) // add 127 | ctx[fn] = this; // change 128 | let args = [...arguments].slice(1) 129 | let result = ctx[fn](...args) // change 130 | delete ctx[fn]; // change 131 | return result; 132 | } 133 | 134 | // call 实现ctx的fn唯一性,可以采用symol 135 | 136 | Function.prototype.call = function (context) { 137 | context = context ? Object(context) : window; 138 | var fn = Symbol(); // added 139 | context[fn] = this; // changed 140 | 141 | let args = [...arguments].slice(1); 142 | let result = context[fn](...args); // changed 143 | 144 | delete context[fn]; // changed 145 | return result; 146 | } 147 | ``` 148 | 149 | 150 | 151 | ### 2.实现bind 152 | 153 | - `bind()` 方法会创建一个新函数,当这个新函数被调用时,它的 `this` 值是传递给 `bind()` 的第一个参数 154 | - 传入bind方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。 155 | - bind返回的绑定函数也能使用 `new` 操作符创建对象:这种行为就像把原函数当成构造器,提供的 `this` 值被忽略,同时调用时的参数被提供给模拟函数 156 | 157 | **模拟实现** 158 | 159 | - 1、可以指定`this` 160 | - 2、返回一个函数 161 | - 3、可以传入参数 162 | - 4、柯里化 163 | 164 | 第一版 165 | 166 | ````javascript 167 | Function.prototype.bind2 = function (context) { 168 | 169 | var self = this; 170 | // 实现第3点,因为第1个参数是指定的this,所以只截取第1个之后的参数 171 | // arr.slice(begin); 即 [begin, end] 172 | var args = Array.prototype.slice.call(arguments, 1); 173 | 174 | return function () { 175 | // 实现第4点,这时的arguments是指bind返回的函数传入的参数 176 | // 即 return function 的参数 177 | var bindArgs = Array.prototype.slice.call(arguments); 178 | return self.apply( context, args.concat(bindArgs) ); 179 | } 180 | } 181 | ```` 182 | 183 | test 184 | 185 | ```javascript 186 | var value = 2; 187 | 188 | var foo = { 189 | value: 1 190 | }; 191 | 192 | function bar(name, age) { 193 | return { 194 | value: this.value, 195 | name: name, 196 | age: age 197 | } 198 | }; 199 | 200 | var bindFoo = bar.bind2(foo, "Jack"); 201 | bindFoo(20); 202 | // {value: 1, name: "Jack", age: 20} 203 | ``` 204 | 205 | 206 | 207 | 到现在已经完成大部分了,但是还有一个难点,`bind` 有以下一个特性 208 | 209 | > 一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器,提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。 210 | 211 | ```js 212 | Function.prototype.bind2 = function (context) { 213 | var self = this; 214 | var args = Array.prototype.slice.call(arguments, 1); 215 | 216 | var fBound = function () { 217 | var bindArgs = Array.prototype.slice.call(arguments); 218 | 219 | // 注释1 220 | return self.apply( 221 | this instanceof fBound ? this : context, 222 | args.concat(bindArgs) 223 | ); 224 | } 225 | // 注释2 226 | fBound.prototype = this.prototype; 227 | return fBound; 228 | } 229 | ``` 230 | 231 | - 注释1: 232 | - 当作为构造函数时,this 指向实例,此时 `this instanceof fBound` 结果为 `true`,可以让实例获得来自绑定函数的值,即上例中实例会具有 `habit` 属性。 233 | - 当作为普通函数时,this 指向 `window`,此时结果为 `false`,将绑定函数的 this 指向 `context` 234 | - 注释2: 修改返回函数的 `prototype` 为绑定函数的 `prototype`,实例就可以继承绑定函数的原型中的值,即上例中 `obj` 可以获取到 `bar` 原型上的 `friend` 235 | 236 | 237 | 238 | 上面bind2实现中 `fBound.prototype = this.prototype`有一个缺点,直接修改 `fBound.prototype` 的时候,也会直接修改 `this.prototype`。 239 | 240 | ```js 241 | // 测试用例 242 | var value = 2; 243 | var foo = { 244 | value: 1 245 | }; 246 | function bar(name, age) { 247 | this.habit = 'shopping'; 248 | console.log(this.value); 249 | console.log(name); 250 | console.log(age); 251 | } 252 | bar.prototype.friend = 'kevin'; 253 | 254 | var bindFoo = bar.bind2(foo, 'Jack'); // bind2 255 | var obj = new bindFoo(20); // 返回正确 256 | // undefined 257 | // Jack 258 | // 20 259 | 260 | obj.habit; // 返回正确 261 | // shopping 262 | 263 | obj.friend; // 返回正确 264 | // kevin 265 | 266 | obj.__proto__.friend = "Kitty"; // 修改原型 267 | 268 | bar.prototype.friend; // 返回错误,这里被修改了 269 | // Kitty 270 | ``` 271 | 272 | 解决方案是用一个空对象作为中介,把 `fBound.prototype` 赋值为空对象的实例(原型式继承)。 273 | 274 | ```js 275 | var fNOP = function () {}; // 创建一个空对象 276 | fNOP.prototype = this.prototype; // 空对象的原型指向绑定函数的原型 277 | fBound.prototype = new fNOP(); // 空对象的实例赋值给 fBound.prototype 278 | ``` 279 | 280 | 这边可以直接使用ES5的 `Object.create()`方法生成一个新对象 281 | 282 | ```js 283 | fBound.prototype = Object.create(this.prototype); 284 | ``` 285 | 286 | 不过 `bind` 和 `Object.create()`都是ES5方法,部分IE浏览器(IE < 9)并不支持,Polyfill中不能用 `Object.create()`实现 `bind`,不过原理是一样的。 287 | 288 | ```js 289 | // 第二版,已通过测试用例 290 | Function.prototype.bind2 = function (context) { 291 | var self = this; 292 | var args = Array.prototype.slice.call(arguments, 1); 293 | 294 | var fNOP = function () {}; 295 | 296 | var fBound = function () { 297 | var bindArgs = Array.prototype.slice.call(arguments); 298 | return self.apply( 299 | this instanceof fNOP ? this : context, 300 | args.concat(bindArgs) 301 | ); 302 | } 303 | 304 | fNOP.prototype = this.prototype; 305 | fBound.prototype = new fNOP(); 306 | return fBound; 307 | } 308 | ``` 309 | 310 | 到这里其实已经差不多了,但有一个问题是调用 `bind` 的不是函数,这时候需要抛出异常。 311 | 312 | ```js 313 | if (typeof this !== "function") { 314 | throw new Error("Function.prototype.bind - what is trying to be bound is not callable"); 315 | } 316 | ``` 317 | 318 | 所以完整版模拟实现代码如下: 319 | 320 | ```js 321 | // 第五版 322 | Function.prototype.bind2 = function (context) { 323 | 324 | if (typeof this !== "function") { 325 | throw new Error("Function.prototype.bind - what is trying to be bound is not callable"); 326 | } 327 | 328 | var self = this; 329 | var args = Array.prototype.slice.call(arguments, 1); 330 | 331 | var fNOP = function () {}; 332 | 333 | var fBound = function () { 334 | var bindArgs = Array.prototype.slice.call(arguments); 335 | return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs)); 336 | } 337 | 338 | fNOP.prototype = this.prototype; 339 | fBound.prototype = new fNOP(); 340 | return fBound; 341 | } 342 | ``` 343 | 344 | 345 | 346 | ### 3.手写一个new 347 | 348 | **模拟实现** 349 | 350 | 当代码 `new Foo(...)` 执行时,会发生以下事情: 351 | 352 | 1. 一个继承自 `Foo.prototype` 的新对象被创建。 353 | 2. 使用指定的参数调用构造函数 `Foo` ,并将 `this` 绑定到新创建的对象。`new Foo` 等同于 `new Foo()`,也就是没有指定参数列表,`Foo` 不带任何参数调用的情况。 354 | 3. 由构造函数返回的对象就是 `new` 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。 355 | 356 | 357 | 358 | ```javascript 359 | function Car(color, name) { 360 | this.color = color; 361 | return { 362 | name: name 363 | } 364 | } 365 | 366 | var car = new Car("black", "BMW"); 367 | car.color; 368 | // undefined 369 | 370 | car.name; 371 | // "BMW" 372 | ``` 373 | 374 | - 1、返回一个对象 375 | - 2、没有 `return`,即返回 `undefined` 376 | - 3、返回`undefined` 以外的基本类型 377 | 378 | ```javascript 379 | // 最终 380 | 381 | function create() { 382 | // 1、获得构造函数,同时删除 arguments 中第一个参数 383 | Con = [].shift.call(arguments); 384 | // 2、创建一个空的对象并链接到原型,obj 可以访问构造函数原型中的属性 385 | var obj = Object.create(Con.prototype); 386 | // 3、绑定 this 实现继承,obj 可以访问到构造函数中的属性 387 | var ret = Con.apply(obj, arguments); 388 | // 4、优先返回构造函数返回的对象 389 | return ret instanceof Object ? ret : obj; 390 | }; 391 | ``` 392 | 393 | 或者 394 | 395 | ```javascript 396 | function create() { 397 | var obj = new Object(); 398 | 399 | var fn = [].shift.call(arguments); 400 | obj.__proto__ = fn.prototype 401 | let rst = fn.apply(obj, arguments) 402 | return rst instanceof Object ? ret : obj 403 | } 404 | ``` 405 | 406 | 407 | 408 | ### 4.实现一个深拷贝 409 | 410 | ```javascript 411 | //判断是否是数组 412 | const isArray = val => Array.isArray(val) 413 | // 判断是否是对象 414 | const isObject = val => typeof val === 'object' && val !== null 415 | 416 | function CloneDeep(source, hash = new WeakMap()) { 417 | // 如果是基础数据类型则返回 418 | if (!isObject(source)) return source 419 | // WeakMap key只能是对象 420 | // weakMap和map的区别 https://segmentfault.com/a/1190000022756283 421 | // hash解决循环引用 422 | if (hash.has(source)) return hash.get(source); 423 | let target = isArray(source) ? [] : {} 424 | hash.set(source, target); 425 | // 如果对象是symbol类型的,无法通过for in进行遍历(也可以通过Object.getOwnPropertySymbols(...)) 426 | Reflect.ownKeys(source).forEach(key => { 427 | if(isObject(key)) { 428 | CloneDeep(source[key], hash) 429 | } else { 430 | target[key] = source[key] 431 | } 432 | }) 433 | return target 434 | } 435 | 436 | function cloneDeep4(source, hash = new WeakMap()) { 437 | 438 | if (!isObject(source)) return source; 439 | if (hash.has(source)) return hash.get(source); 440 | 441 | let target = Array.isArray(source) ? [...source] : { ...source }; // 改动 1 442 | hash.set(source, target); 443 | 444 | Reflect.ownKeys(target).forEach(key => { // 改动 2 445 | if (isObject(source[key])) { 446 | target[key] = cloneDeep4(source[key], hash); 447 | } else { 448 | target[key] = source[key]; 449 | } 450 | }); 451 | return target; 452 | } 453 | 454 | // 广度遍历 https://segmentfault.com/a/1190000016672263 455 | 456 | function cloneLoop(x){ 457 | const root = {} 458 | 459 | // 栈 460 | const loopList = [ 461 | { 462 | parent: root, 463 | key: undefined, 464 | data: x 465 | } 466 | ] 467 | 468 | 469 | while(loopList.length) { 470 | const node = loopList.pop(); 471 | const parent = node.parent; 472 | const key = node.key; 473 | const data = node.data; 474 | 475 | let res = parent; 476 | 477 | if (typeof key !== undefined) { 478 | res = parent[key] = {} 479 | } 480 | 481 | for(let k in data) { 482 | if (typeof data[k] === 'object') { 483 | // 下一次循环 484 | loopList.push({ 485 | parent: res, 486 | key: k, 487 | data: data[k], 488 | }); 489 | } else { 490 | res[k] = data[k]; 491 | } 492 | } 493 | } 494 | 495 | return root 496 | } 497 | 498 | ``` 499 | 500 | 501 | 502 | ### 5.实现instanceof 503 | 504 | `instanceof` 运算符用来检测 `constructor.prototype` 是否存在于参数 `object` 的原型链上。 505 | 506 | ```js 507 | function C(){} 508 | function D(){} 509 | 510 | var o = new C(); 511 | 512 | o instanceof C; // true,因为 Object.getPrototypeOf(o) === C.prototype 513 | o instanceof D; // false,因为 D.prototype 不在 o 的原型链 514 | ``` 515 | 516 | instanceof 原理就是一层一层查找 `__proto__`,如果和 `constructor.prototype` 相等则返回 true,如果一直没有查找成功则返回 false。 517 | 518 | ```js 519 | instance.[__proto__...] === instance.constructor.prototype 520 | ``` 521 | 522 | 知道了原理后我们来实现 instanceof,代码如下。 523 | 524 | ```js 525 | function instance_of(L, R) {//L 表示左表达式,R 表示右表达式 526 | var O = R.prototype;// 取 R 的显示原型 527 | L = L.__proto__;// 取 L 的隐式原型 528 | while (true) { 529 | // Object.prototype.__proto__ === null 530 | if (L === null) 531 | return false; 532 | if (O === L)// 这里重点:当 O 严格等于 L 时,返回 true 533 | return true; 534 | L = L.__proto__; 535 | } 536 | } 537 | 538 | // 测试 539 | function C(){} 540 | function D(){} 541 | 542 | var o = new C(); 543 | 544 | instance_of(o, C); // true 545 | instance_of(o, D); // false 546 | ``` -------------------------------------------------------------------------------- /其他/interview/javascript/1.数据类型.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | 3 | [github地址](https://github.com/charmJiang/front-end-knowledge-systems/blob/main/javascript/1.%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B.md) 4 | 5 | ## js的变量和数据类型 6 | 7 | js的数据类型分为:原始类型(即基本类型)和对象类型(即引用数据类型) 8 | 9 | - 基本类型: 10 | - `Undefined`、`Null`、`Number`、`Boolean`、`String`、`Symbol` 11 | - 引用数据类型: 12 | - `Object`、`Array`、`Function`、`Date`、`RegExp`等等 13 | 14 | 15 | 16 | ## 一、基本类型和引用数据类型的区别 17 | 18 | 19 | 20 | ### 基本类型 21 | 22 | - 基本类型不能由属性 23 | 24 | ```javascript 25 | let name = 'Tom'; 26 | name.age = 18; 27 | 28 | console.log(name.age) // undefined 29 | ``` 30 | 31 | ```javascript 32 | // 只有引用值可以动态添加后面的属性 33 | let name = new Sttring('Tom') 34 | // name -> String {"Tom"} 35 | name.age = 18 36 | console.log(name.age) // 18 37 | // name -> String {"Tom", age: 19} 38 | 39 | typeof name // "object" 40 | ``` 41 | 42 | 43 | 44 | - 基本类型的值是不变的 45 | 46 | ```javascript 47 | let name = 'tom' 48 | name.toUpperCase() // "TOM" 49 | name // tom 50 | ``` 51 | 52 | - 基本类型是按值传递 53 | 54 | ```javascript 55 | let name = 'tom'; 56 | let name2 = 'TOM'; 57 | name = name2; 58 | console.log(name, name2) // TOM TOM 59 | ``` 60 | 61 | - 基本类型的比较是比较它们的值 62 | 63 | ```javascript 64 | let a = 1; 65 | let b = true; 66 | 67 | a == b // true 68 | a === b // false 69 | ``` 70 | 71 | - 基本类型的变量是存放在`stack`(栈) 72 | 73 | ```javascript 74 | let a = 'tom'; 75 | let b = 123; 76 | ``` 77 | 78 | ![rskRFk](https://gitee.com/vr2/images/raw/master/uPic/rskRFk.png) 79 | 80 | ### 引用类型 81 | 82 | - 引用类型的值是可变 83 | 84 | ```javascript 85 | const obj = { 86 | name: 'tom', 87 | age: 18 88 | } 89 | obj.sex = 'man'; 90 | obj.getName = function () { 91 | console.log(this.name) 92 | } 93 | ``` 94 | 95 | - 引用类型的比较是引用的比较 96 | 97 | ```javascript 98 | const o1 = {}; 99 | const o2 = {}; 100 | 101 | o1 === o2 // false 102 | o1 == o2 // false 103 | ``` 104 | 105 | `o1` 、`o2` 引用分别放在堆内存中的2个对象 106 | 107 | - 引用是类型的值是保存在堆内存中,指针存在栈中。 108 | 109 | ![RNaycO](https://gitee.com/vr2/images/raw/master/uPic/RNaycO.png) 110 | 111 | 112 | 113 | #### 一道题实战 114 | 115 | ```javascript 116 | function addNum (num) { 117 | num += 1; 118 | return num 119 | } 120 | 121 | let count = 10; 122 | 123 | let result = addNum(count); 124 | 125 | console.log(count); // 10 126 | console.log(result) // 11 127 | 128 | 129 | function setPhone (obj) { 130 | obj.phone = '123' 131 | } 132 | 133 | let p1 = new Object(); 134 | setPhone(p1) 135 | console.log(p1) // {phone: '123'} 136 | 137 | 138 | function setPhone (obj) { 139 | obj.phone = '123' 140 | // 创建一个新的对象 141 | obj = new Object(); 142 | obj.phone = '456' 143 | } 144 | 145 | let p1 = new Object(); 146 | setPhone(p1) 147 | 148 | console.log(p1) // {phone: '123'} 149 | ``` 150 | 151 | 152 | 153 | ## 二、类型检测 154 | 155 | - `typeof` 156 | - 只能检测基本数据类型 但是typeof null 是"object",不能检测复杂类型 157 | - `instanceof` 通过原型链去查找的 158 | - 返回的是false 或者true 159 | - 可以识别内置对象类型、自定义类型及其父类型 160 | - 不能识别标准类型,会返回false 161 | - 不能识别undefined、null,会报错 162 | - `constructor`属性 实例对象的constructor属性指向其构造函数。如果是内置类型,则输出`function` 数据类型`(){[native code]}`;如果是自定义类型,则输出`function` 数据类型`(){}` 163 | - 可以识别标准类型、内置对象类型及自定义类型 164 | - 不能识别`undefined`、`nul`,会报错,因为它俩没有构造函数 165 | - `Object.prototype.toString()` 返回` [object 数据类型]` 166 | - 可以识别标准类型及内置对象类型 167 | - 不能识别自定义类型 168 | - `Array.isArray()` 数组的检测 169 | 170 | 171 | 172 | ### typeof 173 | 174 | `typeof`只能判断基本数据类型,不能判断引用数据类型(返回 `object`) 175 | 176 | ```javascript 177 | let s = 'tom'; 178 | let num = 123; 179 | let bool = true; 180 | let sy = Symbol('tom') 181 | let u; 182 | let n = null; 183 | let o = new Object(); 184 | 185 | 186 | typeof s // string 187 | typeof num // number 188 | typeof bool // boolean 189 | typeof u // undefined 190 | typeof sy // symbol 191 | 192 | typeof n // object 193 | typeof o // object 194 | ``` 195 | 196 | **注意**:`typeof null `为`object` 197 | 198 | ###### 为什么`typeof null `为`object` ? 199 | 200 | 在 JavaScript 中 `typeof null` 的结果为 `"object"`,这是从 JavaScript 的第一版遗留至今的一个 bug。在第一版的 JavaScript 中,变量的值被设计保存在一个 32 位的内存单元中。该单元包含一个 1 或 3 位的类型标志,和实际数据的值。类型标志存储在单元的最后。 201 | 202 | | 数据类型 | **机器码标识** | 203 | | ------------ | -------------- | 204 | | 整数 | 1 | 205 | | 浮点数 | 010 | 206 | | 字符串 | 100 | 207 | | 布尔 | 110 | 208 | | undefined | -2^31(即全为1) | 209 | | null | 全为0 | 210 | | 对象(Object) | 000 | 211 | 212 | 在判断数据类型时,是根据机器码低位标识来判断的,而`null`的机器码标识为全`0`,而对象的机器码低位标识为`000`。所以`typeof null`的结果被误判为`Object`。 213 | 214 | 215 | 216 | ### instanceof 217 | 218 | - 通过原型链去查找的 219 | - 返回的是false 或者true 220 | - 可以识别内置对象类型、自定义类型及其父类型 221 | - 不能识别标准类型,会返回false 222 | - 不能识别undefined、null,会报错 223 | 224 | ```javascript 225 | 'a' instanceof String -> false 226 | 12 instanceof Number -> false 227 | true instanceof Boolean -> false 228 | undefined instanceof Undefined ->报错 229 | [] instanceof Array -> true 230 | new Person instanceof Person -> true 231 | new Person instanceof Object -> true 232 | ``` 233 | 234 | 235 | 236 | ### constructor 237 | 238 | `constructor`属性 实例对象的constructor属性指向其构造函数。如果是内置类型,则输出`function` 数据类型`(){[native code]}`;如果是自定义类型,则输出`function` 数据类型`(){}` 239 | 240 | - 可以识别标准类型、内置对象类型及自定义类型 241 | - 不能识别`undefined`、`nul`,会报错,因为它俩没有构造函数 242 | 243 | ```javascript 244 | ('a').constructor -> function String(){[native code]} 245 | (undefined).constructor) -> 报错 246 | null).constructor -> 报错 247 | {name: "jerry"}).constructor -> function Object(){[native code]} 248 | (new Person).constructor -> function Person(){} 249 | // 封装成一个类型识别函数 250 | function type(obj){ 251 | var temp = obj.constructor.toString(); 252 | return temp.replace(/^function (\w+)\(\).+$/,'$1'); 253 | } 254 | ``` 255 | 256 | ​ 257 | 258 | ### Object.prototype.toString() 259 | 260 | `Object.prototype.toString()` 返回` [object 数据类型]` 261 | 262 | - 可以识别标准类型及内置对象类型 263 | - 不能识别自定义类型 264 | 265 | ```javascript 266 | Object.prototype.toString.call('a') -> [object String] 267 | Object.prototype.toString.call(undefined) -> [object Undefined] 268 | Object.prototype.toString.call(null) -> [object Null] 269 | Object.prototype.toString.call({name: "jerry"})) ->[object Object] 270 | Object.prototype.toString.call(function(){}) -> [object Function] 271 | Object.prototype.toString.call(new Person)) -> [object Object] 272 | ``` 273 | 274 | 275 | 276 | 封装成一个类型识别函数 277 | 278 | ```javascript 279 | function type(obj){ 280 | return Object.prototype.toString.call(obj).slice(8,-1).toLowerCase(); 281 | } 282 | ``` 283 | 284 | 285 | 286 | ### Array.isArray() 287 | 288 | ​ 数组检测 289 | 290 | ```javascript 291 | const a = [1,2,3] 292 | Array.isArray(a) -> true 293 | Array.isArray([]) -> true 294 | ``` 295 | 296 | ##### 原理 297 | 298 | [Object.prototype.toString()的原理](https://zhuanlan.zhihu.com/p/118793721) 299 | 300 | 301 | 302 | ## 三、其他 303 | 304 | ### 1.`undefined`和`null`的区别 305 | 306 | - `null`和`undefined`都是基本数据类型。 307 | 308 | - `Undefined`类型只有一个值,就是`undefined`。当声明的变量未初始化时,该变量的默认值是`undefined`。所以一般地,`undefined`表示变量没有初始化。 309 | 310 | - `Null`类型只有一个值,就是`null`。`null`是`javascript`语言的关键字,它表示一个特殊值,常用来描述"空值",逻辑角度看,`null`值表示一个空对象指针 311 | 312 | ```javascript 313 | null == undefined -> ture 314 | null === undefined -> false 315 | null === null -> true 316 | null == null -> true 317 | undefined === undefined -> true 318 | ``` 319 | 320 | - 给一个全局变量赋值为`null`,相当于将这个变量的指针对象以及值清空,如果是给对象的属性赋值为`null`,或者局部变量赋值为`null`,相当于给这个属性分配了一块空的内存,然后值为`null`,` JS`会回收全局变量为`null`的对象。 321 | 322 | - 给一个全局变量赋值为`undefined`,相当于将这个对象的值清空,但是这个对象依旧存在,如果是给对象的属性赋值为`undefined`,说明这个值为空值 323 | 324 | **扩展下**: 325 | 326 | ​ 声明了一个变量,但未对其初始化时,这个变量的值就是undefined,它是 JavaScript 基本类型 之一 327 | 328 | ```javascript 329 | var data; 330 | console.log(data === undefined); //true 331 | ``` 332 | 333 | ​ 对于尚未声明过的变量,只能执行一项操作,即使用typeof操作符检测其数据类型,使用其他的操作都会报错。 334 | 335 | ```javascript 336 | //data变量未定义 337 | console.log(typeof data); // "undefined" 338 | console.log(data === undefined); //报错 339 | ``` 340 | 341 | 值 `null` 特指对象的值未设置,它是 JavaScript 基本类型 之一。 342 | 343 | 值 `null` 是一个字面量,它不像[`undefined`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined) 是全局对象的一个属性。`null` 是表示缺少的标识,指示变量未指向任何对象。 344 | 345 | ```javascript 346 | // foo不存在,它从来没有被定义过或者是初始化过: 347 | foo; 348 | "ReferenceError: foo is not defined" 349 | // foo现在已经是知存在的,但是它没有类型或者是值: 350 | var foo = null; 351 | console.log(foo); // null 352 | ``` 353 | 354 | 355 | 356 | ### 2.`JavaScript`中的变量在内存中的具体存储形式 357 | 358 | - 基本类型 --> 保存在**栈**内存中,因为这些类型在内存中分别占有固定大小的空间,通过按值来访问。基本类型一共有6种:`Undefined`、`Null`、`Boolean`、`Number` 、`String`和`Symbol` 359 | - 引用类型 --> 保存在**堆**内存中,因为这种值的大小不固定,因此不能把它们保存到栈内存中,但内存地址大小的固定的,因此保存在堆内存中,在栈内存中存放的只是该对象的访问地址。当查询引用类型的变量时, 先从**栈中读取内存地址**, 然后再通过地址**找到堆中的值**。对于这种,我们把它叫做按引用访问。 360 | 361 | ![WqmHvM](https://gitee.com/vr2/images/raw/master/uPic/WqmHvM.png) 362 | 363 | ### 3.`JavaScript`对象的底层数据结构是什么 364 | 365 | [参考链接1](https://www.mdeditor.tw/jump/aHR0cHM6Ly93d3cuamlhbnNodS5jb20vcC83ZDNhYjlhMjJiMTE=) [参考链接2](https://www.mdeditor.tw/jump/aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vemhvdWx1anVuL3AvMTA4ODE2MzkuaHRtbA==) 366 | 367 | 368 | 369 | ### 4.`Symbol`类型在实际开发中的应用、可手动实现一个简单的 `Symbol` 370 | 371 | [ES6 的 Symbol 类型及使用案例](https://my.oschina.net/u/2903254/blog/818796) 372 | 373 | 374 | 375 | ### 5.javascript隐性转换 376 | 377 | #### 在 JS 中基本数据类型之间的转化只有三种情况: 378 | 379 | - 转换为布尔值 380 | - 转换为数字 381 | - 转换为字符串 382 | 383 | ![ytyGAl](https://gitee.com/vr2/images/raw/master/uPic/ytyGAl.png) 384 | 385 | 上图中包含了所有转换规则,这里总结几点需要注意的地方: 386 | 387 | - `NaN` 是 `number` 类型的数据,所以 `number` 转其他类型时不要漏掉它。 388 | - 对象转字符串,结果为:`[object Object]` 389 | - 数组转数字的规则:空数组、只有一个数字元素的数组转为数字,其余情况均为 `NaN` 390 | - `undefined` 和 `null` 转数字,分别为:`NaN` 和 `0` 391 | - 转为 `Boolean` 时,除了 `undefined`,`null`,`false`,`NaN`,`0`,`-0` 转为 `false`,其他的都转为 `true`,包括所有对象。 392 | 393 | #### 对于对象**转换为**基本类型时,其内部的原理如下: 394 | 395 | 1. 如果已经是基本类型,直接返回 396 | 2. 调用内置的 `valueOf`、`toString`方法,如果转换为基本类型,则返回转换的值 397 | 3. 如果没有转换为基本类型,则会抛出错误 398 | 399 | #### 类型转换的一些小技巧 400 | 401 | - 数据前置 `+` 号,转换为 `number` 类型 402 | - 数据与 `0` 相减,转换为 `number` 类型 403 | - 数据前置 `!!` 号,转换为 `Boolean` 类型 404 | 405 | #### coding 406 | 407 | ```javascript 408 | // 一、只有 + 两边有一边是字符串, 会把其它数据类型调用toString()方法转成字符串然后拼接 409 | // + 是字符串连接符: String(1) + 'true' = '1true' 410 | 1 + "true" // '1true' 411 | 412 | // 二、算术运算符+ :会把其它数据类型调用Number()方法转成数字然后做加法计算 413 | // + 是算法运算 1 + Number(true) 414 | 1 + true // 2 415 | // 1 + Number(undefined) = 1 + NaN = NaN 416 | 1 + undefined //NaN 417 | // 1 + Number(null) = 1+ 0 =1 418 | 1 + null // 1 419 | 420 | // 三、关系运算符 会把其他数据类型转换成number之后再比较关系 421 | 422 | // 1.当关系运算符两边又一边是字符串时,会将其它数据类型使用Number()转换,然后比较关系 423 | // Number('2') > 10 = 2 > 10 = false 424 | '2' > 10 // false 425 | // 2.当关系运算符两边都是字符串,此时同时转成number然后比较关系 426 | // 重点:此时并不是按照Number()的形式转成数字,而是按照字符串对应的unicode编码转成数字 427 | // 使用这个查看字符串的unicode编码: 字符串的charCodeAt(字符下标,默认为0) 428 | // '2'.charCodeAt() > '10'.charCodeAt() = 50 > 49 = true 429 | '2' > '10' // true 430 | // 多个字符从左往右依次比较 431 | // 先比较'a' 和 'b', ‘a' 与 ‘b'不等,则直接得出结果 432 | "abc" > "b" //fasle 433 | // 先比较'a' 和'a',两者相等,继续比较第二个字符'b'与‘a',得出结果 a.charCodeA = 97 b.charCodeB=98 434 | "abc" > "aad" // true 435 | // 3.特殊情况:如果数据类型是undefined与null,得出固定的结果 436 | undefined == undefined // true 437 | undefined === undefined // true 438 | undefined == null // true 439 | null == null // ture 440 | null === null // ture 441 | // 4.NaN与任何数据比较都是NaN 442 | NaN == NaN // false 443 | 444 | //四、复杂数据类型在隐式转换时, 445 | // 如果是转换成number 446 | //1、如果输入的值已经是一个原始值,则直接返回它 447 | // 2、否则,如果输入的值是一个对象,则调用该对象的valueOf()方法, 448 | // 如果valueOf()方法的返回值是一个原始值,则返回这个原始值。 449 | // 3、否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。 450 | // 4、否则,抛出TypeError异常。 451 | 452 | // 如果是转换成string 453 | // 1、如果输入的值已经是一个原始值,则直接返回它 454 | // 2、否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。 455 | // 3、否则,如果输入的值是一个对象,则调用该对象的valueOf()方法, 456 | // 如果valueOf()方法的返回值是一个原始值,则返回这个原始值。 457 | // 4、否则,抛出TypeError异常。 458 | 459 | //复杂数据类型转number顺序如下 460 | // 1.先使用valueOf()方法获取其原始值,如果原始值不是number类型,则使用toString()方法转换成string 461 | // 2.再将string 转换成 number运算。 462 | [1,2] == '1,2' // true [1,2]转换成string [1,2].valueOf() -> [1,2] [1,2].toString() -> '1,2' 463 | 464 | var a = {} 465 | a == "[object Object]" // true a.valueOf().toString() -> "[object Object]" 466 | 467 | // 一道题 468 | var a = ??? 469 | if(a==1 && a==2 &&a==3) { 470 | console.log(1) 471 | } 472 | // 如何完善a,使其正确打印1 473 | 474 | var a = { 475 | i: 0, //声明一个属性i 476 | valueOf: function() { 477 | return ++ a.i //每次调用一次,让对象a的i属性自增一次并且返回 478 | } 479 | } 480 | if(a==1 && a==2 &&a==3) { // 每次运算时都会调用一次a的valueOf方法 481 | console.log(1) 482 | } 483 | 484 | // 五、逻辑非隐形转换与关系元算符隐式转换搞混淆 485 | // 空数组的toString()方法会得到空字符串,而空对象的toString()方法会得到字符串'[object Object]' 486 | 487 | // 1.关系运算法:将其它数据类型转成数字 488 | // 2.逻辑非:将其它数据类型使用Boolean()转成布尔类型 489 | // 以下八种情况转换为布尔类型会得到false 490 | // 0、-0、NaN、undefined、null、空字符、false、document.all() 491 | 492 | // [].valueOf().toString()得到空字符串 Number('') ==0 493 | [] == 0 // ture 494 | 495 | //本质是 ![] 逻辑非与表达式结果与0比较关系 496 | // 1.逻辑非优先级高于关系运算法 ![] = false(空数组转布尔得到true,然后取反得到fasle) 497 | // 2.false == 0 成立 498 | ![] == 0 // true 499 | 500 | // 本质其实是空对象{} 与 !{} 这个逻辑非表达式结果做比较 501 | //1.{}.valueOf().toString()得到字符串'[object Object]' 502 | //2.!{} = false 503 | //3.Number('[object Object]') -> NaN == Number(false) -> 0 504 | {} == !{} 505 | 506 | //引用类型数据存在堆中,栈中存储的是地址,所以他们的结果false 507 | {} == {} // false 508 | 509 | // 本质是空数组[] 与 ![] 这个逻辑表达式结果做比较 510 | //1. [].valueOf().toString()得到空字符串 511 | //2. ![] = false 512 | //3.Number('') == Number(false) 成立 都是0 513 | [] == ![] // true 514 | 515 | [] == [] // false 引用类型数据在堆中,栈中存储的是地址,所以他们的结果都是fasle 516 | 517 | {}.valueOf().toString() // [obejct Obejct] 518 | [].valueOf().toString() //空字符串 519 | ``` 520 | 521 | [掘金:你所忽略的js隐式转换](https://juejin.im/post/5a7172d9f265da3e3245cbca) 522 | 523 | 524 | 525 | ### 6.0.1+-0.2===0.3? 526 | 527 | 出现小数精度丢失的原因, `JavaScript`可以存储的最大数字、最大安全数字, `JavaScript`处理大数字的方法、避免精度丢失的方法 528 | 529 | 0.1 + 0.2 !== 0.3 [详细讲解](https://juejin.im/post/5b90e00e6fb9a05cf9080dff) 530 | 531 | ### 7.== 和=== 532 | 533 | 对于 `==` 来说,如果对比双方的类型不一样的话,就会进行类型转换 534 | 535 | 假如我们需要对比 `x` 和 `y` 是否相同,就会进行如下判断流程: 536 | 537 | 1. 首先会判断两者类型是否相同。相同的话就是比大小了 538 | 539 | 2. 类型不相同的话,那么就会进行类型转换 540 | 541 | 3. 会先判断是否在对比 `null` 和 `undefined`,是的话就会返回 `true` 542 | 543 | 4. 判断两者类型是否为 `string` 和 `number`,是的话就会将字符串转换为 `number` 544 | 545 | ``` 546 | 1 == '1' 547 | ↓ 548 | 1 == 1 549 | ``` 550 | 551 | 5. 判断其中一方是否为 `boolean`,是的话就会把 `boolean` 转为 `number` 再进行判断 552 | 553 | ``` 554 | '1' == true 555 | ↓ 556 | '1' == 1 557 | ↓ 558 | 1 == 1 559 | ``` 560 | 561 | 6. 判断其中一方是否为 `object` 且另一方为 `string`、`number` 或者 `symbol`,是的话就会把 `object` 转为原始类型再进行判断 562 | 563 | ``` 564 | '1' == { name: 'tom' } 565 | ↓ 566 | '1' == '[object Object]' 567 | ``` 568 | 569 | 流程图: 570 | 571 | ![RxynGG](https://gitee.com/vr2/images/raw/master/uPic/RxynGG.png) 572 | 573 | 想了解更多的内容可以参考 [标准文档](https://link.juejin.im/?target=https%3A%2F%2Fwww.ecma-international.org%2Fecma-262%2F5.1%2F%23sec-11.9.1)。 574 | 575 | **对于** `===` **来说就简单多了,就是判断两者类型和值是否相同** 576 | 577 | ```javascript 578 | [] !== [] // true 579 | ``` 580 | 581 | 582 | 583 | ### 8.`var`、`let`、`const`区别 584 | 585 | #### var 586 | 587 | - `var` 命令会发生“变量提升”现象,即变量可以在声明之前使用,值为 `undefined` 。 588 | 589 | ```javascript 590 | console.log(name); 591 | var name = 'tom'; // undefined 592 | ``` 593 | 594 | - 内层变量可能覆盖外层变量 595 | 596 | ```javascript 597 | var name = 'tom'; 598 | { 599 | var name = 'TOM' 600 | } 601 | 602 | name // 'TOM' 603 | ``` 604 | 605 | - 用来计数的循环变量泄露为全局变量 606 | 607 | ```javascript 608 | for(var i = 0; i < 5; i++) {} 609 | ``` 610 | 611 | - `var`不存在块级作用域 612 | 613 | ```javascript 614 | { 615 | var name = 'tom'; 616 | } 617 | console.log(name); // tom 618 | 619 | ``` 620 | 621 | #### let 622 | 623 | - 声明的全局变量不会挂在顶层对象下面 624 | 625 | - `let`所声明的变量,只在`let`命令所在的代码块有效。 626 | 627 | ```javascript 628 | { 629 | let a = 1; 630 | var b = 2 631 | } 632 | a // ReferenceError: a is not defined. 633 | b // 2 634 | ``` 635 | 636 | - 变量的使用,一定要在声明前,否则会报引用错误(`ReferenceError`) 637 | 638 | ```javascript 639 | if (true) { 640 | let a; 641 | } 642 | console.log(a); // ReferenceError: a is not defined 643 | ``` 644 | 645 | - `let`和`const`不允许在相同作用域内,重复声明同一个变量。 646 | 647 | ```javascript 648 | //报错 649 | function func () { 650 | let name = 'tom'; 651 | var name = 'TOM' 652 | } 653 | 654 | // 报错 655 | function func () { 656 | let name = 'tom'; 657 | let name = 'TOM'; 658 | } 659 | ``` 660 | 661 | - 暂时性死区,只要块级作用域内存在 `let` 命令,它所声明的变量就“绑定”(` binding` )这个区域,不再受外部的影响,在代码块内,使用 `let` 命令声明变量之前,该变量都是不可用的。 662 | 663 | ```javascript 664 | var a = 1; 665 | if (true) { 666 | a = 2; // ReferenceError 667 | let a; 668 | } 669 | ``` 670 | 671 | #### cosnt 672 | 673 | - 声明的全局变量不会挂在顶层对象下面 674 | 675 | - `const` 声明之后必须马上赋值,否则会报错 676 | 677 | ```javascript 678 | const name //Uncaught SyntaxError: Missing initializer in const declaration 679 | ``` 680 | 681 | - `const`声明一个只读的常量。一旦声明,常量的值就不能改变。 682 | 683 | ```javascript 684 | const NAME = 'TOM' 685 | NAME = 'tom'; // TypeError: Assignment to constant variable. 686 | ``` 687 | 688 | - `const` 命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。 689 | 690 | ### 9.`var`、`let`、`const`原理 691 | 692 | > JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里。 693 | 694 | - `var` 695 | 696 | 会直接在栈内存里预分配内存空间,然后等到实际语句执行的时候,再存储对应的变量,如果传的是引用类型,那么会在堆内存里开辟一个内存空间存储实际内容,栈内存会存储一个指向堆内存的指针。 697 | 698 | - `let` 699 | 700 | 是不会在栈内存里预分配内存空间,而且在栈内存分配变量时,做一个检查,如果已经有相同变量名存在就会报错。 701 | 702 | - `const` 703 | 704 | 也不会预分配内存空间,在栈内存分配变量时也会做同样的检查。不过const存储的变量是不可修改的,对于基本类型来说你无法修改定义的值,对于引用类型来说你无法修改栈内存里分配的指针,但是你可以修改指针指向的对象里面的属性。 705 | 706 | 707 | 708 | -------------------------------------------------------------------------------- /其他/interview/javascript/2.原型和原型链.md: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------------- /其他/interview/javascript/call,apply.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsen1024/front-end-knowledge-systems/d5e4125399843b0ae59ad03cd55e5255423e01b9/其他/interview/javascript/call,apply.md -------------------------------------------------------------------------------- /其他/interview/javascript/原理以及实现api系列.md: -------------------------------------------------------------------------------- 1 | ## typeof原理 2 | 3 | ## instanceof原理 4 | 5 | `instanceof`可以准确的判断复杂数据类型,但是不能正确判断基本数据类型。 6 | 7 | `instanceof ` 是通过原型链判断的, `L instanceof R `, 在L的原型链中层层查找,是否有原型等于 `R.prototype `,如果一直找到A的原型链的顶端( `null `;即`Object.prototype.__proto__`),仍然不等于 `R.prototype `,那么返回 `false `,否则返回 `true `。 8 | 9 | ```javascript 10 | function instance_of (L, R) { 11 | var O = R.prototype, 12 | L = L.__proto__; 13 | while (true) { 14 | if (L === null) return false 15 | if (O === L) return true 16 | L = L.__proto__; 17 | } 18 | } 19 | ``` 20 | 21 | 22 | 23 | ## constructoer原理 24 | 25 | ## Object.prototype.toString()原理 26 | 27 | -------------------------------------------------------------------------------- /其他/interview/javascript/知识点.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /其他/interview/vue/1.响应式.md: -------------------------------------------------------------------------------- 1 | # vue2.x源码之响应式原理 2 | 3 | data 4 | 5 | 模板: 6 | 7 | ```vue 8 |

{{count}}

9 | ``` 10 | 11 | 数据变化: 12 | 13 | ```js 14 | this.count++ 15 | ``` 16 | 17 | 数据变化,视图会自动更新 18 | 19 | image-20210311134252070 20 | 21 | ## 侵入式和非侵入式 22 | 23 | Vue数据变化: 24 | 25 | ```js 26 | this.count++ 27 | ``` 28 | 29 | React数据变化 30 | 31 | ```'js 32 | this.setState({ 33 | count: this.state.count + 1 34 | }) 35 | ``` 36 | 37 | 小程序数据变化: 38 | 39 | ```js 40 | this.setData({ 41 | count: this.data.count + 1 42 | }) 43 | ``` 44 | 45 | 46 | 47 | vue是非侵入式: 48 | 49 | React和小程序是侵入式:setState不仅可以更新数据还可以更新视图。把视图的更新封装到了setState和setData里面 50 | 51 | ## `Object.defineProperty()` 52 | 53 | `Object.defineProperty`数据劫持(数据代理) 54 | 55 | > `**Object.defineProperty()**` 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。 56 | 57 | [mdn地址](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) 58 | 59 | 60 | 61 | ## 响应式 62 | 63 | ### `Observer` 64 | 65 | 将一个正常的object转换为每个层级的属性都是响应式(可以被侦测的)的object 66 | 67 | ```js 68 | var data = { 69 | name: 'tom', 70 | age: 19, 71 | o: { 72 | a: 1, 73 | b: 2 74 | c: {} 75 | } 76 | } 77 | ``` 78 | 79 | ![6H2l4B](https://gitee.com/vr2/images/raw/master/uPic/6H2l4B.png) 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /其他/interview/vue/new Vue.md: -------------------------------------------------------------------------------- 1 | # new Vue做了什么? 2 | 3 | ```javascript 4 | new Vue({ 5 | router, 6 | store, 7 | render: h => h(App) 8 | }).$mount("#app"); 9 | 10 | ``` 11 | 12 | `Vue` 实际上是一个类,类在 Javascript 中是用 Function 来实现的,来看一下源码,在`src/core/instance/index.js` 中 13 | 14 | ```javascript 15 | function Vue (options) { 16 | if (process.env.NODE_ENV !== 'production' && 17 | !(this instanceof Vue)) { 18 | warn('Vue is a constructor and should be called with the `new` keyword') 19 | } 20 | /*初始化*/ 21 | this._init(options) 22 | } 23 | ``` 24 | 25 | 通过`new`创建一个对象,调用`this._init`方法,`this._init` 是挂载在`Vue`的原型上。`this._init`方法在 26 | 27 | `src/core/instance/init.js` 中 28 | 29 | ```javascript 30 | Vue.prototype._init = function (options?: Object) { 31 | const vm: Component = this 32 | // a uid 33 | vm._uid = uid++ 34 | 35 | let startTag, endTag 36 | /* istanbul ignore if */ 37 | if (process.env.NODE_ENV !== 'production' && config.performance && mark) { 38 | startTag = `vue-perf-init:${vm._uid}` 39 | endTag = `vue-perf-end:${vm._uid}` 40 | mark(startTag) 41 | } 42 | 43 | // a flag to avoid this being observed 44 | /*一个防止vm实例自身被观察的标志位*/ 45 | vm._isVue = true 46 | // merge options 47 | if (options && options._isComponent) { 48 | // optimize internal component instantiation 49 | // since dynamic options merging is pretty slow, and none of the 50 | // internal component options needs special treatment. 51 | initInternalComponent(vm, options) 52 | } else { 53 | vm.$options = mergeOptions( 54 | resolveConstructorOptions(vm.constructor), 55 | options || {}, 56 | vm 57 | ) 58 | } 59 | /* istanbul ignore else */ 60 | if (process.env.NODE_ENV !== 'production') { 61 | initProxy(vm) 62 | } else { 63 | vm._renderProxy = vm 64 | } 65 | // expose real self 66 | vm._self = vm 67 | /*初始化生命周期*/ 68 | initLifecycle(vm) 69 | /*初始化事件*/ 70 | initEvents(vm) 71 | /*初始化render*/ 72 | initRender(vm) 73 | /*调用beforeCreate钩子函数并且触发beforeCreate钩子事件*/ 74 | callHook(vm, 'beforeCreate') 75 | initInjections(vm) // resolve injections before data/props 76 | /*初始化props、methods、data、computed与watch*/ 77 | initState(vm) 78 | initProvide(vm) // resolve provide after data/props 79 | /*调用created钩子函数并且触发created钩子事件*/ 80 | callHook(vm, 'created') 81 | 82 | /* istanbul ignore if */ 83 | if (process.env.NODE_ENV !== 'production' && config.performance && mark) { 84 | /*格式化组件名*/ 85 | vm._name = formatComponentName(vm, false) 86 | mark(endTag) 87 | measure(`${vm._name} init`, startTag, endTag) 88 | } 89 | 90 | if (vm.$options.el) { 91 | /*挂载组件*/ 92 | vm.$mount(vm.$options.el) 93 | } 94 | } 95 | } 96 | ``` 97 | 98 | _init主要做了这两件事: 99 | 100 | 1.合并配置,初始化化生命周期,初始化事件,初始化渲染,初始化state(data、props、computed、watcher)等 101 | 102 | 2.$mount组件。 103 | 104 | 在生命钩子beforeCreate与created之间会初始化state,在此过程中,会依次初始化props、methods、data、computed与watch,这也就是Vue.js对options中的数据进行“响应式化”(即双向绑定)的过程。 105 | 106 | 107 | 108 | 参考 109 | 110 | > [Vue.js 技术揭秘](https://ustbhuangyi.github.io/vue-analysis/) 111 | 112 | -------------------------------------------------------------------------------- /其他/interview/work/c.md: -------------------------------------------------------------------------------- 1 | ### 前端存在的历史问题 2 | 3 | ![x8CxMk](https://gitee.com/vr2/images/raw/master/uPic/x8CxMk.png) 4 | 5 | 1. 1v1页面taro跟单独方案(taro/plans)页面,是2个工程,有新相同的需求需要维护2套工程 6 | 2. (c端)技术栈不统一,新来人员无法快速融入业务开发,增加成本 7 | 8 | 9 | 10 | 11 | 12 | ### 需求开发存在的问题: 13 | 14 | #### **转介绍活动** 15 | 16 | **1.存在的问题**: 17 | 18 | - 如果在原有的1v1页面上开发,代码过于臃肿; 19 | - 如果增加单独的活动,每个活动上线,都得重新部署 20 | - 在原有的1v1上修改,后续项目改造,得重新写代码,无法共用。 21 | 22 | **2.解决的方案**: 23 | 24 | - 转介绍活动过,通过新加工程开发 25 | 26 | **3.成本:** 27 | 28 | - 容器成本 29 | - 项目前期需要花费时间,搭建项目 30 | - 测试需要全部走流程 31 | 32 | **4.好处** 33 | 34 | - 所有的活动项目可以在同一的工程上开发 35 | - 支持单独部署,影响用户体验较少 36 | - 代码不会混在一起,代码优雅 37 | - 业务统一 38 | 39 | 40 | 41 | #### 问卷优化 42 | 43 | **1.存在的问题:** 44 | 45 | - 问卷页面需要修改 46 | - 单独方案页需要修改 47 | - 1v1方案页需要修改 48 | 49 | **2.解决方案** 50 | 51 | - 新增工程 52 | 53 | - 将单独方案页, 1v1和问卷优化工程融合到一起 54 | 55 | **3.好处** 56 | 57 | - 维护成功提高 58 | - 改善用户体验 59 | 60 | 61 | 62 | 63 | 64 | ### 总体方案 65 | 66 | 方案1: 67 | 68 | ![EnFB0g](https://gitee.com/vr2/images/raw/master/uPic/EnFB0g.png) 69 | 70 | 71 | 72 | - 将1v1,问卷,单独方案页融合在一个工程,独立维护,作为一个主应用 73 | - 转介绍(其他)活动做为一个1应用,单独维护,部署。 74 | 75 | **方案2:** 76 | 77 | ![2GJ8SI](https://gitee.com/vr2/images/raw/master/uPic/2GJ8SI.png) 78 | 79 | 80 | 81 | - 将1v1作为一个主应用,问卷和转介绍活动,拆分成不用子应用,支持主,子应用相互跳转 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /前端工程化/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsen1024/front-end-knowledge-systems/d5e4125399843b0ae59ad03cd55e5255423e01b9/前端工程化/.DS_Store -------------------------------------------------------------------------------- /前端工程化/cli/脚手架开发.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | # 脚手架开发 4 | 5 | ## 一、脚手架 6 | 7 | 脚手架 CLI = Command-Line Interface 8 | 9 | 基于文本界面 通过键盘输入命令执行。 10 | 11 | 常用的 cli 有:`webpack-cli` 、`vue-cli` 、`create-react-app` 12 | 13 | 以 vue-cli 为例子 14 | 15 | ```bash 16 | iMac-Pro ~ % vue 17 | 18 | Usage: vue [options] 19 | 20 | Options: 21 | -V, --version output the version number 22 | -h, --help display help for command 23 | 24 | Commands: 25 | create [options] create a new project powered by vue-cli-service 26 | add [options] [pluginOptions] install a plugin and invoke its generator in an already created project 27 | invoke [options] [pluginOptions] invoke the generator of a plugin in an already created project 28 | inspect [options] [paths...] inspect the webpack config in a project with vue-cli-service 29 | serve alias of "npm run serve" in the current project 30 | build alias of "npm run build" in the current project 31 | ui [options] start and open the vue-cli ui 32 | init [options]