https://github.com/KieSun/Front-end-knowledge
目前自己组建的一个团队正在写一份面试图谱,将会在七月中旬开源。内容十分丰富,第一版会开源前端方面知识和程序员必备知识,后期会逐步写入后端方面知识。因为工程所涉及内容太多(目前已经写了一个半月),并且还需翻译成英文,所以所需时间较长。有兴趣的同学可以 Follow 我的 Github 得到最快的更新消息。
JS 相关
CSS
React
- React源码解析(一):组件的实现与挂载
- React源码解析(二):组件的类型与生命周期
- React源码解析(三):详解事务与更新队列
- React源码解析(四):事件系统
- Beyond React 16 by Dan Abramov
- React.js 小书
- SetState
面试
- 技术面试需要掌握的基础知识整理
- 2016十家公司前端面试小记
- 记录面试中一些回答不够好的题(Vue 居多)
- 收集的前端面试题和答案
- 前端笔试题面试题记录
- 2018阿里巴巴前端面试总结
- 前端面试考点多?看这些文章就够了
Node
其他
- 可能是让你受益匪浅的英语进阶指南
- Webapck4 教程
- 查看网站使用了哪些技术栈
- 什么是 PWA
- 区块链集合
- 系统设计
- 网络编程懒人入门系列文章
- 小程序插件开发
- 前端工程师手册
- 精读《webpack4.0 升级指南》
- 浏览器的工作原理
- ES8 & ES9
JS 相关
- jsonp 的原理与实现
- 关于不可变对象,比较通俗易懂
- 前端工程师为什么要学习编译原理
- JS 多线程
- JavaScript闭包的底层运行机制
- 前端数据结构与算法
- ES7, 8, 9 *
- [1] + [2] - [3] === 9
- 用 Webpack 写出更牛逼的 JS
- Node 事件循环解析 *
- 学习如何使用 Chomre Devtools Debug JS *
- 深入剖析 JavaScriptCore
- 几个 JS 新特性 *
- 超大型 JavaScript 应用的设计哲学
- 60多篇 JS 文章 *
CSS
Webpack
框架
- 如何在2018年学习React,Angular或Vue?
- Medium上精选的200篇JS的文章
- 我在开发React-native/Redux应用过程中犯的11个错误
- RN学习指南
- React 16.3 更新
- Mvvm 前端数据流框架精讲
- 为什么Vue使用异步更新队列?
- 从setState promise化的探讨 体会React团队设计思想
- React 性能优化 *
- React 的一本开源书籍
面试
其他
- JavaScript 是如何工作的(该系列文章很值得阅读)
- 写一个自己的库
- Top 10 JavaScript errors 英文
- 现代浏览器支持的不同类型的观察者
- async await 是把双刃剑
- JS 字符串模板
- ES 2018 中正则表达式的更新 英文*
- DOM系列:DOM树和遍历DOM
- DOM系列:getElement 和 querySelector)
CSS
Webpack
框架
其他
- Web 安全 英文*
- 搞懂 MVC、SPA 與 SSR 技术名词 英文*
- 优化静态资源 英文*
- Web performance made easy (Google I/O '18) 英文*
- 前端工程师的技术进阶点在哪里?
- 高级前端专家是如何进阶的?
Github 仓库推荐
- BigInt
- Deno 并不是下一代 Node.js
- 你需要了解的23种JavaScript设计模式
- 聊一聊前端「同构」
- 网站性能优化实战
- JS 参数传递
- 前端进阶必备:JavaScript 内存机制
- [译] ES2018(ES9)的新特性
CSS
框架
- 深入框架本源系列 —— Virtual Dom
- 打包工具原理
- 从0到1手写一个vuejs
- 现代 js 框架存在的根本原因
- Vue 技术内幕
- 前端路由简介以及vue-router实现原理
- React 复合组件
- QQ音乐:React v16 新特性实践
- 如何管理好10万行代码的前端单页面应用
- 一个Vue页面的内存泄露分析
- Awesome React Native
Github
其他
部分内容预览
MVVM
MVVM 由以下三个内容组成
- View:界面
- Model:数据模型
- ViewModel:作为桥梁负责沟通 View 和 Model
在 JQuery 时期,如果需要刷新 UI 时,需要先取到对应的 DOM 再更新 UI,这样数据和业务的逻辑就和页面有强耦合。
在 MVVM 中,UI 是通过数据驱动的,数据一旦改变就会相应的刷新对应的 UI,UI 如果改变,也会改变对应的数据。这种方式就可以在业务处理中只关心数据的流转,而无需直接和页面打交道。ViewModel 只关心数据和业务的处理,不关心 View 如何处理数据,在这种情况下,View 和 Model 都可以独立出来,任何一方改变了也不一定需要改变另一方,并且可以将一些可复用的逻辑放在一个 ViewModel 中,让多个 View 复用这个 ViewModel。
在 MVVM 中,最核心的也就是数据双向绑定,例如 Angluar 的脏数据检测,Vue 中的数据劫持。
脏数据检测
当触发了指定事件后会进入脏数据检测,这时会调用 $digest
循环遍历所有的数据观察者,判断当前值是否和先前的值有区别,如果检测到变化的话,会调用 $watch
函数,然后再次调用 $digest
循环直到发现没有变化。循环至少为二次 ,至多为十次。
脏数据检测虽然存在低效的问题,但是不关心数据是通过什么方式改变的,都可以完成任务,但是这在 Vue 中的双向绑定是存在问题的。并且脏数据检测可以实现批量检测出更新的值,再去统一更新 UI,大大减少了操作 DOM 的次数。所以低效也是相对的,这就仁者见仁智者见智了。
数据劫持
Vue 内部使用了 Obeject.defineProperty()
来实现双向绑定,通过这个函数可以监听到 set
和 get
的事件。
var data = { name: 'yck' }
observe(data)
let name = data.name // -> get value
data.name = 'yyy' // -> change value
function observe(obj) {
// 判断类型
if (!obj || typeof obj !== 'object') {
return
}
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key])
})
}
function defineReactive(obj, key, val) {
// 递归子属性
observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log('get value')
return val
},
set: function reactiveSetter(newVal) {
console.log('change value')
val = newVal
}
})
}
复制代码
以上代码简单的实现了如何监听数据的 set
和 get
的事件,但是仅仅如此是不够的,还需要在适当的时候给属性添加发布订阅
<div>
{{name}}
</div>
复制代码
在解析如上模板代码时,遇到 {{name}}
就会给属性 name
添加发布订阅。
// 通过 Dep 解耦
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
// sub 是 Watcher 实例
this.subs.push(sub)
}
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
// 全局属性,通过该属性配置 Watcher
Dep.target = null
function update(value) {
document.querySelector('div').innerText = value
}
class Watcher {
constructor(obj, key, cb) {
// 将 Dep.target 指向自己
// 然后触发属性的 getter 添加监听
// 最后将 Dep.target 置空
Dep.target = this
this.cb = cb
this.obj = obj
this.key = key
this.value = obj[key]
Dep.target = null
}
update() {
// 获得新值
this.value = this.obj[this.key]
// 调用 update 方法更新 Dom
this.cb(this.value)
}
}
var data = { name: 'yck' }
observe(data)
// 模拟解析到 `{{name}}` 触发的操作
new Watcher(data, 'name', update)
// update Dom innerText
data.name = 'yyy'
复制代码
接下来,对 defineReactive
函数进行改造
function defineReactive(obj, key, val) {
// 递归子属性
observe(val)
let dp = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log('get value')
// 将 Watcher 添加到订阅
if (Dep.target) {
dp.addSub(Dep.target)
}
return val
},
set: function reactiveSetter(newVal) {
console.log('change value')
val = newVal
// 执行 watcher 的 update 方法
dp.notify()
}
})
}
复制代码
以上实现了一个简易的双向绑定,核心思路就是手动触发一次属性的 getter 来实现发布订阅的添加。
Proxy 与 Obeject.defineProperty 对比
Obeject.defineProperty
虽然已经能够实现双向绑定了,但是他还是有缺陷的。
- 只能对属性进行数据劫持,所以需要深度遍历整个对象
- 对于数组不能监听到数据的变化
虽然 Vue 中确实能检测到数组数据的变化,但是其实是使用了 hack 的办法,并且也是有缺陷的。
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
// hack 以下几个函数
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(function (method) {
// 获得原生函数
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
// 调用原生函数
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// 触发更新
ob.dep.notify()
return result
})
})
复制代码
反观 Proxy 就没以上的问题,原生支持监听数组变化,并且可以直接对整个对象进行拦截,所以 Vue 也将在下个大版本中使用 Proxy 替换 Obeject.defineProperty
let onWatch = (obj, setBind, getLogger) => {
let handler = {
get(target, property, receiver) {
getLogger(target, property)
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
setBind(value);
return Reflect.set(target, property, value);
}
};
return new Proxy(obj, handler);
};
let obj = { a: 1 }
let value
let p = onWatch(obj, (v) => {
value = v
}, (target, property) => {
console.log(`Get '${property}' = ${target[property]}`);
})
p.a = 2 // bind `value` to `2`
p.a // -> Get 'a' = 2
复制代码
路由原理
前端路由实现起来其实很简单,本质就是监听 URL 的变化,然后匹配路由规则,显示相应的页面,并且无须刷新。目前单页面使用的路由就只有两种实现方式
- hash 模式
- history 模式
www.test.com/#/
就是 Hash URL,当 #
后面的哈希值发生变化时,不会向服务器请求数据,可以通过 hashchange
事件来监听到 URL 的变化,从而进行跳转页面。
History 模式是 HTML5 新推出的功能,比之 Hash URL 更加美观
内置类型
JS 中分为七种内置类型,七种内置类型又分为两大类型:基本类型和对象(Object)。
基本类型有六种: null
,undefined
,boolean
,number
,string
,symbol
。
其中 JS 的数字类型是浮点类型的,没有整型。并且浮点类型基于 IEEE 754标准实现,在使用中会遇到某些 Bug。NaN
也属于 number
类型,并且 NaN
不等于自身。
对于基本类型来说,如果使用字面量的方式,那么这个变量只是个字面量,只有在必要的时候才会转换为对应的类型
let a = 111 // 这只是字面量,不是 number 类型
a.toString() // 使用时候才会转换为对象类型
复制代码
对象(Object)是引用类型,在使用过程中会遇到浅拷贝和深拷贝的问题。
let a = { name: 'FE' }
let b = a
b.name = 'EF'
console.log(a.name) // EF
复制代码
Typeof
typeof
对于基本类型,除了 null
都可以显示正确的类型
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof b // b 没有声明,但是还会显示 undefined
复制代码
typeof
对于对象,除了函数都会显示 object
typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
复制代码
对于 null
来说,虽然它是基本类型,但是会显示 object
,这是一个存在很久了的 Bug
typeof null // 'object'
复制代码
PS:为什么会出现这种情况呢?因为在 JS 的最初版本中,使用的是 32 位系统,为了性能考虑使用低位存储了变量的类型信息,000
开头代表是对象,然而 null
表示为全零,所以将它错误的判断为 object
。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。
如果我们想获得一个变量的正确类型,可以通过 Object.prototype.toString.call(xx)
。这样我们就可以获得类似 [Object Type]
的字符串。
let a
// 我们也可以这样判断 undefined
a === undefined
// 但是 undefined 不是保留字,能够在低版本浏览器被赋值
let undefined = 1
// 这样判断就会出错
// 所以可以用下面的方式来判断,并且代码量更少
// 因为 void 后面随便跟上一个组成表达式
// 返回就是 undefined
a === void 0
复制代码
类型转换
转Boolean
在条件判断时,除了 undefined
, null
, false
, NaN
, ''
, 0
, -0
,其他所有值都转为 true
,包括所有对象。
对象转基本类型
对象在转换基本类型时,首先会调用 valueOf
然后调用 toString
。并且这两个方法你是可以重写的。
let a = {
valueOf() {
return 0
}
}
复制代码
四则运算符
只有当加法运算时,其中一方是字符串类型,就会把另一个也转为字符串类型。其他运算只要其中一方是数字,那么另一方就转为数字。并且加法运算会触发三种类型转换:将值转换为原始值,转换为数字,转换为字符串。
1 + '1' // '11'
2 * '2' // 4
[1, 2] + [2, 1] // '1,22,1'
// [1, 2].toString() -> '1,2'
// [2, 1].toString() -> '2,1'
// '1,2' + '2,1' = '1,22,1'
复制代码
对于加号需要注意这个表达式 'a' + + 'b'
'a' + + 'b' // -> "aNaN"
// 因为 + 'b' -> NaN
// 你也许在一些代码中看到过 + '1' -> 1
复制代码
==
操作符
上图中的 toPrimitive
就是对象转基本类型。
一般推荐使用 ===
判断两个值,但是你如果想知道一个值是不是 null
,你可以通过 xx == null
来比较。
这里来解析一道题目 [] == ![] // -> true
,下面是这个表达式为何为 true
的步骤
// [] 转成 true,然后取反变成 false
[] == false
// 根据第 8 条得出
[] == ToNumber(false)
[] == 0
// 根据第 10 条得出
ToPrimitive([]) == 0
// [].toString() -> ''
'' == 0
// 根据第 6 条得出
0 == 0 // -> true
复制代码
比较运算符
- 如果是对象,就通过
toPrimitive
转换对象 - 如果是字符串,就通过
unicode
字符索引来比较
作者:夕阳
链接:https://juejin.im/post/5abb22925188255c4c1050e0
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。