vue2工具函数

34 阅读9分钟

本文参加了由公众号@若川视野发起的每周源码共读活动,点击了解详情一起参与

这是源码共读的第24期,链接:juejin.cn/post/707976…

工具函数

1.1 emptyObject

var emptyObject = Object.freeze({});

Object.freeze()

Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。

"use strict"

const obj = {name: '张三', age: 23}
console.log(Object.getOwnPropertyDescriptors(obj)) // { name: { value: '张三', writable: true, enumerable: true, configurable: true }, age: { value: 23, writable: true, enumerable: true, configurable: true } }

const freeObj = Object.freeze(obj)
console.log(Object.getOwnPropertyDescriptors(freeObj)) // { name: { value: '张三', writable: false, enumerable: true, configurable: false }, age: { value: 23, writable: false, enumerable: true, configurable: false } }

// 严格模式下以下操作都会报错
// 添加属性
freeObj.gender = '男'
// 删除属性
delete freeObj.name
// 修改属性值
freeObj.name = '李四'
// 设置原型
Object.setPrototypeOf(freeObj, {sayHello() {console.log('hello')}})
const obj = {name: '张三', age: 23}

const freeObj = Object.freeze(obj)

freeObj.name = '李四'

// 非严格模式下 虽然没有报错 但是对象不会被修改
console.log(freeObj.name) // 张三

PS: 不明白这个空对象用来做什么。

1.2 isUndef

function isUndef(v) {
  return v === undefined || v === null
}

1.3 isDef

function isDef(v) {
  return v !== undefined && v !== null
}

1.4 isTrue

function isTrue(v) {
  return v === true
}

1.5 isFalse

function isFalse(v) {
  return v === false
}

1.6 isPrimitive

// 判断值是否是原始值
function isPrimitive(value) {
  return (
    typeof value === 'string' ||
    typeof value === 'number' ||
    typeof value === 'symbol' ||
    typeof value === 'boolean'
  )
}

console.log(isPrimitive('1')) // true
console.log(isPrimitive(1)) // true
console.log(isPrimitive(Symbol('1'))) // true
console.log(isPrimitive(true)) // true
console.log(isPrimitive({})) // false
console.log(isPrimitive([])) // false
console.log(isPrimitive(null)) // false
console.log(isPrimitive(undefined)) // false
console.log(isPrimitive(function() {})) // false
console.log(isPrimitive(1n)) // false

JavaScript 中有 7 种原始类型:string,number,bigint,boolean,symbol,nullundefined

1.7 isObject

function isObject(obj) {
  return obj !== null && typeof obj === 'object'
}

1.8 _toString

var _toString = Object.prototype.toString;

Object.prototype.toString()

语法:

obj.toString()

返回值:

一个表示该对象的字符串。

描述:

每个对象都有一个 toString() 方法,默认情况下,toString() 方法被每个 Object 对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 "[object type]",其中 type 是对象的类型。

const _toString = Object.prototype.toString;

console.log(_toString.call(1)) // [object Number]
console.log(_toString.call('1')) // [object String]
console.log(_toString.call(true)) // [object Boolean]
console.log(_toString.call(undefined)) // [object Undefined]
console.log(_toString.call(null)) // [object Null]
console.log(_toString.call({})) // [object Object]
console.log(_toString.call([])) // [object Array]
console.log(_toString.call(function(){})) // [object Function]
console.log(_toString.call(new Date())) // [object Date]
console.log(_toString.call(new RegExp())) // [object RegExp]
console.log(_toString.call(new Error())) // [object Error]
console.log(_toString.call(Symbol())) // [object Symbol]

JavaScript 1.8.5 开始,toString() 调用 null 返回 [object Null]undefined 返回 [object Undefined]

覆盖默认的 toString 方法:

可以自定义一个方法,来取代默认的 toString() 方法。该 toString() 方法不能传入参数,并且必须返回一个字符串。

function Student(name, age) {
    this.name = name;
    this.age = age;
}

const s = new Student("John", 20);
// 默认的 toString() 方法
console.log(s.toString()); // [object Object]

// 覆盖默认的 toString() 方法
s.toString = function() { 
    return 'name: ' + this.name + ', age: ' + this.age;
}
console.log(s.toString()); // name: John, age: 20

// 也可以这样写
Student.prototype.toString = function() {
    return 'name: ' + this.name + ', age: ' + this.age;
}
console.log(s.toString()); // name: John, age: 20

1.9 toRawType

// 转换成原始类型
function toRawType(value) {
  return _toString.call(value).slice(8, -1)
}

