VueBloghyhero6

框架的触类旁通(一)

2019-12-09 / 2019-12-09 / 320次浏览

如何触类旁通都种框架,框架这种东西,如果只是达到简单会用,那么,就会一种框架的神秘感,稍微触及其核心和设计模式,就会一脸茫然加懵逼。

前端框架的三驾马车: Angular、React 和 Vue, 但是实际上我更倾向于这样给他们区分 React 、 Vue 还有 Angular , 尤其以 目前来看 Angular 的小众化,中国市场用的真不多。

提炼一些关键词出来:

响应式框架基本原理:

  1. 数据劫持与代理

  2. 监听数组变化

  3. Object.defineProperty VS Proxy

模板编译原理介绍:

  1. 模板编译实现

  2. 双向绑定实现

发布订阅模式简单应用: 前段时候我写过一篇,小明和房地产的故事。

揭秘虚拟DOM:

  1. 虚拟 DOM diff

  2. 最小化差异应用

又是网上提烂的了一些博客文章讲的:

直观上,数据在变化时,不再需要开发者去手动的更新视图, 而视图会根据变化的数据“自动”进行更新。想完成这个过程我们需要:

  1. 收集视图依赖了哪些数据

  2. 感知被依赖数据的变化

  3. 数据变化的时,自动"通知"需要更新的视图部分进行更新

其实这套理论在网上已经快被讲烂了。

需求知道了,我们把需求过程转换成对应的技术概念就是:

  1. 依赖收集

  2. 数据劫持 / 数据代理

  3. 发布订阅模式

接下来我们继续拆解: 需求知道了就是上代码。

数据劫持与代理

感知数据变化的方法很直接,就是利于原始的JS API 对数据进行数据劫持或数据代理。我们往往通过Object.defineProperty 实现。 这个方法可以定义数据的getter 和 setter, 具体用法不再赘述。

直接跨一步出去看完整版的代码的,由于属性劫持只能劫持一阶段属性,所以需要在此基础上添加一波遍历。

let data = {
    stage:'Gitchat',
    course: {
        title:'前端开发进阶',
        author:['Lucas'],
        publishTime: '2018 年 5 月'
    }
}
    const observe = data => {
        if(!data || typeof data !== 'object') {
            return
        }
        Object.keys(data).forEach(key => {
            let currentValue = data[key]
            observe(currentValue)
            Object.defineProperty(data, key, {
                enumerable: true,
                configurable: false,
                get() {
                    console.log(`getingg ${key} value now,getting value is:`,currentValue)
                    return currentValue
                },
                set(newValue) {
                    currentValue = newValue
                    console.log(`setting ${key} value now,setting value is`, currentValue)
                }
            })
        })
    }

observe(data)

data.course.author.push('Messi')

上述代码的核心思路就是递归操作,递归操作实现深层次对对象监听。就是这句在forEach 里面 observe(currentValue),对对象执行了递归遍历。实现对对象的监听,但是根据作者的描述,为了简化学习效果,依旧是只能对基本类型进行监听。

如:

data.course.title = '前端开发进阶2' // 这段代码是可以实现的

data.course.title = { // 这段代码就无法实现数据监听
    title: '前端开发进阶2'
}

框架实现的第一步,就是数据劫持。

那么接下来,下一步的问题是对数组进行拦截监听测试,但是失败了,测试代码如下:

let data = {
  stage: 'GitChat',
  course: {
    title: '前端开发进阶',
    author: ['Lucas', 'Ronaldo'],
    publishTime: '2018 年 5 月'
  }
}

const observe = data => {
  if (!data || typeof data !== 'object') {
      return
  }
  Object.keys(data).forEach(key => {
    let currentValue = data[key]

    observe(currentValue)

    Object.defineProperty(data, key, {
      enumerable: true,
      configurable: false,
      get() {
        console.log(`getting ${key} value now, getting value is:`, currentValue)
        return currentValue
      },
      set(newValue) {
        currentValue = newValue
        console.log(`setting ${key} value now, setting value is`, currentValue)
      }
    })
  }) 
}

observe(data)

data.course.author.push('Messi')

这就是面试中有的面试官喜欢问的局限性, Object.defineProperty 然而 新版的 Proxy (代理) 就可以监听到数组,

那么我们绕回来,Vue 同样存在这样的问题,但是它对数组实现了监听,它的解决思路是:将数组的常用方法进行重写,覆盖掉原生数组方法,重写之后的数组方法需要能够被拦截。

下面几个方法直接搬 MDN 出来吧,毕竟涉及框架底层,底层的js基础代码也就比较多。

// Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);

console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 }

console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 }

// Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 (请打开浏览器控制台以查看运行结果。)
const person = {
  isHuman: false,
  printIntroduction: function () {
    console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
  }
};

const me = Object.create(person);

me.name = "Matthew"; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // inherited properties can be overwritten

me.printIntroduction();
// expected output: "My name is Matthew. Am I human? true"

用了俩个方法,一个是复制对象的方法,另外一个创建一个新对象。

const arrExtend = Object.create(Array.prototype)
const arrMethods = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

arrMethods.forEach(method => {
  const oldMethod = Array.prototype[method]
  const newMethod = function(...args) {
    oldMethod.apply(this, args)
    console.log(`${method} 方法被执行了`)
  }
  arrExtend[method] = newMethod
})

那么为什么原生数组方法不能被拦截。

https://www.infoq.cn/article/sPCMAcrdAZQfmLbGJeGr

这个位置乱入一下,文章的讲的很详细很好。

这里可以很清楚的看到,我们复写了对象的方法。

总体写下来这篇文章太长,我们大概在这个位置小结一下。

如何使用: 采用 Object.assign 函数直接进行覆盖

Array.prototype = Object.assign(Array.prototype, arrExtend)

let array = [1, 2, 3]
array.push(4)
// push 方法被执行了
const arrExtend = Object.create(Array.prototype)
const arrMethods = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

arrMethods.forEach(method => {
  const oldMethod = Array.prototype[method]
  const newMethod = function(...args) {
    oldMethod.apply(this, args)
    console.log(`${method} 方法被执行了`)
  }
  arrExtend[method] = newMethod
})

Array.prototype = Object.assign(Array.prototype, arrExtend)


let data = {
  stage: 'GitChat',
  course: {
    title: '前端开发进阶',
    author: ['Lucas', 'Ronaldo'],
    publishTime: '2018 年 5 月'
  }
}

const observe = data => {
  if (!data || typeof data !== 'object') {
      return
  }
  Object.keys(data).forEach(key => {
    let currentValue = data[key]

    observe(currentValue)

    Object.defineProperty(data, key, {
      enumerable: true,
      configurable: false,
      get() {
        console.log(`getting ${key} value now, getting value is:`, currentValue)
        return currentValue
      },
      set(newValue) {
        currentValue = newValue
        console.log(`setting ${key} value now, setting value is`, currentValue)
      }
    })
  }) 
}

observe(data)

data.course.author.push('Messi')
// 将会输出:

// getting course value now, getting value is: {//...}
// getting author value now, getting value is: (2) [(...), (...)]
// push 方法被执行了

以上第一小节暂时这些。