leeeeeem / blog Goto Github PK
View Code? Open in Web Editor NEWdaily blog
daily blog
使用::和es5的bind&&call&&apply的区别。
使用call bind apply 不是一种面向oo的编程方式,所以会导致什么问题呢? 无法链式调用,这就很蛋疼了。
所以::操作符的一部分作用就是可以面向oo编程,可以链式调用。
fn2.bind(fn1.bind(this, a1)(argu1))(argu2) // 恶心不?很恶心
this::fn1(argu1)::fn2(argu2) // OK,链式调用。
// 获取矩阵中最大连续的块。例如[
// [1, 0, 1, 0, 1],
// [1, 1, 0, 1, 1],
// [0, 1, 0, 0, 1],
// [0, 1, 0, 0, 0],
// [0, 1, 0, 0, 0],
// ]最大连续块个数为6。
function getBlock(array, i, j) {
if (array[i][j] === 0) return 0
let sum = 0
array[i][j] = 0
sum++
if (array[i] && array[i][j - 1] === 1) {
sum += getBlock(array, i, j - 1)
}
if (array[i] && array[i][j + 1] === 1) {
sum += getBlock(array, i, j + 1)
}
if (array[i + 1] && array[i + 1][j] === 1) {
sum += getBlock(array, i + 1, j)
}
if (array[i - 1] && array[i - 1][j] === 1) {
sum += getBlock(array, i - 1, j)
}
return sum
}
function getMaxBlocks(bytesArray) {
const blocks = []
for (let i = 0; i < bytesArray.length; i++) {
for (let j = 0; j < bytesArray[i].length; j++) {
let block = getBlock(bytesArray, i, j)
if (block > 0) {
blocks.push(block)
}
}
}
return Math.max(...blocks)
}
const a = getMaxBlocks(
[
[1, 1, 1, 0, 1],
[1, 1, 0, 1, 1],
[0, 1, 0, 0, 1],
[0, 1, 1, 0, 0],
[0, 1, 0, 0, 0],
]
)
console.log(a)
function getMount(array) {
let lIndex = 0
let rIndex = array.length - 1
let lMaxIndex = 0
let rMaxIndex = array.length - 1
let result = 0
while (lIndex < rIndex) {
if (array[lMaxIndex] < array[rMaxIndex]) {
lIndex++
if (array[lIndex] > array[lMaxIndex]) {
lMaxIndex = lIndex
}
result += array[lMaxIndex] - array[lIndex]
} else {
rIndex--
if (array[rIndex] > array[rMaxIndex]) {
rMaxIndex = rIndex
}
result += array[rMaxIndex] - array[rIndex]
}
}
return result
}
const a = [2, 8, 6, 4, 7, 9, 1, 5]
console.log(getMount(a))
观察者模式,为什么要使用观察者模式呢?
如果熟悉Node的Stream模块或者知道什么是流,那么观察者模式就特别好理解了。我们感知世界的方式大概有两种,一种跟随世界的变化,被动地接受,随心所欲超然世外。另一种物为我用,主动获取,改造自然。结合现实获取数据的方式,除了主动请求服务之外,另一种就是推送。我们的观察者模式就是利用推送(或者其他的方式,相对于观察者来说)来获取数据并处理数据。
Vue的View-Model是通过Object.defineProperty get/set去实现的,在创建对象的时候可以随意实现。所以想要监听数据,就必须有get/set。
但是在数组中就不一样了,Vue的文档只说了一半。
var a = [1, 2]
var b = Object.getOwnPropertyDescriptor(a, 'length')
b
//{value: 2, writable: true, enumerable: false, configurable: false}
configurable: false底层不支持重写get/set,只可以通过writable设为true。进行读写。
目前开发中最容易被忽略的内存泄漏主要是DOM的内存泄漏,为什么呢?主要是存在循环引用,父子节点相互引用,在removeChild的时候如果跨层级remove就会出现这种情况。
Node的原理决定了它面向请求的应用场景。类似Nginx的事件驱动,底层区别也不大。
每一条请求,都是封装了同一个app对象,所有有用的没有的东西都是挂在它上面。所以,副作用很大,一不小心就暴露了安全数据。这个暂且不谈,Express的中间件原理给了它强大的生命力。如何实现?暂且抛开异步操作,我们不需要在函数内使用大量的async/await G/yield。
Vuex其实特别简单。
state -> 数据持久层
getter -> 读数据
mutation -> 原子操作,所有的原子操作都是同步,写数据
action -> 原子操作的各种组合,可以是同步,也可以是异步。各种读写。
that's all, bye
最早了解到装饰器是从JAVA中的注解,给元数据提供自定义能力。JAVA的注解范围比较大,JS中装饰器可以用来注解类
、属性
、方法
、参数
。接下来我们实现一下类的注解。
function callNext(promize, callback, action, value) {
if (typeof callback === 'function') {
let result
try {
result = callback(value)
} catch (e) {
reject(promize, e)
return
}
if (result === promize) {
const err = new Error('cycle error')
reject(promize, err)
}
else if (result instanceof Promize) {
result.then(function(data) {
resolve(promize, data)
}, function (err) {
reject(promize, err)
})
} else {
resolve(promize, result)
}
}
else {
action === 'resolve' ? resolve(promize, value) : reject(promize, value)
}
}
function runCallback(promize) {
if (promize.state === 'pending') return
const value = promize.value
const callbacks = promize.state === 'resolved' ? promize._doneCallbacks.slice() : promize._failCallbacks.slice()
setTimeout(function() {
for (var i = 0, len = callbacks.length; i < len; i++) {
callbacks[i](value)
}
})
promize._doneCallbacks = []
promize._failCallbacks = []
}
function resolve(promize, data) {
if (promize.state !== 'pending') return
promize.state = 'resolved'
promize.value = data
runCallback(promize)
}
function reject(promize, reason) {
if (promize.state !== 'pending') return
promize.state = 'rejected'
promize.value = reason
runCallback(promize)
}
function Promize (resolver) {
this.state = 'pending'
this.value = undefined
this._doneCallbacks = []
this._failCallbacks = []
resolver(resolve.bind(this, this), reject.bind(this, this))
}
Promize.prototype.then = function (resolve, reject = function(err) { throw err } ) {
const promize = new Promize(function() {})
this._doneCallbacks.push(callNext.bind(promize, promize, resolve, 'resolve'))
this._failCallbacks.push(callNext.bind(promize, promize, reject, 'reject'))
runCallback(this)
return promize
}
Promize.prototype.catch = function (errHandler) {
return this.then(null, errHandler)
}
var a = new Promize(function(resolve, reject) {
reject(111)
})
.then(function(res) {
console.log(res)
return 333
}, function(reason) {
console.log(reason)
throw 1
})
.then(function(res) {
console.log(res)
})
.then(function(res) {
console.log(res)
})
.then(function(res) {
console.log(res)
}).catch(err => {
console.log('catch', err)
});
查看源码后发现,v-model指令其实只是在web的platform下,vue提供了自定义指令。
因为v-model指令需求场景太多了,所以这个一个强需求,需要保留到快应用平台。
v-model指令其实可以拆分成两块,一个是数据绑定v-bind,另一个是v-on[update:${key}
],触发change事件之后的回调赋值。按理说,只要dom结构一致的话,移植其实是无成本的。但是由于快应用的dom实现与web并不一致,所以看源码,directive/model里面跟web相关的该删除删除。vue-template-compiler该修改的修改,保证一致,即可实现v-model指令。同理v-show指令也是这样,不过常用v-if代替,如果做动画的话还是建议保留。
class Scheduler {
add(promiseCreator) {
}
// todo
}
const scdu = new Scheduler()
const timeout = time => {
return new Promise(resolve => {
setTimeout(resolve, time)
})
}
function addTask(time, order) {
scdu.add(() => timeout(time)).then(() => console.log(order))
}
addTask(1000, 1)
addTask(500, 2)
addTask(300, 3)
addTask(400, 4)
// 2 3 1 4
最近在研究快应用的style节点,发现我们的框架有很多对对象的引用,当页面销毁的时候,仍然会有很多的StyleNode节点仍然会保持对多个抽象对象引用。每次执行destroy的时候需要手动的进行null指向或者用性能极差的delete操作。到底有没有什么解决的方法?
众所周知,v8有自己的GC,闭包会阻止GC,手动将对象指向null或者undefined也可以进行垃圾回收(可以使用profiler自己做个试验;debugger可以看到closure被保存在scope内部,不会被GC)。
var obj = {
name: 'mi',
age: 30
}
obj = null
class Singleton {
static instance = null
static getInstance() {
if (!Singleton.instance) {
lock (anyObject) { // 伪代码,表示同一时间只有一个读写操作,防止多次实例化。
Singleton.instance = new Singleton()
}
}
return Singleton.instance
}
constructor() {
}
}
component对应的是组件类这个概念,每一个自定义的react组件都是一个reactComponent类,对应的vueComponent,存储model。而reactElement对应的是一个element的概念,这个类更多的是指react树结构。此外,react中还有一个_renderedComponent其中对应的_currentElement是一个将react树解析成原生标签的结构,这一层是做dom diff的关键。
最近使用Proxy的场景在不断增多,所以对Proxy的使用场景做了调研。Proxy在get/set中引入副作用能完美地应用于响应式開发。主流库使用Proxy主要是Vue和immer。Vue的Proxy改造我已经实现,下面看下immer。
首先要明白immer是做什么的,才能真正看明白immer。
在mobx
出现之前,react社区使用最多的就是redux
以及其衍生库。redux
使用reducer产生新的对象,覆盖原始的对象,这是reducer使用的准则,即生成一个无副作用的对象,这样做起时间旅行等操作会方便很多。对象不复杂还是可以接受的,但是一个嵌套多层的对象再重新生成那么必将引起性能问题。这个时候react出品的immutable.js
就发挥了使用价值。immutable.js
对未经修改的子对象进行了共享,这样减少了重新生成新对象的开销。
mobx
在基于observe对象的前提下,对对象进行了代理操作,通过get/set操作进行依赖收集,在set阶段触发setState。本质上和Vue类似。同样,重新赋值多层嵌套的对象也会引起性能问题。在mobx
里当然可以使用immutable.js
。但是immutable.js
体积不小,并且immutable对象和原生对象切换会导致问题。学习成本比较高。所以immer
横空出世。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
body {
margin: 0;
font-size: 50px;
}
</style>
</head>
<body>
<div style="height: 500px;" frameborder="0">111111</div>
<div id="xxx" style="height: 1000px; background-color: aliceblue; will-change: auto;"></div>
<script>
const bool = true
const frame = document.getElementById('xxx')
frame.innerHTML = bool.toString()
document.body.addEventListener('touchstart',
event => {
console.log('start', event.cancelable)
if (event.cancelable) {
console.log('start')
event.preventDefault()
}
}, {
passive: bool
})
document.body.addEventListener('touchmove',
event => {
console.log('move', event.cancelable)
if (event.cancelable) {
console.log('move')
event.preventDefault()
}
}, {
passive: bool
})
document.body.addEventListener('touchend',
event => {
console.log('end', event.cancelable)
if (event.cancelable) {
console.log('end')
event.preventDefault()
}
}, {
passive: bool
})
document.body.addEventListener('click',
event => {
console.log('click')
}, {
passive: bool
})
</script>
</body>
</html>
1、如果是通过props传入的值,不要使用v-model,破坏了单一数据源。
这条比较常见,很多人在写input标签的时候总是喜欢用v-model,vue做了双向绑定美滋滋,其实v-model是v-bind和update:xxx的语法糖。如果外部传来的一个值用了v-model,其实是破坏了单一数据流动。不过vue会有提示。
2、如果是data内的私有属性,可以使用v-model。
因为data里面放的是组件私有的属性,所有可以放心使用v-model,数据的流动就在这个组件内部。
3、如果属性不参与渲染或者传递,则不要放到data里面,造成无谓的数据劫持。
很多人喜欢把所有在vue整个生命周期的东西都放到data里面,其实并不是这样,放在data里的属性总是要做数据劫持的,如果不参与指令绑定,那么完全可以不放到data里。完全可以直接在this上挂载。常见的如this.timer = setTimeout(() => {}),为了防止内存泄漏,在beforeDestroy里clearTimeout。这种定时器在整个过程中什么只是做了一次定时清除。如果这种情况多,可以在this上单独开个空间time = {}存储。
模板方法模式
这种主要用于一定流程的代码。
比如说Vue的整个初始化以及挂载过程。
有initState、initMethods、initComputed以及穿插在其中的各个钩子。
// 在涉及到多个类似的流程代码的时候,会抽象出公有代码。
// 比如js设计模式所提到的冲泡咖啡和泡茶的过程
// 1、烧开水
// 2、放入咖啡/茶叶
// 3、倒水
// 4、加入辅料
// 其中烧开水和倒水是公共方法可以抽象出来。
// 抽象出一个抽象类或者接口,作用就是让子类实现,本身不实现,抽象类方法执行报错
class DoDrink {
boil() {
throw new Error(`禁止调用抽象类烧水方法`)
}
pure() {
throw new Error(`禁止调用抽象类倒水方法`)
}
init() {
// 这是一个共有模板方法入口,必须实现。所以我们这个类并不是纯粹的抽象类
this.boil()
this.putIn()
this.pure()
this.putOther()
}
}
class DoTea extends DoDrink {
putIn() {
console.log(`把茶叶放进杯子`)
}
putOther() {
console.log(`放入柚子`)
}
}
class DoCoffee extends DoDrink {
putIn() {
console.log(`把咖啡放进杯子`)
}
putOther() {
console.log(`放入牛奶`)
}
}
var dotea = new DoDrink()
dotea.init()
var docoffee = new DoCoffee()
docoffee.init()
最近在研究webpack源码,针对一些node工具,回调还是特别实用的手段。
技术背景:
在web浏览器中,浏览器对ua暴露了DOM接口,所以开发者只需要调用最底层的appendChild、insertBefore、removeChild三个最基本的api就可以完成大部分的开发(setAttribute也是很常见的)。
但是在其他实现DOM的平台上,渲染引擎并没有对外直接暴露API。所以这个时候需要实现一个专门向渲染引擎发送指令的通道,这些指令包括insert/append/remove/update等。
在浏览器中,这些发送这些指令(append/insert等)是同步的,在浏览器底层却是使用其他线程异步操作的。
这个在快应用的xvm框架里面使用的是同步发送指令。为了提升性能,这里决定使用异步发送。
所以这里我在发送指令的实例streamer内增加了异步发送策略。开发者可以传入Promise.reslove().then或者setTimeout,但是我比较懒,直接把Vue的Vue.nextTick传入strategy。以下是部分代码
sendAsync (id, actions) {
if (actions) {
this.list = this.list.concat(actions)
}
if (this._sendTick) return
const fn = () => {
const list = this.list.splice(0)
list.length && this.call(id, list)
this._sendTick = null
}
this._sendTick = this._stragegy(fn)
}
近期开发框即使用rollup打包以及配合webpack做测试用例发现的问题。
Vue为什么要实例一个Observer对象,而每一个Observer对象都挂有三个属性:value(原始数据)、vmCount(number of vms that have this object as root $data,被VM作为根数据的个数)、dep。起初我以为这个dep这个属性对于所有的observer都有用。直到后来看了好几遍Vue源码才体会到并非起初想得那样。劫持数据的过程:vm上先得到一个_data属性,将需要劫持的数据挂上去。类似于以下代码:
data() {
return {
name: {
cc: 'slm'
}
}
}
得到_data差不多是{name: {cc: 'slm'}}
,然后走observe方法,判断当前对象(_data)是否存在__ob__属性,如果存在,则说明已经被劫持过了,不需要再劫持处理了。没有则实例话一个Observer对象(满足条件一般是对象或数组)。第一次的话实例一个Observer对象。先在当前对象(或数组)def一个__ob__属性,用于标记并存放当前的value,这个属性之后的操作几乎不用。接下来就要对该对象或者数组遍历并做劫持了。walk -> defineRactive 劫持。重头戏来了:
function defineReactive() {
///
const dep = new Dep() 这是一个闭包,其实这里的dep才是最终要使用的依赖收集器
///
// cater for pre-defined getter/setters 预处理getter和setter
// 正戏
let childOb = !shallow && observe(val) // warnin~!!!! 这里很重要,我们都知道js中的对象是引用地址的,只要对象引用的地址不变,那么该对象就不变。
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend() // dep作为闭包,收集依赖该value的watcher,用于UI更新或者数据跟踪。
if (childOb) {
childOb.dep.depend() // warning~!!!! 这里很重要,为什么依赖子属性的watcher也要订阅呢???这是因为如果该值发生了变化,即在set的时候发生变化,执行notify,那么该对象的子属性肯定也变化了,这个是毫无疑问的,所以这里子属性的依赖收集器也必须订阅一下。 warning,那么这里似乎我们对文章开头的时候提出的疑问似乎可以解答了,为什么Vue要实例Observer并使其有dep属性?实例化的原因不说了,面向对象的**。为什么dep属性要挂到对象上呢??不是都放到这个空间的闭包里面吗?回答:因为需要子属性的引用,这里childOb.dep就是为了对依赖收集器的引用,所以挂到了Observer的实例上,所有有的人很懵,为什么又是闭包有挂到实例属性上...
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal) /// 赋值之后重新Observe劫持数据
dep.notify() // 触发一次notify,搞事情(更新等)
}
})
}
最近花了一些时间整理了一下san的template parser。
为什么要使用Symbol?
本来JS只有5种基本类型null
/ undefined
/ Boolean
/ Number
/ String
。
ES6中新增了一个Symbol类型。基本上现在没有多少人用这个新feature。然而它是真的很好用。
场景1: 起名字难题,有人给属性起名字很蒙蔽,用的别人的库,想自己封装一下,添加几个属性,但是原作者已经用了Px2y这个属性,现在依然想用怎么办?
// polyf has Px2y attribute
const polyf = require('xxxx');
let Px2y = Symbol('Px2y');
polyf[Px2y] = function () {
// your implement
};
module.exports.Px2y = Px2y;
module.exports = polyf;
// import
const polyf = require('./xx');
const Px2y = require('./xx').Px2y;
场景2:内部使用的属性不想暴露给外部,外部无法for-in遍历到。保证安全性。使用Object.getOwnPropertySymbols可以访问。
let Px2y = Symbol('Px2y');
class A {
constructor() {
this.name = 'A';
this.age = '1000';
this[Px2y] = 'Not Cheap';
}
}
let obj = new A();
for (let i in obj) {
console.log(obj[i])
}
console.log(Object.getOwnPropertySymbols(obj))
今天这里给自己讨论的是ref callbacks的设计模式。虽然react最新的api可以在代码层级上更优美地处理ref。但是在灵活性上ref callbacks更加出众。
介绍看问题提交
如果ts的引入早于js,那么现在很多的流行库都会使用ts来开发,因为可维护性和可读性都是明显高于js的。
但是,ts作为一个后引入的集合,先前发布的js该如何无缝插入到ts代码中呢?
在操作之前,我们来看一下文档。
ts模块
cmd和amd是很久之前就达成共识的模块化规范,直到后来es6的引入。cmd/amd的环境里都有一个exports变量,它是module.exports的引用。es6的模块支持export/export default但是对exports/exports.xxx可不认识,所以对cmd/amd的语法就存在这一种别扭的引入方式import xxx = require('xxx') export = xxx。
为什么要这么用,怎么使用?看下例子。
我们正常去开发一个ts模块
/// mod2t2.ts
export function isEven(x:number): boolean {
return x % 2 === 0
}
export default function isOdd(x: number): boolean {
return !isEven(x)
}
/// mod2t1.ts
import isOdd, { isEven } from './mod2t2'
const a = isEven(5)
const b = isOdd(5)
console.log(a, b)
全局安装typescript,tsc编译--module es6编译成es6代码。
全局安装babel-cli,本地安装babel-preset-es2015,创建.babelrc。babel-node执行。
以上是一个正常开发的ts模块export和import
我们按照上面的思路引入我们以前用commonjs写的老代码,不是写一个类似与h文件头的声明文件吗?每个声明文件与源文件一一对应即可。
///源文件 modj1.js
function isEven(x) {
return x % 2 === 0
}
function isOdd(x) {
return !isEven(x)
}
module.exports = isOdd
exports = module.exports // 常见问题,重新引用一下
exports.isEven = isEven
/// modj1.d.ts 声明文件
export default function isOdd(x: number):boolean
export function isEven(x: number):boolean
/// modt1.ts
import isOdd, { isEven } from './modj1'
const a = isEven(5)
const b = isOdd(5)
console.log(a, b)
使用tsc编译--module commonjs,编译之后的commonjs代码用node执行,结果出现问题:modj3_1.default is not a function,细细想一下是因为commonjs中没有default这个属性。
/// 编译之后的mod2.js
"use strict";
exports.__esModule = true;
var mod1_1 = require("./mod1");
var a = mod1_1.isEven(5);
var b = mod1_1["default"](5); // error
console.log(a, b);
解决,所以这个地方就需要使用commonjs的引入语法。
/// modj1.js
function isEven(x) {
return x % 2 === 0
}
function isOdd(x) {
return !isEven(x)
}
module.exports = isOdd
exports = module.exports
exports.isEven = isEven
/// modj1.d.ts
declare function isOdd(x:number):boolean
declare namespace isOdd {
export function isEven(x:number):boolean
}
export = isOdd
/// modt1.ts
import isOdd = require('./modj1')
import { isEven } from './modj1'
const b:boolean = isEven(2)
const c:boolean = isOdd(4)
console.log(b, c)
经过上面步骤编译之后,使用node正常执行。
/// modt1.js
"use strict";
exports.__esModule = true;
var isOdd = require("./modj1");
var modj1_1 = require("./modj1");
var b = modj1_1.isEven(2);
var c = isOdd(4);
console.log(b, c);
编译之后的代码中不包含default,OK~
最近在研究Delete
操作,V8可以给出相应的优化点,同时用node的命令行操作也给出了一些调用v8内部能力的api。ES6添加了一些新的语法可以给我们更深入操作底层。Proxy
和Reflect
、Object描述符
就是其中两块,而Object描述符
已经广泛应用于Vue等MVVM框架里。
之前一直在思考,同样的方法在Object
的原型链上已经存在,为什么还要在Reflect
里面重新定义,JS是面向对象的语言,完全可以用Object
原型链上的方法啊,后来想了下,可能是为了照顾函数式编程规范。
Proxy
顾名思义是可以拦截底层的操作包装器。在底层对象操作之前添加了一层控制。对原始底层对象的直接访问不会经过这层拦截器。
Proxy
构造函数第一个参数是要拦截的对象,第二个参数类似于Object.defineProperty
的handler,提供一个各种行为类似set/get的函数对象,若对象为空或没有相应的方法,则相当于没有拦截。这是一个有副作用的功能。拦截的操作包括set/get/has/遍历/defineProperty/delete/construct 实例化等。
let target = {
age: 30
};
let p = new Proxy(target, {
get(target, name) {
if (!target.hasOwnProperty(name)) {
throw Error('target don\'t has this property');
}
console.log('get success');
return target[name];
},
set(target, name, value) {
if (!target.hasOwnProperty(name)) {
throw Error('target don\'t has this property, can\'t set property');
}
console.log('set success');
return target[name] = value;
}
});
p.age // success
p.age = 12 // success
p.name // throw Error
p.name = 12 // throw Error
在引入context之前,我们先拿JS做对比(因为我最熟悉JS)。
context上下文在程序的各个地方都有体现,无论是function内部的上下文环境还是一个请求的上下文环境。
在Node中,一个请求在各个中间件之间流动。中间件处理完之后都会释放该调用栈内的变量,当然全局变量和闭包是人为保留下来的,不在我们讨论的范围内。那么貌似跟我们在go里面讨论的context包没有任何关系。
那么在一个Vue实例的mount方法中调用了一个异步操作setInterval,在组件beforeDestroy时,我们需要手动地清除定时器,防止内存泄漏。JS给我们提供了一个int句柄var timer = setInterval(() => {}, 1000),这样在销毁的时候调用句柄即可清理定时器。
同理,go的协程里面使用了channel,在for操作执行之后都会close channel。
func () {
ch := make(chan int)
defer close(ch)
for i:= 0; i< 5; i++ {
ch <- i
}
return ch
}()
在某些接口里面,我们需要对外暴露一个Close方法,用于关闭内部的channel,防止协程泄露。这个时候context包就用上了,Google提供了一个context包封装了线程安全的context。
其中WithCancel返回了cxt和hanlder。WithDeadline、Withtimeount提供了ctx和hanlder,如果超过时间会自动调用hanlder,这个时候再去执行hanlder会返回已经执行的结果。
最近我们的快应用框架需要支持vue。
我们的快应用框架中包含这么几个部分:
1、APP启动bootstrap,快应用中的代码入口点是rpk包(可以理解为打包之后用zip压缩)中manifest.json文件,原生端初始化V8引擎,加载框架,注入全局的global函数,runtime,以及全局的共享数据。
2、加载页面Page,每一个页面都是一个V8实例,互不干扰,实例化VM,注册Page函数,启动Page相关的生命周期钩子函数。
3、构建页面。
此时只要修改每个页面中的VM,UI随即更新。
我们类比小程序,即可发现mpvue在runtime的时候拦截了全局的Page中的setData函数,开发过原生小程序的同学都知道,这个类似react中的setState函数的钩子是我们修改页面的唯一途径。所以,类似的,我们的快应用框架如果需要引入VUE框架,实际上只需要在Page中拦截vm,只要vm发生变化,随即将数据的变化发送给快应用框架中的对应的VM即可。
VDOM patch 在目前主流的前端框架广泛使用。顾名思义就是最小化地操作DOM,给DOM打补丁。Vue和react等框架里面使用的diff算法可能不一致,但是最后都能达到Patch的效果。网上对React的diff算法讲得比较多,所以这里我们以Vue为例,看一下Vue的Patch过程。
// patch.js => updateChildren
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx, idxInOld, vnodeToMove, refElm
// removeOnly is a special flag used only by <transition-group>
// to ensure removed elements stay in correct relative positions
// during leaving transitions
const canMove = !removeOnly
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(newCh)
}
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
vnodeToMove = oldCh[idxInOld]
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}
大体上分为两个阶段:1、while循环,主要进行move操作和create操作。2、循环之后的操作,批量删除和批量插入。
while循环阶段用于判断oldVnode是否为undefined(如果在newCh里面不满足后面的条件但是在oldCh找到),头头相等、尾尾相等、头尾相等、尾头相等、找到对应key移动(上面判断undefined操作来源于此)、未找到对应key创建这几种情况。
这里选取Vue 2.5.17版本。测试代码如下
const list1 = [{
name: '111',
key: '111',
show: false
}, {
name: '222',
key: '222',
show: false
}, {
name: '333',
key: '333',
show: true
}, {
name: '444',
key: '444',
show: true
}, {
name: '555',
key: '555',
show: true
}, {
name: '666',
key: '666',
show: false
}]
const list2 = [{
name: '222',
key: '222',
show: true
}, {
name: '444',
key: '444',
show: false
}, {
name: '333',
key: '333',
show: true
}, {
name: '111',
key: '111',
show: true
}, {
name: '555',
key: '555',
show: false
}, {
name: '777',
key: '777',
show: true
}]
/* start */
const v3 = new Vue({
template: `
<div>
<div v-for="(item, index) in list" :key="item.key" v-if="item.show">
{{item.name}}
</div>
</div>
`,
data () {
return {
list: list1
}
},
mounted () {
setTimeout(() => {
/* 这里可以打断点,源码里patch.js patchVnode和 updateChildren 方法内打上断点 */
this.list = list2
}, 1000)
}
})
v3.$mount(`#app3`)
N节点代表comment节点
注意:操作开始,Vue会将未渲染的节点标记为comment节点。所以如果你第三方框架想要支持类似Vue的patch操作,需要在DOM层实现comment节点。
这里我们要关注的地方在于:1、实现DOM规范node节点的childNodes属性和children属性,childNodes这个类数组包含着最后渲染到视图上的子节点(包含comment节点),children包含所有的element元素。2、两次Vnode数组newCh和OldCh。
由于第一次循环操作不符合头头、尾尾、头尾、尾头相同的情况,创建一个oldKeyToIdx存储key的对象,供所有的循环操作使用。newStartVnode在oldKeyToIdx找不到对应的Vnode,所以创建一个新的element并插入到oldStartVNode的ele之前,newStartIndex后移一位,进入下一次循环。
由于第二次循环操作符合头头相等的情况,所以oldStartIndex和newStartIndex都后移一位,进入下一次循环。
由于第三次操作不符合头头、尾尾、头尾、尾头相同的情况。但是newStartVnode在oldKeyToIdx找到对应的key,并且是相同的element,复用oldVnode的ele,将ele插入到oldStartVnode的ele之前。同时将在oldCh中找到的相同key的Vnode设为undefined,newStartIndex后移一位,进入下次循环。
由于第四次循环操作不符合头头、尾尾、头尾、尾头相同的情况。newStartVnode在oldKeyToIdx找不到对应的key,所以新建一个新的element,插入到oldStartVnode的ele之前(insertedVnodeQueue用于递归操作,保存内部的elment)。newStartIndex后移一位,进入下次循环。
由于第五次循环操作符合头头相等的情况,所以oldStartIndex和newStartIndex都后移一位,进入下一次循环。
由于第六次循环操作oldStartVnode是undefined,所以直接跳过,oldStartIndex后移一位。
由于第七次循环操作不符合头头、尾尾、头尾、尾头相同的情况。newStartVnode在oldKeyToIdx找不到对应的key,所以新建一个新的element,插入到oldStartVnode的ele之前(insertedVnodeQueue用于递归操作,保存内部的elment)。newStartIndex后移一位,进入下次循环。
由于NewStartIndex > NewEndIndex,所以跳出循环。开始批量删除或者添加操作,这里批量删除oldCh中oldStartIndex到oldEndIndex之间vnode对应的ele。这里删除 444、555、N这三个节点。
在我们快应用中引入了Vue框架,针对diff算法改进了我们的DOM实现。使得Vue能够在快应用平台完美使用。
在开发框架之前,我们要首先要了解一些基本知识:框架的用途;渲染原理。
首先前端的概念需要重新定义一下,我的理解是JS的使用场景所覆盖的地方都是前端,这跟传统的css+html+js技术栈是有区别的。JS是基础,当然,我这里不是贬低DOM和CSSOM,他们都很伟大,都是值得深入探讨的规范(此处可以看出MDN的重要作用),我本人对CSS和DOM的理解也值停留在中级阶段。目前我在做的部分基本脱离了浏览器,DOM和CSS已经不在浏览器上表现了,它们目前只是一个抽象的表达。对于通用的框架,它应该可以脱离浏览器工作的,框架的核心就是理念和**。react:虚拟dom、diff、单向数据流。vue:数据劫持为基础的mvvm、双向绑定等。除了在浏览器,他们在小程序开发、桌面开发、native开发中也占据了重要位置。
什么是渲染?webview(dom)、canvas(包括webgl)、native渲染。渲染都是去调用底层的c模块让cpu和gpu一帧一帧地画图。性能上肯定dom慢,原因就是基础的那一套(dom树、cssom、js计算、布局重绘合成等等),native渲染直接让移动设备调用底层渲染,没有DOM模型,但是也正是这样,通用性不行。但是native渲染的用户体验高出webview渲染很多。
native负责渲染,那么我们的框架需要做的事情就很明显了,native需要什么样的格式,我们就给它发送什么样的格式,native收到后按照渲染规则一帧一帧画出来就行了。我们的框架就是用来构造这个格式并维护这个格式的。
在React中可以使用this.props.children获取闭合组件内部插入的reactElement。
在Vue中为了实现自定义的分发,而不是整体的children的获取,采用了webcomponents标准的slot。
slot的使用哲学是在父组件内定义好模板,子组件按照父组件的模板把子组件内部的model对应地绑定到相应的模板位置。
scoped-slot就是这个原理,简单点就是,父组件定义好格式,子组件根据这个格式塞数据就好。
而简单的slot就是单纯地静态地将父组件内的模板覆盖子组件模板。
所以,在编译自定义组件到真实DOM的时候,父组件会将解析之后的各个slot一并传入,在子组件编译的时候一并获取即可。
而在Vue的文档中,这些原理并没有讲明白,插槽和编译作用域会让人迷惑。
https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/slot
当然,使用slot的时候一样要对父子组件的结构清晰,这个和React中获取children是一样的。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.