console.log(toRawType(1)); // Number
console.log(toRawType('1')); // String
console.log(toRawType(true)) // Boolean
console.log(toRawType(null)) // Null
console.log(toRawType(undefined)) // Undefined
console.log(toRawType({})) // Object
console.log(toRawType(Symbol())) // Symbol

String.prototype.slice()

slice() 方法提取某个字符串的一部分,并返回一个新的字符串,且不会改动原字符串。

语法:

str.slice(beginIndex[, endIndex])

参数:

beginIndex

从该索引(以 0 为基数)处开始提取原字符串中的字符。如果值为负数,会被当做 strLength + beginIndex 看待,这里的 strLength 是字符串的长度(例如, 如果 beginIndex-3 则看作是:strLength - 3

endIndex

可选。在该索引(以 0 为基数)处结束提取字符串。如果省略该参数,slice() 会一直提取到字符串末尾。如果该参数为负数,则被看作是 strLength + endIndex,这里的 strLength 就是字符串的长度 (例如,如果 endIndex-3,则是,strLength - 3)。

返回值:

返回一个从原字符串中提取出来的新字符串

描述:

slice() 提取的新字符串包括 beginIndex 但不包括 endIndex

const str = 'welcome to javascript!' // length = 23
console.log(str.slice(8)); // to javascript!
console.log(str.slice(0, 7)) // welcome
console.log(str.slice(11, -1)) // javascript

1.10 isPlainObject

// 是否是纯对象
function isPlainObject(obj) {
  return _toString.call(obj) === '[object Object]'
}

console.log(isPlainObject({})) // true
console.log(isPlainObject([])) // false
console.log(isPlainObject(new Date())) // false

1.11 isRegExp

// 是否是正则表达式
function isRegExp(v) {
  return _toString.call(v) === '[object RegExp]'
}

1.12 isValidArrayIndex

// 是否是可用的数组索引值
function isValidArrayIndex(val) {
  var n = parseFloat(String(val));
  return n >= 0 && Math.floor(n) === n && isFinite(val)
}

console.log(isValidArrayIndex(1)) // true
console.log(isValidArrayIndex(1.1)) // false
console.log(isValidArrayIndex(-1)) // false

parseFloat

语法:

parseFloat(string)

参数:

string 需要被解析成为浮点数的值。

返回值:

给定值被解析成浮点数。如果给定值不能被转换成数值,则会返回 NaN

描述:

parseFloat 是个全局函数,不属于任何对象。

  • 如果 parseFloat 在解析过程中遇到了正号(+)、负号(-)、数字(0-9)、小数点(.)、或者科学记数法中的指数(e 或 E)以外的字符,则它会忽略该字符以及之后的所有字符,返回当前已经解析到的浮点数。

  • 第二个小数点的出现也会使解析停止(在这之前的字符都会被解析)。

  • 参数首位和末位的空白符会被忽略。

  • 如果参数字符串的第一个字符不能被解析成为数字,则 parseFloat 返回 NaN

  • parseFloat 也可以解析并返回 Infinity

  • parseFloat 解析 BigIntNumbers, 丢失精度。因为末位 n 字符被丢弃。

console.log(parseFloat('1.2.3')); // 1.2
console.log(parseFloat(' 1.2.3 ')); // 1.2
console.log(parseFloat('A')); // NaN
console.log(parseFloat('1n')); // 1
console.log(parseFloat('Infinity')); // Infinity

Math.floor()

语法:

Math.floor(x)

参数:

x 一个数字。

返回值:

一个表示小于或等于指定数字的最大整数的数字。

描述:

由于 floorMath 的一个静态方法,你总是应该像这样使用它 Math.floor()

示例:

console.log(Math.floor(1.2)); // 1
console.log(Math.floor(1.9)); // 1
console.log(Math.floor(-1.2)); // -2
console.log(Math.floor(-1.9)); // -2

isFinite

语法:

isFinite(testValue)

参数:

testValue 用于检测有限性的值。

描述:

isFinite 是全局的方法,不与任何对象有关系。

你可以用这个方法来判定一个数字是否是有限数字。isFinite 方法检测它参数的数值。如果参数是 NaN正无穷大 或者 负无穷大,会返回 false,其他返回 true

示例:

console.log(isFinite(Infinity)); // false
console.log(isFinite(-Infinity)); // false
console.log(isFinite(NaN)); // false
console.log(isFinite(undefined)); // false
console.log(isFinite(null)); // true
console.log(isFinite(0)); // true
console.log(isFinite(2e64)); // true
console.log(isFinite('0')); // true

1.13 isPromise

// 判断是否是Promise
function isPromise(val) {
  return (
    isDef(val) &&
    typeof val.then === 'function' &&
    typeof val.catch === 'function'
  )
}

console.log(isPromise(new Promise((resolve, reject) => {
  resolve(1)
}))); // true

1.14 toString

// 转换成字符串。如果 「val是数组」或者 「val是对象并且对象的 toString 方法是 Object.prototype.toString」,用 JSON.stringify 转换。
function toString(val) {
  return val == null
    ? ''
    : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
      ? JSON.stringify(val, null, 2)
      : String(val)
}

console.log(toString(1)); // 1
console.log(toString('1')); // 1
console.log(toString(null)); //
console.log(toString(undefined)); //
console.log(toString({ a: 1 })); // { "a": 1 }
console.log(toString([1, 2, 3])); // [ 1, 2, 3 ]
console.log(toString([1, 2, { a: 1 }])); // [ 1, 2, { "a": 1 } ]

JSON.stringify()

语法:

JSON.stringify(value[, replacer [, space]])

参数:

value 将要序列化成 一个 JSON 字符串的值。

replacer 可选。如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;如果该参数为 null 或者未提供,则对象所有的属性都会被序列化。

space 可选。指定缩进用的空白字符串,用于美化输出;如果参数是个数字,它代表有多少的空格;上限为 10。该值若小于 1,则意味着没有空格;如果该参数为字符串(当字符串长度超过 10 个字母,取其前 10 个字母),该字符串将被作为空格;如果该参数没有提供(或者为 null ),将没有空格。

返回值:

一个表示给定值的 JSON 字符串。

异常:

当在循环引用时会抛出异常 TypeError ("cyclic object value")(循环对象值)

当尝试去转换 BigInt 类型的值会抛出 TypeError ("BigInt value can't be serialized in JSON")BigInt 值不能 JSON 序列化)。

