JavaScript
原文链接https://juejin.im/post/5c64d15d6fb9a049d37f9c20
1. 原型/构造函数/实例
原型:一个简单对象,用于实现对象的继承属性。
构造函数:通过 new 来新建一个对象的函数。
实例:通过构造函数和 new 创建出来的对象。
2. 原型链
- 原型链是由原型组成。是一个用来实现继承和共享属性的有限的对象链。
- 属性查找机制:当查找对象的属性是,如果实例对象自身不存在该属性,则沿着原型链往上一级查找,直至最顶级的 Object.prototype。如果没找到,则输出
undefined
。 - 属性修改机制:只会修改实例对象本身的属性,不存在则添加,存在则修改。
3. 执行上下文
执行上下文可以简单理解成一个对象:
-
它包含三部分:
- 变量对象(VO)
- 作用域链(词法作用域)
-
this
指向
-
它的类型
- 全局执行上下文
- 函数执行上下文
-
eval
执行上下文
-
代码执行过程
- 创建 全局上下文(global EC)
- 全局执行上下文(caller)逐行 自上而下 执行。遇到函数时,函数执行上下文(callee) 被
push
到执行栈顶层。 - 函数执行上下文被激活,成为 active EC,开始执行函数和的代码,caller 被挂起。
- 函数执行完后,callee 被
pop
移除执行栈,控制全交换全局上下文(caller),继续执行。
3.1 变量对象
变量对象:是执行上下文中的一部分,可以抽象为一种数据作用域,其实也可以理解为就是一个简单的对象,它储存着该执行上下文中的所有 变量和函数声明(不包括函数表达式)。
活动对象(AO):当变量对象所处的上下文为 active EC 时,称为活动对象。
3.2 作用域
作用域 可以理解为该上下文声明的 变量和声明的作用范围。可分为 块级作用域和函数作用域。
特性:
- 提前声明:一个声明在函数体内都是可见的,函数优先于变量
- 费匿名自执行函数,函数变量为 只读状态,无法修改。
4. 闭包
闭包属于一种特殊的作用域,成为静态作用域。
它可以理解为:父函数被销毁的情况下,返回出的子函数中仍然保留着父级作用域的变量和作用域,因此可以继续访问到父级的变量对象,这样的函数成为闭包。
- 闭包会产生的问题:
多个子函数都同时指向父级,父级的状态是完全共享的。一次当父级的变量对象被修改是,所有子函数都受到影响。
5. script 引入方式
- html 静态
引入
- js 动态插入
-
: 延迟加载,元素解析完成后执行
-
: 异步加载,但执行时会阻塞元素渲染。
6. 对象拷贝
- 浅拷贝:以赋值的形式拷贝引用对象,仍指向同一个地址,修改时原对象也会收到影响
Object.assign
- 展开运算符(...)
深拷贝:完全拷贝一个新对象,修改是原对象不再受到任何影响
JSON,parse(JSON.stringify(obj))
具有循环引用时报错。
当值为函数,undefined
,或symbol
时,无法拷贝递归进行逐一赋值
7. new 运算符的执行过程
- 生成一个新对象
- 链接到原型
obj.__proto__
= Con.prototype - 使用
apply
进行 this 绑定。 - 返回新对象(如果构造函数有自己 return 时,则返回改值)
8. instanceof 原理
instanceof 主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false。
function instanceof(left, right) {
const rightVal = right.prototype
const leftVal = left.__proto__
// 若找不到就到一直循环到父类型或祖类型
while(true) {
if (leftVal === null) {
return false
}
if (leftVal === rightVal) {
return true
}
leftVal = leftVal.__proto__ // 获取祖类型的__proto__
}
}
9. 代码复用
当代码需要重复书写时,就要考虑如何复用,一般有以下几种方式:
- 函数封装
- 继承
extend
10. 继承
在 js 中,继承通常指的是原型链继承,指的是通过原型链继承原型上的属性或者方法。
- 最优化:神杯模式
var inherit = (
function(c,p){
var F = function(){}
return function(c,p){
F.prototype = p.prototype;
c.prototype = new F();
c.xxx = p.prototype;
c.prototype.constructor = c;
}
}
)()
- 使用 ES6 的语法糖
class/extends
11. 类型转换
- 减(-)乘(*)取商(/)取模(%):一律转换成数值后计算
- 加(+):
数字 + 字符串 = 字符串
数字 + 对象,优先调用对象 valueOf > toString
数字 + boolean/null -> 数字
数字 + undefined -> NaN -
[1].toString
=== '1' {}.toString() === '[object object]'
-
NaN !== Nan
+undefined 为 NaN
12. 类型判断
判断 Target 的类型,单单使用 typeof 无法满足,这其实不是 bug,本质因为 JS 的万物皆对象导致的。因此需要区分对待:
- 基本类型(
null
):使用String(null)
- 基本类型(
string/number/boolean/undefined
) +function
:直接使用typeof
即可 - 其余引用类型(
Array/Date/RegExp/Error
):调用toString
后根据[object xxx]
进行判断
let class2Type = {};
'Array Date RegExp Object Error'.split(' ').forEach(e => {class2Type[`[object ${e}]`] = e.toLowerCase()})
function type(obj) {
if (obj == null) return String(obj)
return typeof obj === 'object' ? class2type[ Object.prototype.toString.call(obj) ] || 'object' : typeof obj
}
13. 模块化
在浏览器中使用 ES6 的模块化支持,在 Nodejs 中使用 commonjs 的模块化支持。
-
分类
- es6:
import/expoer
- commonjs:
require/module.exports/exports
- amd:
require/define
- es6:
-
require
与import
的区别
1.require
支持动态导入,import
不支持,正在提案(babel下可支持)-
require
是同步导入,import
属于异步导入。 -
require
是 值拷贝,到处变化不会影响导入值;import
指向内存地址,导入值会随导出值而变化。
-
14. 节流和防抖
节流和防抖函数是一种常用的 高频触发优化方式,对性能提高有较大帮助。
- 防抖(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 执行一次即可。
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)
}
}
}
15. 函数执行改变 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)
16. ES6/ES7
由于 Babel 的强大和普及,现在 ES6/ES7 基本已经是现代开发的必备了。通过新的语法糖,能让代码整体更为简洁和易读。
- 声明:
-
let/const
: 块级作用域,不存在变量声明提升,暂时性死去,不允许重复声明 -
const
: 声明常亮,无法修改
-
- 解构赋值
-
class/extends
: 类声明与继承 -
Set/Map
:新的数据解构 - 异步解决方案:
-
promise
的实现和使用 -
generator
:
-
yield
: 暂停代码 -
next()
: 继续执行代码
-
await/async
: 是generator
的语法糖,babel 中式基于promise
实现。
-
17. AST(抽象语法树)
抽象语法树(Abstract Syntax Tree)是将代码逐行字母解析成 树状对象的形式。这是语言之间的转换,代码语法检测,代码风格检测,代码格式化,代码高亮,代码错误提示,代码自动补全等等的基础。例如:
[图片上传失败...(image-e38f13-1581929094976)]
18. babel 编译原理
- babylon 将 ES6/ES7 代码解析成 AST
- babel-traverse 对 AST 进行遍历转义,得到新的 AST
- 新 AST 通过 babel-generator 转换成 ES5
19. 函数柯里化
在一个函数中,首先填充几个参数,然后再返回一个新的函数的技术,成为函数的柯里化。通常可用于在不侵入函数的前提下,为函数预设通用参数,供多次重复使用。
const add = function add(x) {
return function (y) {
return x + y
}
}
const add1 = add(1)
add1(2) === 3
add1(20) === 21
- 数组(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() {
return this.toString().split(',').map(item => +item )
}