2019中高级前端秘籍之JavaScript篇


2019中高级前端秘籍之CSS篇
2019中高级前端秘籍之JavaScript篇
2019中高级前端秘籍之浏览器篇
2019中高级前端秘籍之服务端与网络篇
2019中高级前端秘籍之算法篇


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 === 构造函数

放大来看,我画了张图供大家彻底理解:


实例原型构造函数.png

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. 作用域

执行上下文中还包含作用域链。理解作用域之前,先介绍下作用域。作用域其实可理解为该上下文中声明的 变量和声明的作用范围。可分为 块级作用域函数作用域
特性:
    声明提前: 一个声明在函数体内都是可见的, 函数优先于变量
    非匿名自执行函数,函数变量为 只读 状态,无法修改

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 时,则返回该值)

11. instanceof原理

能在实例的 原型对象链 中找到该构造函数的prototype属性所指向的 原型对象,就返回true。即:

// __proto__: 代表原型对象链
instance.[__proto__...] === instance.constructor.prototype

// return true

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 、+undefinedNaN

15. 类型判断

判断 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
}

16. 模块化

模块化开发在现代开发中已是必不可少的一部分,它大大提高了项目的可维护、可拓展和可协作性。通常,我们 在浏览器中使用 ES6 的模块化支持,在 Node 中使用 commonjs 的模块化支持。

分类:

    es6: import / export
    commonjs: require / module.exports / exports
    amd: require / defined

require与import的区别

    require支持 动态导入,import不支持,正在提案 (babel 下可支持)
    require是 同步 导入,import属于 异步 导入
    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执行一次即可。

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),是将代码逐字母解析成 树状对象 的形式。这是语言之间的转换、代码语法检查,代码风格检查,代码格式化,代码高亮,代码错误提示,代码自动补全等等的基础。例如:

function square(n){
    return n * n
}

通过解析转化成的AST如下图:


AST.png

21. babel编译原理

babylonES6/ES7 代码解析成 AST
babel-traverseAST 进行遍历转译,得到新的 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/catchthrow 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 )
}

作者:郭东东
链接:https://juejin.im/post/5c64d15d6fb9a049d37f9c20
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268