1.15 toNumber

// 转换成数字。如果转换失败返回val。
function toNumber(val) {
  var n = parseFloat(val);
  return isNaN(n) ? val : n
}

console.log(toNumber('1.2')); // 1.2
console.log(toNumber('A')); // A

1.16 makeMap

/**
 * 把字符串转换成对象,返回一个验证函数
 * @param {string} str 
 * @param {boolean} expectsLowerCase 
 * @returns value => boolean
 */
function makeMap(str, expectsLowerCase) {
  var map = Object.create(null);
  var list = str.split(',');
  for (var i = 0; i < list.length; i++) {
    map[list[i]] = true;
  }
  return expectsLowerCase
    ? function (val) { return map[val.toLowerCase()]; }
    : function (val) { return map[val]; }
}

console.log(makeMap('hello,world')('Hello')); // undefined
console.log(makeMap('hello,world', true)('Hello')); // true

1.17 isBuiltInTag

// 检查是否是 slot | component
var isBuiltInTag = makeMap('slot,component', true);

console.log(isBuiltInTag('slot')); // true
console.log(isBuiltInTag('component')); // true
console.log(isBuiltInTag('Slot')); // true
console.log(isBuiltInTag('Component')); // true

1.18 isReservedAttribute

// 检查是否是 key | ref | slot | slot-scope | is
var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is');

console.log(isReservedAttribute('key')); // true
console.log(isReservedAttribute('Key')); // undefined
console.log(isReservedAttribute('ref')); // true
console.log(isReservedAttribute('slot')); // true
console.log(isReservedAttribute('slot-scope')); // true
console.log(isReservedAttribute('is')); // true

1.19 remove

// 移除数组中的一项
function remove(arr, item) {
  if (arr.length) {
    var index = arr.indexOf(item);
    if (index > -1) {
      return arr.splice(index, 1)
    }
  }
}

const arr = [1, 2, 3, 4, 5];
console.log(remove(arr, 2)); // [2]
console.log(arr); // [1, 3, 4, 5]
console.log(remove(arr, 2)); // undefined
console.log(arr); // [1, 3, 4, 5]

Array.prototype.indexOf()

语法:

indexOf(searchElement, fromIndex)

参数:

searchElement 要查找的元素。

fromIndex 可选,开始查找的位置。如果该索引值大于或等于数组长度,意味着不会在数组里查找,返回 -1。如果参数中提供的索引值是一个负值,则将其作为数组末尾的一个抵消,即 -1 表示从最后一个元素开始查找,-2 表示从倒数第二个元素开始查找 ,以此类推。注意:如果参数中提供的索引值是一个负值,并不改变其查找顺序,查找顺序仍然是从前向后查询数组。

