二.[前端总结]之JavaScript篇
1.原型 / 构造函数 / 实例
原型( prototype ): ⼀个简单的对象,⽤于实现对象的 属性继承。可以简单的理解成对象的爹。在 Firefox 和 Chrome 中,每个 JavaScript 对象中都包含⼀个 proto (⾮标准)的属性指向它爹(该对象的原型),可 obj. proto 进⾏访问。
构造函数: 可以通过 new 来 新建⼀个对象 的函数。
实例: 通过构造函数和 new 创建出来的对象,便是实例。 实例通过 proto 指向原型,通过 constructor 指向构造函数。
以 Object 为例,我们常⽤的 Object 便是⼀个构造函数,因此我们可以通过它构建实例。
// 实 例
const instance = new Object()
则此时, 实例为 instance , 构造函数为 Object ,我们知道,构造函数拥有⼀个 prototype 的属性指向原型,因此原型为:
// 原 型
const prototype = Object.prototype
这⾥我们可以来看出三者的关系:
实例. proto === 原 型
原型.constructor === 构造函数构造函数.prototype === 原型
// 这条线其实是是基于原型进⾏获取的,可以理解成⼀条基于原型的映射线 // 例如: // const o = new Object() // o.constructor === Object --> true // o. proto = null; // o.constructor === Object --> false 实例.constructor === 构造函数
2.原型链:
原型链是由原型对象组成,每个对象都有 proto 属性,指向了创建该对象的构造函数的原型, proto 将对象连接起来组成了原型链。是⼀个⽤来实现继承和共享属性的有限的对象链。
属性查找机制: 当查找对象的属性时,如果实例对象⾃身不存在该属性,则沿着原型链往上⼀级查找,找到时则输出,不存在时,则继续沿着原型链往上⼀级查找,直⾄最顶级的原 型对象 Object.prototype ,如还是没找到,则输出 undefined ;
属性修改机制: 只会修改实例对象本身的属性,如果不存在,则进⾏添加该属性,如果需要修改原型的属性时,则可以⽤: b.prototype.x = 2 ;但是这样会造成所有继承于该对象的实例的属性发⽣改变。
3.执⾏上下⽂(EC)
执⾏上下⽂可以简单理解为⼀个对象:
它包含三个部分:
变量对象( VO )
作⽤域链(词法作⽤域)
this 指 向
它的类型:
全局执⾏上下⽂
函数执⾏上下⽂
eval 执⾏上下⽂
代码执⾏过程:
创建 全局上下⽂ ( global EC )
全局执⾏上下⽂ ( caller ) 逐⾏ ⾃上⽽下 执⾏。遇到函数时,函数执⾏上下⽂( callee ) 被 push 到执⾏栈顶层
函数执⾏上下⽂被激活,成为 active EC , 开始执⾏函数中的代码, caller 被挂起函数
执⾏完后, callee 被 pop 移除出执⾏栈,控制权交还全局上下⽂ ( caller ),继续执⾏
4.变量对象
变量对象,是执⾏上下⽂中的⼀部分,可以抽象为⼀种 数据作⽤域,其实也可以理解为就是⼀个简单的对象,它存储着该执⾏上下⽂中的所有 变量和函数声明(不包含函数表达
式)。
活动对象 ( AO ): 当变量对象所处的上下⽂为 active EC 时,称为活动对象。
5. 作⽤域
执⾏上下⽂中还包含作⽤域链。理解作⽤域之前,先介绍下作⽤域。作⽤域其 实可理解为该上下⽂中声明的 变量和声明的作⽤范围。可分为 块级作⽤域 和函数作⽤域
特性:
声明提前: ⼀个声明在函数体内都是可⻅的, 函数优先于变量
⾮匿名⾃执⾏函数,函数变量为 只读 状态,⽆法修改
js
let foo = function() { console.log(1) } (function foo() { foo = 10 // 由于foo在函数中只为可读,因此赋值⽆效 console.log(foo) }()) // 结果打印: ƒ foo() { foo = 10 ; console.log(foo) }
6. 作⽤域链
我们知道,我们可以在执⾏上下⽂中访问到⽗级甚⾄全局的变量,这便是作⽤ 域链的功劳。作⽤域链可以理解为⼀组对象列表,包含 ⽗级和⾃身的变量对象,因此我们便能通过作⽤域链访问到⽗级⾥声明的变量或者函数。
由两部分组成:
[[scope]] 属性: 指向⽗级变量对象和作⽤域链,也就是包含了⽗级的 [[scope]] 和 AO AO : ⾃身活动对象
如此 [[scopr]] 包含 [[scope]] ,便⾃上⽽下形成⼀条 链式作⽤域。
7. 闭包
闭包属于⼀种特殊的作⽤域,称为 静态作⽤域。它的定义可以理解为: ⽗函数被销毁 的情况下,返回出的⼦函数的 [[scope]] 中仍然保留着⽗级的单变量对象和作⽤域链,因此可以继续访问到⽗级的变量对象,这样的函数称为闭
。
闭包会产⽣⼀个很经典的问题:
多个⼦函数的 [[scope]] 都是同时指向⽗级,是完全共享的。因此当⽗级的变量对象被修改时,所有⼦函数都受到影响。
••解决:**
变量可以通过 函数参数的形式 传⼊,避免使⽤默认的 [[scope]] 向上查找
使⽤ setTimeout 包裹,通过第三个参数传⼊
使⽤ 块级作⽤域,让变量成为⾃⼰上下⽂的属性,避免共享
8.script 引⼊⽅式:
html 静 态 <script> 引 ⼊
js 动态插⼊ <script>
<script defer> : 异步加载,元素解析完成后执⾏
<script async> : 异步加载,但执⾏时会阻塞元素渲染
9. 对象的拷⻉
浅拷⻉: 以赋值的形式拷⻉引⽤对象,仍指向同⼀个地址,修改时原对象也会受到影响
Object.assign
展开运算符( ... )
深拷⻉: 完全拷⻉⼀个新对象,修改时原对象不再受到任何影响
JSON.parse(JSON.stringify(obj)) : 性能最快具有循环引⽤的对象时,报错
当值为函数、 undefined 、或 symbol 时,⽆法拷⻉递归进⾏逐⼀赋值
10.new运算符的执⾏过程
新⽣成⼀个对象
链接到原型: obj. proto = Con.prototype
绑 定 this: apply
返回新对象(如果构造函数有⾃⼰ retrun 时,则返回该值)
new运算符的执⾏过程
新⽣成⼀个对象
链接到原型: obj. proto = Con.prototype
绑 定 this: apply
返回新对象(如果构造函数有⾃⼰ retrun 时,则返回该值)
11. instanceof原理
能在实例的 原型对象链 中找到该构造函数的 prototype 属性所指向的 原型对象,就返回 true 。即:
// proto : 代表原型对象链 instance.[ proto ...] === instance.constructor.prototype // return true
j
12. 代码的复⽤
当你发现任何代码开始写第⼆遍时,就要开始考虑如何复⽤。⼀般有以下的⽅ 式:
函数封装继承
复 制 extend
混⼊ mixin
借 ⽤ apply/call
13. 继承
在 JS 中,继承通常指的便是 原型链继承,也就是通过指定原型,并可以通过原型链继承原型上的属性或者⽅法。
最优化: 圣杯模式
var inherit = (function(c,p){ var F = function(){}; return function(c,p){ F.prototype = p.prototype; c.prototype = new F(); c.uber = p.prototype; c.prototype.constructor = c; } })();
使⽤ ES6 的语法糖 class / extends
14. 类型转换
⼤家都知道 JS 中在使⽤运算符号或者对⽐符时,会⾃带隐式转换,规则如下:
-、*、/、% :⼀律转换成数值后计算
+:
数字 + 字符串 = 字符串, 运算顺序是从左到右
数字 + 对象, 优先调⽤对象的 valueOf -> toString
数字 + boolean/null -> 数字数 字 + undefined -> NaN
[1].toString() === '1'
{}.toString() === '[object object]' NaN !== NaN 、+ undefined 为 NaN
15. 类型判断
判断 Target 的类型,单单⽤ typeof 并⽆法完全满⾜,这其实并不是bug ,本质原因是 JS 的万物皆对象的理论。因此要真正完美判断时,我们需要区分对待:
基本类型( null ): 使⽤ String(null)
基本类型( string / number / boolean / undefined ) + function : - 直接使⽤typeof 即 可
其余引⽤类型( Array / Date / RegExp Error ): 调⽤ toString 后根据 [object XXX] 进⾏判断
很稳的判断封装:
j
let class2type = {} 'Array Date RegExp Object Error'.split(' ').forEach(e => class2type[ '[obje function type(obj) { if (obj == null) return String(obj) return typeof obj === 'object' ? class2type[Object.prototype.toString. }
16. 模块化
模块化开发在现代开发中已是必不可少的⼀部分,它⼤⼤提⾼了项⽬的可维护、可拓展和可协作性。通常,我们 在浏览器中使⽤ ES6 的模块化⽀持,在Node 中使⽤ commonjs 的模块化⽀持。
分类:
es6: import / export
commonjs: require / module.exports / exports
amd: require / defined
require与import的区别
require ⽀持 动态导⼊, import 不⽀持,正在提案 ( babel 下可⽀持)
require 是 同步 导⼊, impor t属于 异步 导⼊
require 是 值拷⻉,导出值变化不会影响导⼊值; import 指向 内存地址,导⼊值会随导出值⽽变化
17. 防抖与节流
防抖与节流函数是⼀种最常⽤的 ⾼频触发优化⽅式,能对性能有较⼤的帮助。
防抖 (debounce): 将多次⾼频操作优化为只在最后⼀次执⾏,通常使⽤的场景是:⽤户输
⼊,只需再输⼊完成后做⼀次输⼊校验即可。
function debounce(fn, wait, immediate) { let timer = null return function() { let args = arguments let context = this if (immediate && !timer) { fn.apply(context, args) } if (timer) clearTimeout(timer) timer = setTimeout(() => { fn.apply(context, args) }, wait) } }
节流(throttle): 每隔⼀段时间后执⾏⼀次,也就是降低频率,将⾼频操作优化成低频操作, 通常使⽤场景: 滚动条事件 或者 resize 事件,通常每隔 100~500 ms 执⾏⼀次即可。
js
function throttle(fn, wait, immediate) { let timer = null let callNow = immediate return function() { let context = this, args = arguments if (callNow) { fn.apply(context, args) callNow = false } if (!timer) { timer = setTimeout(() => { fn.apply(context, args) timer = null }, wait) } } }
18.函数执⾏改变this
由于 JS 的设计原理: 在函数中,可以引⽤运⾏环境中的变量。因此就需要⼀个机制来让我们可以在函数体内部获取当前的运⾏环境,这便是 this 。
因此要明⽩ this 指向,其实就是要搞清楚 函数的运⾏环境,说⼈话就是, 谁调⽤了函数 例如
obj.fn() ,便是 obj 调⽤了函数,既函数中的 this === obj fn() ,这⾥可以看成 window.fn() ,因此 this === window
但这种机制并不完全能满⾜我们的业务需求,因此提供了三种⽅式可以⼿动修 改 this 的 指 向 :
call: fn.call(target, 1, 2)
apply: fn.apply(target, [1, 2])
bind: fn.bind(target)(1,2)
19. ES6/ES7
由于 Babel 的强⼤和普及,现在 ES6/ES7 基本上已经是现代化开发的必备了。通过新的语法糖,能让代码整体更为简洁和易读。
声明
let / const : 块级作⽤域、不存在变量提升、暂时性死区、不允许重复声明
const : 声明常量,⽆法修改
解构赋值
class / extend: 类声明与继承
Set / Map: 新的数据结构
异步解决⽅案:
Promise 的使⽤与实现
generator :
yield : 暂停代码
next() : 继续执⾏代码
function* helloWorld() { yield 'hello'; yield 'world'; return 'ending'; } const generator = helloWorld(); generator.next() // { value: 'hello', done: false } generator.next() // { value: 'world', done: false } generator.next() // { value: 'ending', done: true } generator.next() // { value: undefined, done: true } await / async : 是 generator 的语法糖, babel 中是基于 promise 实现。 async function getUserByAsync(){ let user = await fetchUser(); return user; } const user = await getUserByAsync() console.log(user)
20. AST
抽象语法树 ( Abstract Syntax Tree ),是将代码逐字⺟解析成 树状对象 的形式。这是语⾔之间的转换、代码语法检查,代码⻛格检查,代码格式化,代 码⾼亮,代码错误提示,代码⾃动补全等等的基础。例如:
js
function square(n){ return n * n }
通过解析转化成的AST如下图:
21.babel编译原理
babylon 将 ES6/ES7 代码解析成 AST
babel-traverse 对 AST 进⾏遍历转译,得到新的 AST
新 AST 通过 babel-generator 转换成 ES5
22. 函数柯⾥化
在⼀个函数中,⾸先填充⼏个参数,然后再返回⼀个新的函数的技术,称为函 数的柯⾥化。通常可⽤于在不侵⼊函数的前提下,为函数 预置通⽤参数,供多次重复调⽤。
const add = function add(x) { return function (y) { return x + y } } const add1 = add(1) add1(2) === 3 add1(20) === 21
23.数组(array)
map : 遍历数组,返回回调返回值组成的新数组
forEach : ⽆法 break ,可以⽤ try/catch 中 throw new Error 来停⽌
filter : 过滤
some : 有⼀项返回 true ,则整体为 true
every : 有⼀项返回 false ,则整体为 false
join : 通过指定连接符⽣成字符串
push / pop : 末尾推⼊和弹出,改变原数组, 返回推⼊/弹出项
unshift / shift : 头部推⼊和弹出,改变原数组,返回操作项
sort(fn) / reverse : 排序与反转,改变原数组
concat : 连接数组,不影响原数组, 浅拷⻉
slice(start, end) : 返回截断后的新数组,不改变原数组
splice(start, number, value...) : 返回删除元素组成的数组, value 为插⼊项,改
变原数组
indexOf / lastIndexOf(value, fromIndex) : 查找数组项,返回对应的下标
reduce / reduceRight(fn(prev, cur) , defaultPrev) : 两两执⾏, prev 为上次化简函数的 return 值, cur 为当前值(从第⼆项开始)
数组乱序:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; arr.sort(function () { return Math.random() - 0.5; });
数组拆解: flat: [1,[2,3]] --> [1, 2, 3]
Array.prototype.flat = function() { this.toString().split(',').map(item => +item ) }
j