vue-next-shared 模块

310 阅读8分钟

学习目标

熟悉 vue 各种工具函数
思考如何为我所用

工具函数

1.1 EMPTY_OBJ 空对象

Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改,冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改已有属性的值,不能修改对象的原型。
作用:对于纯展示数据中,使用 Object.freeze 能有效提升性能。vue源码中对冻结的对象,不会做 getter 和 setter 的转换( **observer **),即无响应式。当然也可以在 created 中定义,vue 也不会对对象进行响应式处理。

const EMPTY_OBJ = (process.env.NODE_ENV !== 'production')
  ? Object.freeze({})
  : {};

// 1、冻结对象是浅冻结。如果需要深冻结,需要递归处理
let freeze1 = Object.freeze({
	name: '堆'
});
freeze1.name = '栈'
console.log(freeze1.name); // '堆'

let freeze1_1 = Object.freeze({
	obj: {
  	name: '堆'
  }
});
freeze1_1.obj.name = '栈'
console.log(freeze1_1.obj.name); // '栈'

// 2、返回和传入的参数相同的对象
let freeze2 = {
	name: '堆'
}
let freeze3 = Object.freeze(freeze2);
console.log(freeze3 === freeze2); // true

// 3、冻结的是指,对象可重新赋值,堆指针重新分配引用
let freeze3 = {
	name: '堆'
}
let freeze4 = Object.freeze(freeze3);
console.log(freeze3 == freeze4); // true
freeze3 = {
	name: '栈'
}
console.log(freeze3 == freeze4); // false

// 4、被冻结后,对象的Property发生变化
let freeze5 = {
	name: '堆'
}
Object.freeze(freeze5)
let freeze6 = Object.getOwnPropertyDescriptor(freeze5, 'name')
console.log(freeze6)
// {value:"堆", writable:false, enumerable:true, configurable:false}

1.2 EMPTY_OBJ 空数组

const EMPTY_ARR = (process.env.NODE_ENV !== 'production') ? Object.freeze([]) : [];

// 1、数组和对象一样,都是引用类型
const freeze1 = Object.freeze([])
freeze1.push(1) // 报错

// 2、无法修改
const freeze2 = Object.freeze([])
freeze2.length = 3;
console.log(freeze2.length); // 0

1.3 NOOP 空函数

export const NOOP = () => {}

// NOOP: No Operation的缩写,意为无操作。在各种语言、框架中都有定义这样的空函数。
// 例:jQuery.noop()。

// 作用:1.提高阅读性,给一些函数提供默认值。2.方便压缩(匿名函数无法被压缩)

1.4 NO 返回false函数

/**
 * Always return false.
 */
export const NO = () => false

// 作用:1.提高阅读性,给一些Boolean参数提供默认值。

1.5 isOn 判断字符串是否为on开头,而且on后面首字母不是小写

const onRE = /^on[^a-z]/
const isOn = (key) => onRE.test(key);

// ^匹配字符串的开始位置,但是在方括号表达式中,表示非
// [a-z]表示一个区间,匹配所有的小写字母
// [^a-z]表示一个区间,匹配所有非小写字母
// ^[a-z]表示一个区间,匹配以小写字母开头

isOn('onClick') // true
isOn('onclick') // false

1.6 isModelListener 判断字符串是否为onUpdate:开头

const isModelListener = (key) => key.startsWith('onUpdate:');

// 判断字符串以指定前缀开始, start: 查找的开始位置
// string.startsWith(searchStr, start)

'onUpdate:'.startsWith('onUpdate:') // true
'conUpdate:'.startsWith('onUpdate:') // false
'conUpdate:'.startsWith('onUpdate:'1) // true

// startsWith实现原理
string.substring(position, position + searchStr.length) === searchStr;
'conUpdate:'.substring(1,  1 + 'onUpdate:'.length) == 'onUpdate:' // true

// 判断字符串以指定前缀开始, position: 字符串结尾处
// string.endsWith(searchStr, position)

'onUpdate:'.endsWith('onUpdate:') // true
'onUpdate:'.endsWith('date', 8) // true

// endsWith实现原理:
string.substr(position - searchStr.length, searchStr.length) === searchStr)