返回值:

首个被找到的元素在数组中的索引位置; 若没有找到则返回 -1

描述:

indexOf 使用全等运算 === 判断 searchElement 与数组中包含的元素之间的关系。

示例:

const arr = [1, 2, 3, 4, 5];

console.log(arr.indexOf(2)); // 1
console.log(arr.indexOf(2, 2)); // -1
console.log(arr.indexOf(1, -1)); // -1
console.log(arr.indexOf(1, -2)); // -1
console.log(arr.indexOf(5, -1)); // 4
console.log(arr.indexOf(5, -2)); // 4
console.log(arr.indexOf(4, -1)); // -1
console.log(arr.indexOf(4, -2)); // 3

1.20 hasOwn

var hasOwnProperty = Object.prototype.hasOwnProperty;

function hasOwn(obj, key) {
  return hasOwnProperty.call(obj, key)
}

console.log(hasOwn({ a: 1 }, 'a')); // true
console.log(hasOwn({ a: 1 }, 'b')); // false
console.log(hasOwn({ a: 1 }, 'toString')); // false
console.log(hasOwn({ a: 1, toString: () => {} }, 'toString')); // true
console.log(hasOwn({ a: 1, toString: () => {} }, 'hasOwnProperty')); // false
console.log(hasOwn({ a: 1, hasOwnProperty: () => {} }, 'hasOwnProperty')); // true

Object.prototype.hasOwnProperty()

语法:

obj.hasOwnProperty(prop)

参数:

prop 要检测的属性的名称,String 或者 Symbol

返回值:

布尔值 Boolean

描述:

所有继承了 Object 的对象都会继承到 hasOwnProperty 方法。这个方法可以用来检测一个对象是否含有特定的自身属性;和 in 运算符不同,该方法会忽略掉那些从原型链上继承到的属性。

备注:

即使属性的值是 nullundefined,只要属性存在,hasOwnProperty 依旧会返回 true

示例:

const obj = {name: null, age: undefined}
console.log(obj.hasOwnProperty('name')); // true
console.log(obj.hasOwnProperty('age')); // true

JavaScript 并没有保护 hasOwnProperty 这个属性名,因此,当某个对象可能自有一个占用该属性名的属性时,就需要使用外部的 hasOwnProperty 获得正确的结果:

const obj = {
  name: null, 
  age: undefined, 
  hasOwnProperty: () => {
    return false
  }
};

console.log(obj.hasOwnProperty('name')); // false
console.log(obj.hasOwnProperty('age')); // false
console.log(obj.hasOwnProperty('hasOwnProperty')); // false

Object.prototype.hasOwnProperty.call(obj, 'age'); // true

1.21 cached

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

function upper(str) {
  return str.toUpperCase()
}

// cached 会缓存 upper 函数的计算结果
console.log(cached(upper)('hello')) // HELLO
// 从缓存中取值
console.log(cached(upper)('hello')) // HELLO

1.22 camelize

// 连字符转驼峰
var camelizeRE = /-(\w)/g;
var camelize = cached(function (str) {
  return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; })
});

console.log(camelize('a-b-c')); // aBC
console.log(camelize('hello-world')); // helloWorld

1.23 capitalize

// 首字母转大写
var capitalize = cached(function (str) {
  return str.charAt(0).toUpperCase() + str.slice(1)
});

console.log(capitalize('hello')) // Hello

1.24 hyphenate

// 驼峰转连字符
var hyphenateRE = /\B([A-Z])/g;
var hyphenate = cached(function (str) {
  return str.replace(hyphenateRE, '-$1').toLowerCase()
});

console.log(hyphenate('helloWorld')); // hello-world

1.25 toArray

// 把类数组转换成真数组
function toArray(list, start) {
  start = start || 0;
  var i = list.length - start;
  var ret = new Array(i);
  while (i--) {
    ret[i] = list[i + start];
  }
  return ret
}

function sum() {
  const args = toArray(arguments);
  return args.reduce((a, b) => a + b, 0);
}

console.log(sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));

1.26 extend

// 合并对象
function extend(to, _from) {
  for (var key in _from) {
    to[key] = _from[key];
  }
  return to
}

const obj_1 = {name: 'zhangsan'};
const obj_2 = {age: 18};
const obj_3 = extend(obj_1, obj_2);
console.log(obj_1); // { name: 'zhangsan', age: 18 }
console.log(obj_2); // { age: 18 }
console.log(obj_3); // { name: 'zhangsan', age: 18 }