【源码阅读】—— vue 工具函数

vue工具函数

最近看了下基于vue-2.6的工具函数代码,对应的地址在这里https://github.com/vuejs/vue/blob/2.6/src/shared/util.js

阅读过程中有一些可以借鉴的函数,可以放到自己的项目中。

1. Object.freeze()

export const emptyObject = Object.freeze({})

这里声明了一个emptyObject,关于Object.freeze(),这个方法我们平时比较少使用,我们可以先看一下关于这个方法的定义(MDN上复制的)。

Object.freeze()方法可以冻结一个对象,被冻结的对象不能被修改,不能像这个对象添加新的属性,不能删除已有属性,不能改变已有属性的可枚举型(enumerable)可配置性(configurable),可修改性(writable)


此外,冻结一个对象后该对象的原型也不能i需改,freeze()返回和传入的参数是相同的对象。

有一个注意点是,freeze只是浅冻结,当freeze的对象中还有一个次级的子对象,是可以被修改的。

例子:

const frozenObj = Object.freeze({ name: 'frozenObj', child:{ name: 'childFrozen' }});

frozenObj.name = 'xxx';
console.log(frozenObj.name); // 打印依然是frozenObj
console.log(frozenObj.child.name) // childFrozen
frozenObj.child.name = 'childFrozenChange...'
console.log(frozenObj.child.name); // childFrozenChange...

通过这个例子我们可以看到,frozenObjname属性没有被成功改变,但是他的子对象childname被改变了。

freeze一起使用的还有Object.isFrozen(),可以用来判断一个对象是否是冻结对象。

Object.freeze() 如何进行深冻结

可以用递归的方式来实现

// 深冻结函数。
function deepFreeze(obj) {

  // 取回定义在 obj 上的属性名
  var propNames = Object.getOwnPropertyNames(obj);

  // 在冻结自身之前冻结属性
  propNames.forEach(function(name) {
    var prop = obj[name];

    // 如果 prop 是个对象,冻结它
    if (typeof prop == 'object' && prop !== null)
      deepFreeze(prop);
  });

  // 冻结自身 (no-op if already frozen)
  return Object.freeze(obj);
}

Object.freeze()的使用场景

当我们使用Vue框架时,如果某个对象数据比较大并且它是响应式的(比如data),我们可以将这个对象freeze,这么做的好处是可以节省性能,当这个对象被冻结之后,Vue就不会将它定义为可响应式的了,这一段的源码我们可以在vue\src\core\observer\index.js这个文件中看到,如果我们要再改变这个data,我们可以重新赋给对象一个Object.freeze()的值,比如

<div v-for="item in list" :key="list.id">
	{{ item }} 
</div>

...
<script>
data() {
  return {
    list: Object.freeze([
    	{ id: '1', val: '1' },
 	    { id: '2', val: '2' }
    ])
  }
},
method: {
  // 从后端拿到新的数据
  async fetchData() {
    const { list } = await fetchApi()
    this.list = Object.freeze(list) // 这里重新赋值了依然使用Object.freeze来处理
  }
}
</script>

源码片段vue\src\core\observer\index.js

/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()

  // Object.getOwnPropertyDescriptor(),从对象中获取一个属性,获得这个属性的相关描述
  // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor

  const property = Object.getOwnPropertyDescriptor(obj, key)
  // **************************  如果是freeze的对象,这里就直接return了  *************************
  if (property && property.configurable === false) {
    return
  }

  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      // 这里的target是干嘛的,没搞懂??
      // 如果 Dep 上的静态属性 target 存在的话
      if (Dep.target) {
        // 向 dep 中添加依赖,依赖是 Watcher 的实例
        dep.depend() // 这一步相当于执行 Dep.target.addDep(this)
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify() // 这里是更新后,通知dep
    }
  })
}
...

通过这段源码可以看到,通过Object.getOwnPropertyDescriptor()可以获取属性值相关的属性,当属性值的configurable时,直接return出去,不执行下面的set,get方法,也就不会处理成响应式了,从而提高了性能。

参考地址:
Vue性能提升之Object.freeze()

Object.seal()

这时候有个疑问,Object.seal()Object.freeze()有什么区别;Vue中的data数据如果用Object.seal()也可以优化吗?

用Object.seal()密封的对象可以改变它们现有的属性。使用Object.freeze() 冻结的对象中现有属性是不可变的。

使用例子:

const aa = Object.seal({name: aa, age: 11})
aa.name = 'aaName'
console.log(aa.name) // aaName

这里可以看到,被seal之后还是可以成功改变的。

那么第二个问题,是否可以优化,我们可以看一下Object.seal之后的对象,它的configurable是否为false。