'onUpdate:'.substr(8 - 'date'.length, 'date'.length) == 'date' // true

1.7 extend 对象合并

export const extend = Object.assign

// Object.assign(target, source1, source2)
// 将对象source 属性复制到 target 上, 并返回target

const target = { a: 1, b: 1};
const source1 = { b: 2, c: 2};
const source2 = { c: 3};
extend(target, source1, source2) // {a:1, b:2, c:3}

// 注意:Object.assign是浅拷贝

const target = { a: 1, b: 2};
const source1 = { c: {d: 1}};
extend(target, source1) // {a:1, b:2, c:{d:1}}
source1.c.d = 100
console.log(target) // {a:1, b:2, c:{d:100}}

1.8 remove 数组移除指定的一项

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]

1.9 hasOwn 检测一个属性是否为自己的自有属性

const hasOwnProperty = Object.prototype.hasOwnProperty;
const hasOwn = (val, key) => hasOwnProperty.call(val, key);

const A = {
  A1: {
    A2: {
    	name: 1
    }
  }
};
console.log(hasOwn(A, 'A1')); // true
console.log(hasOwn(A, 'A2')); // false
console.log(hasOwn(A.A1, 'A2')); // true
console.log(hasOwn(A.A1.A2, 'name')); // true

// 本身自有属性,无法检查原型链中是否包含某个属性
var B = {
	__proto__: { c: 1 }
}
console.log(hasOwn(B, 'c')); // false
console.log(hasOwn(B.__proto__, 'c')); // true

1.10 isArray 判断是否为数组

const isArray = Array.isArray;

// 如果参数是数据,返回true,否则返回false。
console.log(isArray([])) // true 

// Array.prototype 也是数组
Array.isArray(Array.prototype); // true

// 原型对象指向数组原型的对象能被分辨
Array.isArray({ __proto__: Array.prototype });	// false

// 对比: instanceof,判断右边参数的原型是否在左边参数的原型链上
// 不能判断 Array.prototype
Array.prototype.__proto__ === Object.prototype // true
Array.prototype instanceof Array // false 

// 可以通过原型链进行误导
{ __proto__: Array.prototype } instanceof Array // true

1.11 isMap 判断是否为Map对象

const objectToString = Object.prototype.toString;
const toTypeString = (value) => objectToString.call(value);
const isMap = (val) => toTypeString(val) === '[object Map]';

// map es6新增的数据结构,相对比Object结构,
// 1、map能接受非字符串key
// 2、map键值是有序的,object无序的
// 3、map是可以迭代的,object需要获取key进行迭代
// 4、map不会修改原型链的键名,Object原型链有可能和对象上的键名冲突

const map = new Map([['first', '1'], ['second', '2']])
map.set("key", 'key')
console.log(map.get('key')); // key
console.log(map.has('key')); // true
console.log(map.has('first')); // true
console.log(isMap(map)); // true

// 属性:size set get has delete clear
// 方法:keys values entries forEach

es6语法可以看阮一峰的ECMAScript 6 入门

1.12 isSet 判断是否为set对象

const isSet = (val) => toTypeString(val) === '[object Set]';

// 例
const s = new Set();
isSet(s); // true

// 类似于数组,但是成员的值都是唯一的,没有重复的值。
// 数组去重方法1
const s = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
console.log([...s]) // 1,2,3,4,5
console.log(s.size) // 5
// 数组去重方法2
const s = Array.from(new Set([1, 2, 3, 4, 5, 5, 5, 5]))
console.log([...s]) // 1,2,3,4,5
console.log(s.size) // 5

// 字符串去重
const s = new Set('abccc');
console.log([...s]) // ['a', 'b', 'c']
console.log(s.size) // 3

// 属性:size
// 方法:add delete has clear

1.13 isDate 判断是否为Date对象

const isDate = (val) => val instanceof Date;

// instanceof,判断右边参数的原型是否在左边参数的原型链上
isDate(new Date()); // true
isDate({__proto__: new Date()}); // true

1.14 isFunction 判断是否为函数对象

const isFunction = (val) => typeof val === 'function';

const fun = function(){}
console.log(isFunction(fun)) // true

