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...
通过这个例子我们可以看到,frozenObj
的name
属性没有被成功改变,但是他的子对象child
的name
被改变了。
和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(camelize
和func2
),并且传入相同的参数,因为一开始我在想为什么不用把函数名称存下来,
后来发现每次执行都会重新生成一个闭包,也就是说不同函数的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)
}
}
}
确保函数只执行一次。
其中我认为once
,cached
方法适合自己去实现一遍。
本文参考:
这篇文章中推荐的相关文章和书籍
《JavaScript 设计模型与开发实施》
现代js教程
es6阮一峰
《JavaScript面向象编程2》面向象讲的很详细。
《JavaScript权威指南》第7版
js对象所有API解析
老姚谈谈怎么学js
老姚:js正则迷你书问世了
正则前端使用手册
js字符串API全揭秘
js数组API揭秘