Git Product home page Git Product logo

blog's Introduction

内容在Issues

blog's People

Contributors

leeeeeem avatar

Watchers

James Cloos avatar

blog's Issues

为什么要使用::操作符

使用::和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)

leetcode 42

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中数组的特殊处理

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。进行读写。

JS内存泄漏

目前开发中最容易被忽略的内存泄漏主要是DOM的内存泄漏,为什么呢?主要是存在循环引用,父子节点相互引用,在removeChild的时候如果跨层级remove就会出现这种情况。

express如何实现中间件?

Node的原理决定了它面向请求的应用场景。类似Nginx的事件驱动,底层区别也不大。
每一条请求,都是封装了同一个app对象,所有有用的没有的东西都是挂在它上面。所以,副作用很大,一不小心就暴露了安全数据。这个暂且不谈,Express的中间件原理给了它强大的生命力。如何实现?暂且抛开异步操作,我们不需要在函数内使用大量的async/await G/yield。

Vuex 使用心得

Vuex其实特别简单。
state -> 数据持久层
getter -> 读数据
mutation -> 原子操作,所有的原子操作都是同步,写数据
action -> 原子操作的各种组合,可以是同步,也可以是异步。各种读写。

that's all, bye

js装饰器

最早了解到装饰器是从JAVA中的注解,给元数据提供自定义能力。JAVA的注解范围比较大,JS中装饰器可以用来注解属性方法参数。接下来我们实现一下类的注解。

实现promise

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)
});

Vue v-model 指令的移植

查看源码后发现,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代替,如果做动画的话还是建议保留。

设计一个并发为2的调度器

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

Map && WeakMap使用

最近在研究快应用的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() {

  }
}

reactComponent和reactElement的区别

component对应的是组件类这个概念,每一个自定义的react组件都是一个reactComponent类,对应的vueComponent,存储model。而reactElement对应的是一个element的概念,这个类更多的是指react树结构。此外,react中还有一个_renderedComponent其中对应的_currentElement是一个将react树解析成原生标签的结构,这一层是做dom diff的关键。

精读immer系列

最近使用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横空出世。

addEventListener的option选项

<!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>

vue的一些使用规范,自我体会版。

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()

js回调的一些使用

最近在研究webpack源码,针对一些node工具,回调还是特别实用的手段。

最后的整合,Vue与快应用发送指令策略。

技术背景:
在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对象,并带有dep属性?

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,搞事情(更新等)
        }
      })
    }

MVVM:san Parser

最近花了一些时间整理了一下san的template parser。

Symbol使用

为什么要使用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))

react HOC ref的使用。

今天这里给自己讨论的是ref callbacks的设计模式。虽然react最新的api可以在代码层级上更优美地处理ref。但是在灵活性上ref callbacks更加出众。

typescript的各种import方式

从javscript到typescript

从0开始写typescript

如果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~

使用Proxy&&Reflect

最近在研究Delete操作,V8可以给出相应的优化点,同时用node的命令行操作也给出了一些调用v8内部能力的api。ES6添加了一些新的语法可以给我们更深入操作底层。ProxyReflectObject描述符就是其中两块,而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

go context包的使用心得

这是一篇关于go context的文章。

在引入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会返回已经执行的结果。

仿照mpvue将vue引入我们的框架👌

最近我们的快应用框架需要支持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即可。

Vue VDOM patch详解

VDOM patch

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创建这几种情况。

测试demo

这里选取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`)

详解过程

初始Vnodes

setTimeout之后的Vnodes图示

N节点代表comment节点

注意:操作开始,Vue会将未渲染的节点标记为comment节点。所以如果你第三方框架想要支持类似Vue的patch操作,需要在DOM层实现comment节点。

这里我们要关注的地方在于:1、实现DOM规范node节点的childNodes属性和children属性,childNodes这个类数组包含着最后渲染到视图上的子节点(包含comment节点),children包含所有的element元素。2、两次Vnode数组newCh和OldCh。

调试器中新旧两次Vnodes

调试器中parentElm

第一次循环操作

第一次循环操作

由于第一次循环操作不符合头头、尾尾、头尾、尾头相同的情况,创建一个oldKeyToIdx存储key的对象,供所有的循环操作使用。newStartVnode在oldKeyToIdx找不到对应的Vnode,所以创建一个新的element并插入到oldStartVNode的ele之前,newStartIndex后移一位,进入下一次循环。

调试器中parentElm

第二次循环操作

第二次循环操作

由于第二次循环操作符合头头相等的情况,所以oldStartIndex和newStartIndex都后移一位,进入下一次循环。

调试器中parentElm

第三次循环操作

第三次循环操作

由于第三次操作不符合头头、尾尾、头尾、尾头相同的情况。但是newStartVnode在oldKeyToIdx找到对应的key,并且是相同的element,复用oldVnode的ele,将ele插入到oldStartVnode的ele之前。同时将在oldCh中找到的相同key的Vnode设为undefined,newStartIndex后移一位,进入下次循环。

调试器中parentElm

第四次循环操作

第四次循环操作

由于第四次循环操作不符合头头、尾尾、头尾、尾头相同的情况。newStartVnode在oldKeyToIdx找不到对应的key,所以新建一个新的element,插入到oldStartVnode的ele之前(insertedVnodeQueue用于递归操作,保存内部的elment)。newStartIndex后移一位,进入下次循环。

调试器中parentElm

第五次循环操作

第五次循环操作

由于第五次循环操作符合头头相等的情况,所以oldStartIndex和newStartIndex都后移一位,进入下一次循环。

调试器中parentElm

第六次循环操作

第六次循环操作

由于第六次循环操作oldStartVnode是undefined,所以直接跳过,oldStartIndex后移一位。

调试器中parentElm

第七次循环操作

第七次循环操作

由于第七次循环操作不符合头头、尾尾、头尾、尾头相同的情况。newStartVnode在oldKeyToIdx找不到对应的key,所以新建一个新的element,插入到oldStartVnode的ele之前(insertedVnodeQueue用于递归操作,保存内部的elment)。newStartIndex后移一位,进入下次循环。

调试器中parentElm

跳出循环

跳出循环

由于NewStartIndex > NewEndIndex,所以跳出循环。开始批量删除或者添加操作,这里批量删除oldCh中oldStartIndex到oldEndIndex之间vnode对应的ele。这里删除 444、555、N这三个节点。

调试器中parentElm

最终结果

最终结果

后记

在我们快应用中引入了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收到后按照渲染规则一帧一帧画出来就行了。我们的框架就是用来构造这个格式并维护这个格式的。

Vue slot的使用。

在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是一样的。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.