// 其他方法:不能判断class函数
// Object.prototype.toString.call(fn)=== '[object Function]'

1.15 isString 判断是否为字符串

const isString = (val) => typeof val === 'string';

// 例
const str = '1'
isString(str) // true

// 其他方法
// Object.prototype.toString.call(str) == "[object String]"
// str.constructor === String

1.16 isSymbol 判断是否为symbol对象

const isSymbol = (val) => typeof val === 'symbol';

// 例
const symbol = Symbol()
isSymbol(symbol) // true

// 其他方法
// Object.prototype.toString.call(Symbol()) == '[object Symbol]'
// symbol.constructor === Symbol

1.17 isObject 判断是否为对象

const isObject = (val) => val !== null && typeof val === 'object';

// 例
const obj = {}
isObject(obj) // true

// 不能辨别数组对象
const arr = []
isObject(arr) // true

// 其他方法
// Object.prototype.toString.call(obj) == '[object Object]'
// obj.constructor === Object

1.18 isPromise 判断是否为Promise

const isPromise = (val) => {
  return isObject(val) && isFunction(val.then) && isFunction(val.catch);
};
 
// 例
const promise = new Promise(()=>{})
isPromise(promise) // true

1.19 objectToString 获取原型对象toString

const objectToString = Object.prototype.toString;
 
// 例: 用于判断类型
console.log(objectToString) // ƒ toString() { [native code] }

1.20 toTypeString 获取值类型的方法

const toTypeString = (value) => objectToString.call(value);
 
// 例: 用于判断类型
toTypeString({}) // '[object Object]'
toTypeString(null) // '[object Null]'
toTypeString(undefined) // '[object Undefined]'

1.21 toRawType 获取原始值类型的方法

const toRawType = (value) => {
  // extract "RawType" from strings like "[object RawType]"
  return toTypeString(value).slice(8, -1);
};

// 例
toRawType({}) // 'Object'
toRawType('') // 'String'

1.22 isPlainObject 是否为纯普通对象

const isPlainObject = (val) => toTypeString(val) === '[object Object]';

 # 例
 isPlainObject({}) // true
 isPlainObject([]) // false

1.23 isIntegerKey 是否为整数数字字符串

const isIntegerKey = (key) => isString(key) &&
  key !== 'NaN' &&
  key[0] !== '-' &&
  '' + parseInt(key, 10) === key;
 
// 例
 isIntegerKey(1) // false
 isIntegerKey('1') // true

1.24 isReservedProp 判断字符串是否为map键名称

/**
 * Make a map and return a function for checking if a key
 * is in that map.
 * IMPORTANT: all calls of this function must be prefixed with
 * \/\*#\_\_PURE\_\_\*\/
 * So that rollup can tree-shake them if necessary.
 */
// 以逗号分割传入的字符串,生成键值对,并返回一个可以检测值是否存在键值对的函数
function makeMap(str, expectsLowerCase) {
  const map = Object.create(null);
  const list = str.split(',');
  for (let i = 0; i < list.length; i++) {
    map[list[i]] = true;
  }
  return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val];
}

const isReservedProp = /*#__PURE__*/ makeMap(
  // the leading comma is intentional so empty string "" is also included
  ',key,ref,' +
  'onVnodeBeforeMount,onVnodeMounted,' +
  'onVnodeBeforeUpdate,onVnodeUpdated,' +
  'onVnodeBeforeUnmount,onVnodeUnmounted');

 // 例
 isReservedProp('key') // true
 isReservedProp('KEY') // false

1.25 cacheStringFunction 闭包缓存数据

const cacheStringFunction = (fn) => {
  const cache = Object.create(null);
  return ((str) => {
    const hit = cache[str];
    return hit || (cache[str] = fn(str));
  });
};

1.26 camelize 连字符转小写驼峰

const camelizeRE = /-(\w)/g;
// 开头为-,\w 代表 0-9 a-z A-Z。如:on-click v-if
const camelize = cacheStringFunction((str) => {
  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''));
});
camelize('on-click') // 'onClick'

1.27 hyphenate 小写驼峰转连字符