const aa = Object.seal({name: aa, age: 11})
Object.getOwnPropertyDescriptor(aa, 'name') // {value: 'aa', writable: true, enumerable: true, configurable: false}

可以看到seal过后的属性configurable也是false,所以用seal,也是可以的。

remove函数(移除数组某一项)

这段转载于若川的GitHub。
vue 工具函数源码解析

const remove = (arr, el) => {
    const i = arr.indexOf(el);
    if (i > -1) {
        arr.splice(i, 1);
    }
};

// 例子:
const arr = [1, 2, 3];
remove(arr, 3);
console.log(arr); // [1, 2]

splice 其实是一个很耗性能的方法。删除数组中的一项,其他元素都要移动位置。

引申:axios InterceptorManager 拦截器源码 中,拦截器用数组存储的。但实际移除拦截器时,只是把拦截器置为 null 。而不是用splice移除。最后执行时为 null 的不执行,同样效果。axios 拦截器这个场景下,不得不说为性能做到了很好的考虑。

看如下 axios 拦截器代码示例:

// 代码有删减
this.handlers = [];

// 移除
if (this.handlers[id]) {
    this.handlers[id] = null;
}

// 执行
if (h !== null) {
    fn(h);
}

cache方法

function cached(fn) {
  const cache = Object.create(null)
  return (function cachedFn (str) {
    const hit = cache[str]
      console.log(cache, str);
    return hit || (cache[str] = fn(str))
  })
}

/**
 * Camelize a hyphen-delimited string.
 */
const camelizeRE = /-(\w)/g
const camelize = cached((str) => {
  return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
})

const func2 = cached(str => str + 'func2...')

camelize('slot-scope')
camelize('slot-scope')
func2('slot-scope')
func2('slot-scope')

我这里写了两个function(camelizefunc2),并且传入相同的参数,因为一开始我在想为什么不用把函数名称存下来,
后来发现每次执行都会重新生成一个闭包,也就是说不同函数的cache是独立的。

hasChanged 判断是不是有变化 (Object.is())

这段依然是从若川大佬那篇文章摘录的

hasChanged 这个方法,值得一提的是:我刚写这篇文章时,还没有用Object.is,后来看 git 记录发现有人 提PR 修改为Object.is了,尤大合并了。

const hasChanged = (value, oldValue) => !Object.is(value, oldValue);

该方法用来比较两个值是否严格相等

// compare whether a value has changed, accounting for NaN.
const hasChanged = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue);
// 例子:
// 认为 NaN 是不变的
hasChanged(NaN, NaN); // false
hasChanged(1, 1); // false
hasChanged(1, 2); // true
hasChanged(+0, -0); // false
// Obect.is 认为 +0 和 -0 不是同一个值
Object.is(+0, -0); // false           
// Object.is 认为  NaN 和 本身 相比 是同一个值
Object.is(NaN, NaN); // true
// 场景
// watch 监测值是不是变化了

// (value === value || oldValue === oldValue)
// 为什么会有这句 因为要判断 NaN 。认为 NaN 是不变的。因为 NaN === NaN 为 false

ES5可以通过以下代码部署Object.is。

Object.defineProperty(Object, 'is', {
    value: function() {x, y} {
        if (x === y) {
           // 针对+0不等于-0的情况
           return x !== 0 || 1 / x === 1 / y;
        }
        // 针对 NaN的情况
        return x !== x && y !== y;
    },
    configurable: true,
    enumerable: false,
    writable: true
});
const def = (obj, key, value) => {
    Object.defineProperty(obj, key, {
        configurable: true,
        enumerable: false,
        value
    });
};

Object.defineProperty 涉及到比较重要的知识点。

value 当试图获取属性时所返回的值
writeable 是否可以写入
configurable 是否可以删除
enumerable 是否可以枚举
get() 获取属性值所调用的函数,需要return
set() 设置属性值调用的函数

如果同时设置value和get会发生什么?

答:会报错

Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>

once函数

/**
 * Ensure a function is called only once.
 */
function once (fn) {
  let called = false
  return function () {
    if (!called) {
      called = true
      fn.apply(this, arguments)
    }
  }
}

确保函数只执行一次。

其中我认为oncecached方法适合自己去实现一遍。

本文参考:

初学者也能看懂的 Vue3 源码中那些实用的基础工具函数

这篇文章中推荐的相关文章和书籍

《JavaScript 设计模型与开发实施》
现代js教程
es6阮一峰
《JavaScript面向象编程2》面向象讲的很详细。
《JavaScript权威指南》第7版
js对象所有API解析
老姚谈谈怎么学js
老姚:js正则迷你书问世了
正则前端使用手册
js字符串API全揭秘
js数组API揭秘

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值