const hyphenateRE = /\B([A-Z])/g;
// \B 匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
// \b 匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
// 单词边界就是单词和符号之间的边界,单词是中文字符,英文字符,数字;符号可以是中文符号, 英文符号,空格,制表符,换行。
'edg yyds'.replace(/ yyds\b/g, '') // 'edg'
'edg yyds'.replace(/\bedg /g, '') // 'yyds'
'a b c'.replace(/\b[a-z]/g, (w) => w.toLocaleUpperCase()) // 'A B C'
const hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, '-$1').toLowerCase());
// -$1: $1 匹配的文本,-$1 在匹配的文本前加-
hyphenate('onClick') // 'on-click'

1.28 capitalize 首字母转大写

const capitalize = cacheStringFunction((str) => str.charAt(0).toUpperCase() + str.slice(1));
capitalize('onClick') // 'Onclick' 

1.29 toHandlerKey 首字母转大写并加上on前缀

const toHandlerKey = cacheStringFunction((str) => str ? `on${capitalize(str)}` : ``);
toHandlerKey('click') // 'onClick'

1.30 hasChanged 比较值是否有变化

// compare whether a value has changed, accounting for NaN.
const hasChanged = (value, oldValue) => !Object.is(value, oldValue);
// 例
var edg = {a: 1}; // yyds = edg;
hasChanged(edg, yyds) // false
var yyds = {b: 1}
hasChanged(edg, yyds) // true

1.31 invokeArrayFns 执行数组中的函数

const invokeArrayFns = (fns, arg) => {
  for (let i = 0; i < fns.length; i++) {
    fns[i](arg);
  }
};

1.32 def 对对象定义属性及通过重新定义赋值

const def = (obj, key, value) => {
  Object.defineProperty(obj, key, {
    configurable: true, // 配置描述符:可删除对象属性及属性重新定义赋值
    enumerable: false, // 是否出现在for in 或者 Object.keys()的遍历中
    value
  });
};
// 例如
var person = {}
def(person, 'name', 'jack') // person.name ==> jack
person.name = 'rose' // person.name ==> jack
def(person, 'name', 'rose') // person.name ==> rose

// Object.defineProperty(obj, prop, desc)
// obj 当前对象 prop 属性名称 desc 属性描述(value,writable,enumerable,configurable,get,setter)
// 两种形式定义
let obj = {}
obj.name = 'jack'
// 等价于
Object.defineProperty(obj, 'name', {
  configurable: true,
  enumerable: true,
  writable: true,
  value: 'jack'
});

Object.defineProperty(obj, 'name', {
  value: 'jack'
});
// 等价于
Object.defineProperty(obj, 'name', {
  configurable: false,
  enumerable: false,
  writable: false,
  value: 'jack'
});

1.33 toNumber 转数字

const toNumber = (val) => {
  const n = parseFloat(val);
  return isNaN(n) ? val : n;
};
// parseFloat() 函数可解析一个字符串,并返回一个浮点数。
// 该函数指定字符串中的首个字符是否是数字。如果是,则对字符串进行解析,直到到达数字的末端为止,然后以数字返回该数字,而不是作为字符串。
toNumber('rose') // 'rose'
toNumber('1') // 1
toNumber('1rose') // 1
toNumber('rose1') // 'rose1'
// isNaN判断传入的参数是否能转换成Number类型,如果失败返回false,并不是严格的判断是否等于NaN。
// Number.isNaN判断传入的参数是否等于NaN
isNaN('rose') // true
Number.isNaN('rose') // false 

1.34 getGlobalThis 获取全局this指向

let _globalThis;
const getGlobalThis = () => {
  return (_globalThis ||
    (_globalThis =
      typeof globalThis !== 'undefined'
        ? globalThis
        : typeof self !== 'undefined'
          ? self
          : typeof window !== 'undefined'
            ? window
            : typeof global !== 'undefined'
              ? global
              : {}));
};
// this 即表示作用域的执行上下文self
// window web的全局对象
// self 也可以获取web全局对象
// global node 环境下全局对象

总结

简简单单的工具函数,也收获蛮多,复习了基础知识、学习了如何调试代码,抱着大佬的大腿受益良多。源码共读群是很适合学习的地方,有大佬的指引,还有志同道合的伙伴。万丈高楼平地起。