【若川视野 x 源码共读】第 24 期 | vue2 工具函数

本文参加了由公众号@若川视野发起的每周源码共读活动,点击了解详情一起参与。
这是源码共读的第 24 期,链接:https://juejin.cn/post/7079765637437849614

前言

偶然看到源码共度活动,觉得有用,还有从易到难推荐学习顺序,提交笔记是输出,制定了学习目标,有学习任务,看了几篇若川的博客,觉得挺有意思。

还有跟着实际操作了一下,感受就是流程不错,环境准备就有必要,明白了为什么,像我这种新手,就不知道。

就想可以跟着源码共度活动走一期先,再加上最近感悟源码的重要性,再加上看一个大佬文章说“出问题就看源码”。

跟着大佬走大佬走过的路,希望读过一段时间自己实际解决问题的能力可以有所提升。

知其然知其所以然(不止一次看到过这句话),新手教程开始吧~

开始

环境准备

  • 果然: vue 仓库 .github/contributing.md 贡献指南
  • 果然:项目目录结构 -> shared -> 包含整个代码库中共享的工具
  • 打包后的源码 vue/dist/vue.js 的前 379 行
  • 访问 github1s 快
  • share 里的 util.js 使用了 Flow 类型?什么鬼。
  • 我是新手,所以降低难度,看打包后的 vue/dist/vue.js
    • 是的,我是初学者,知道了要是以后看源码就可以看看.github/contributing.md ,了解一下目录结构,有成就感。

工具函数

vue/src/shared/util.js
vue/dist/vue.js

1. emptyObject(一个冻结的空对象)

源码

14

/*!
 * Vue.js v2.6.14
 * (c) 2014-2021 Evan You
 * Released under the MIT License.
 */
/*  */
var emptyObject = Object.freeze({}); // 一个冻结的空对象,第一层无法修改

文章

Object.isFrozen(emptyObject); // -> true 判断对象是否冻结
  • 【注意】
    • 冻结对象:obj={a:1}; 冻结对象 obj 后,增加属性、删除属性、光明正大修改属性(obj.a=2)、悄悄修改属性(Object.defineProperty(obj,a,{value:2,enumerable:false})),都不可以!
    • 第一层无法修改:若冻结的对象的某属性的值是引用类型,引用类型的属性的值可以修改;若冻结的对象的某属性的值是值类型,不可修改。所以说,冻结的空对象,第一层无法修改。即完全冻结一个对象要深度遍历,鄙人水平有限,暂时只知道递归,应该还有深度遍历非递归方式,还需不断学习。

扩展

Object.freeze()

我被冻在了 vue2 源码工具函数第一行 Object.freeze()(一)

2. isUndef & isDef & isTrue & isFalse(未定义? & 已定义? & 真值? & 假值?)

源码

18-32

// These helpers produce better VM code in JS engines due to their
// explicitness and function inlining.
function isUndef(v) {
  return v === undefined || v === null;
} // v 是未定义的

function isDef(v) {
  return v !== undefined && v !== null;
} // v 是已定义的

function isTrue(v) {
  return v === true;
} // v 是 真值 truly 变量 !!v===true

function isFalse(v) {
  return v === false;
} // v 是 假值 falsely 变量 !!v===false
  • 下班后尽情写感悟

  • 最近校验 Number 类型的一个大小 kb 为单位的字段,使用到了以下 vue2 工具函数源码,嗯,具体是使用到了 isUndef 和 isFalse 。不然我的代码通篇是判断空值未定义值,不易于维护理解。

  • 后面又做其他项目都使用到了。

3. isPrimitive(是原始值 string|number|symbol|boolean?)

源码

37-45

/**
 * Check if value is primitive.
 */
function isPrimitive(value) {
  return (
    typeof value === "string" ||
    typeof value === "number" ||
    // $flow-disable-line
    typeof value === "symbol" ||
    typeof value === "boolean"
  );
} // 判断原始值
  1. 变量类型有值类型和引用类型,常见值类型有 Undefined String Number Boolean Symbol。
  2. 数据类型包括 6 种简单数据类型(也称为原始类型):Undefined、Null、Boolean、Number、String 和 Symbol,和 1 种复杂数据类型:Object。
  3. typeof 操作符使用在一个值上,会返回 ‘undefined’ 、 ‘boolean’ 、 ‘string’ 、 ‘number’ 、 ‘object’ 、 ‘function’ 、 ‘symbol’ 其中之一。
  4. typeof null 返回 ‘object’ ,因为特殊值 null 被认为是一个对空对象的引用。
  5. 所以除了 6 种简单数据类型/原始类型中的 Null 和 1 种复杂数据类型 Object,其他的数据类型的值对其使用 typeof 操作符会返回数据类型变小写。
  6. 对复杂数据类型 Object 的值使用 typeof 操作符:函数在 ECMAScript 中被认为是对象,并不是数据类型,但函数有自己特殊的属性,所以第 3 条明确了:复杂数据类型 Object 里的函数要被区分,所以使用 typeof 操作符区分函数和其他对象,typeof 函数返回’function’。
  7. 再次提示:Null 是一种数据类型,但 typeof 操作符对 null 使用,返回 ‘object’,因为 null 是一个特殊值,被认为是一个空对象的引用。
  8. 当然,Undefined 类型只有一个值就是 undefined,Null 类型只有一个值就是 null,这两个是特殊值。声明变量没有初始化,相当于给变量赋予 undefined 值。
  • 总结:isPrimitive 是用来判断已经定义的常见值类型的值的,即判断原始值。要是把 undefined 也包括进来,那不如判断不是引用类型,即 typeof 变量 !== ‘object’(不是数组、不是对象、不是 null)且 typeof 变量 !== ‘function’。
  • 再总结:isPrimitive 是原始值这个函数判断了变量既不是 Undefined 类型,也不是 Object 类型,又不是函数;判断变量不是 3 个什么类型,那还是判断变量是什么类型吧,即上述那段代码片段: function isPrimitive(value){…}。
  • 接下来就是做项目会不会遇到使用isPrimitive这个函数的情况了(判断是值类型且已定义的情况)。

4. isObject(是引用类型?)

源码

52-54

/**
 * Quick object check - this is primarily used to tell
 * Objects from primitive values when we know the value
 * is a JSON-compliant type.
 */
function isObject(obj) {
  return obj !== null && typeof obj === "object";
} // 判断变量的值是对象不是空,即判断是引用类型的且已经定义过的情况。

文章

typeof null; // -> 'object'
typeof []; // -> 'object'

isObject([]); // true
// 有时不需要严格区分数组和对象。

扩展

深拷贝

deepClone
感想
  • 希望自己走过的每一步路都在为以后打基础。
  • 最近也发现了自己的学习方法,太细节,太拘泥于形式,这样需要很多时间,那可不就效率不高了么,对于现在的企业来说,嗯。
  • 希望自己有一个意识,能朝着某一个好的方向优化。
  • 可是,嗯也对,每个人学习方式不一样。还是继续阅读吧。

5. toRawType(转换成原始类型)

源码

59-63

/**
 * Get the raw type string of a value, e.g., [object Object].
 */
var _toString = Object.prototype.toString;

function toRawType(value) {
  return _toString.call(value).slice(8, -1);
}

今天是 5 月 30 号,今天的这篇 vue2 工具函数的笔记能完结吗,前两天看了一些程序员编程的学习方法,自己的这种方式太低效,自己意识到了。看到的学习方法说,凡事对他有个印象,能不能记住不重要,重要的是有印象,在你需要用的时候你能想到它。所以前面写了许多废话,包括现在的也是废话,也有研究发现,说废话可以提升幸福感。在你需要用的时候,想到,并且查阅资料,就可以完成任务,这就可以,所以,接下来朝着这个方向努力吧。

文章

// 例子
toRawType(""); // 'String'
toRawType(); // 'Undefined'

Object.prototype.toString()方法返回一个表示该对象的字符串。

value 可以继承_toString()中的所有方法和属性。

扩展

Object.prototype.toString()

6. isPlainObject(是纯对象?)

69-71

/**
 * Strict object type check. Only returns true
 * for plain JavaScript objects.
 */
function isPlainObject(obj) {
  return _toString.call(obj) === "[object Object]";
}

文章

isObject([]) === true; // true 数组[]是对象吗?是。
isPlainObject([]) === true; // false 数组[]是纯对象吗?不是。
isPlainObject({}) === true; // true 对象{}是纯对象吗?是。

7. isRegExp(是正则表达式?)

73-75

function isRegExp(v) {
  return _toString.call(v) === "[object RegExp]";
}

文章

// 例子
isRegExp(/ruochuan/); // true

8. isValidArrayIndex(是可用的数组索引值?)

80-83

/**
 * Check if val is a valid array index.
 */
function isValidArrayIndex(val) {
  var n = parseFloat(String(val));
  return n >= 0 && Math.floor(n) === n && isFinite(val);
} // val 转字符串再转浮点值,这个浮点值大于等于 0 且对浮点数向下取整

【注意:】
floor 是 Math 的一个静态方法,Math.floor()这样使用,而不是创建一个 Math 对象的一种方法,Math 不是一个构造函数。

Math.floor(45.95); // 45
Math.floor(45.05); // 45
Math.floor(4); // 4
Math.floor(-45.05); // -46
Math.floor(-45.95); // -46

文章

isFinite(Infinity); // false
isFinite(NaN); // false
isFinite(-Infinity); // false

isFinite(0); // true
isFinite(2e64); // true,在更强壮的 Number.isFinite(null) 中将会得到 false

isFinite("0"); // true,在更强壮的 Number.isFinite('0') 中将会得到 false

数组可用的索引值是0('0')、1('1')、2('2')...

isFinite(val)函数判断传入参数是否是一个有限数值(finite number)

必要情况下,参数会首先转为一个数值。
来源于mdn isFinite()的示例

  • 参考链接

Math.floor() mdn

isFinite mdn

9. isPromise(是 Promise?)

85-91

function isPromise(val) {
  return (
    isDef(val) &&
    typeof val.then === "function" &&
    typeof val.catch === "function"
  );
}

文章

// 例子:
// 判断是不是Promise对象
const p1 = new Promise(function (resolve, reject) {
  resolve("若川");
});
isPromise(p1); // true

这里用 isDef 判断其实相对 isObject 来判断 来说有点不严谨。但是够用。

10. toString(转字符串)

96-102

/**
 * Convert a value to a string that is actually rendered.
 */
function toString(val) {
  return val == null
    ? ""
    : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
    ? JSON.stringify(val, null, 2)
    : String(val);
}

文章

转换成字符串。是数组或者对象并且对象的 toString 方法是 Object.prototype.toString,用 JSON.stringify 转换。

11. toNumber(转数字)

108-111

/**
 * Convert an input value to a number for persistence.
 * If the conversion fails, return original string.
 */
function toNumber(val) {
  var n = parseFloat(val);
  return isNaN(n) ? val : n;
}

文章

转换成数字。如果转换失败依旧返回原始字符串。

12. makeMap(制作一个 map 以判断 key 在 map 中,第二个参数用来期待是小写,把 str 转后的数组的 val 小写一下) & isBuiltInTag(是否是内置的 tag) & isReservedAttribute(是否是保留的属性)

117-139

/**
 * Make a map and return a function for checking if a key
 * is in that map.
 */
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];
      };
}

/**
 * Check if a tag is a built-in tag.
 */
var isBuiltInTag = makeMap("slot,component", true);

/**
 * Check if an attribute is a reserved attribute.
 */
var isReservedAttribute = makeMap("key,ref,slot,slot-scope,is");

文章

// 1.makeMap
传入一个以逗号分隔的字符串,生成一个 map(键值对),并且返回一个函数检测 key 值在不在这个 map 中。第二个参数是小写选项。

// 2.isBuiltInTag
isBuiltInTag 是否是内置的 tag
// 返回的函数,第二个参数不区分大小写
isBuiltInTag('slot') // true
isBuiltInTag('component') // true
isBuiltInTag('Slot') // true
isBuiltInTag('Component') // true

// 3.isReservedAttribute
isReservedAttribute('key') // true
isReservedAttribute('ref') // true
isReservedAttribute('slot') // true
isReservedAttribute('slot-scope') // true
isReservedAttribute('is') // true
isReservedAttribute('IS') // undefined

13. remove(移除数组中的中一项)

144-151

/**
 * Remove an item from an array.
 */
function remove(arr, item) {
  if (arr.length) {
    var index = arr.indexOf(item);
    if (index > -1) {
      return arr.splice(index, 1);
    }
  }
}

用人家的工具函数可比自己写要简洁明了得多。

文章

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

引申:axios InterceptorManager 拦截器源码 中,拦截器用数组存储的。但实际移除拦截器时,只是把拦截器置为 null 。而不是用 splice 移除。最后执行时为 null 的不执行,同样效果。axios 拦截器这个场景下,不得不说为性能做到了很好的考虑。因为拦截器是用户自定义的,理论上可以有无数个,所以做性能考虑是必要的。

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

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

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

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

今天完结不了了,( ̄ o  ̄) . z Z

14. hasOwn(是自己的属性不是原型的属性?)

156-159

/**
 * Check whether an object has the property.
 */
var hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
  return hasOwnProperty.call(obj, key);
}

文章

// 例子

// __proto__ 隐式原型,浏览器实现的原型写法。
// 原型相关API
// Object.getPrototypeOf
// Object.setPrototypeOf
// Object.isPrototypeOf

// .call 指定函数里的this为传入的第一个参数,并执行该函数。

// 例子很生动形象
hasOwn({ __proto__: { a: 1 } }, "a"); // false
hasOwn({ a: undefined }, "a"); // true
hasOwn({}, "a"); // false
hasOwn({}, "hasOwnProperty"); // false
hasOwn({}, "toString"); // false
// 是自己本身的属性,不是通过原型链向上查找的。

15. cached(缓存)

164-193

/**
 * Create a cached version of a pure function.
 */
function cached(fn) {
  var cache = Object.create(null);
  return function cachedFn(str) {
    var hit = cache[str];
    return hit || (cache[str] = fn(str));
  };
}

文章

利用闭包特性,缓存数据

扩展

正则相关知识点

16. camelize(连字符转小驼峰)

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

文章

连字符-转驼峰 on-click => onClick

17. capitalize(首字母转大写)

/**
 * Capitalize a string.
 */
var capitalize = cached(function (str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
});

18. hyphenate(小驼峰转连字符)

/**
 * Hyphenate a camelCase string.
 */
var hyphenateRE = /\B([A-Z])/g;
var hyphenate = cached(function (str) {
  return str.replace(hyphenateRE, "-$1").toLowerCase();
});

文章

onClick => on-click

19. polyfillBind & nativeBind(polyfillBind bind 的垫片)

204-224

/**
 * Simple bind polyfill for environments that do not support it,
 * e.g., PhantomJS 1.x. Technically, we don't need this anymore
 * since native bind is now performant enough in most browsers.
 * But removing it would mean breaking code that was able to run in
 * PhantomJS 1.x, so this must be kept for backward compatibility.
 */

/* istanbul ignore next */
function polyfillBind(fn, ctx) {
  function boundFn(a) {
    var l = arguments.length;
    return l
      ? l > 1
        ? fn.apply(ctx, arguments)
        : fn.call(ctx, a)
      : fn.call(ctx);
  }

  boundFn._length = fn.length;
  return boundFn;
}

function nativeBind(fn, ctx) {
  return fn.bind(ctx);
}

var bind = Function.prototype.bind ? nativeBind : polyfillBind;

我的思考:

  • fn 变成 boundFn,ctx 是对象

  • call 和 apply 功能性质一样,call 的参数是一个指定的 this 值和多个参数列;表,apply 的参数是一个指定的 this 值和一个类数组对象,比如参数列表组成的数组。

    • 执行 call/apply 前的函数,修改 this 指向,给前函数传过去参数执行用。
    • call 和 apply 相当于换车里的司机,换来换去车都能走。
  • bind 函数返回原函数的拷贝,参数是为了指定 this 值和初始参数,改变原函数中的 this 指向。

    • bind 函数相当于换人,车熄火了,给一个变量(遥控),只有加括号,遥控开启了,车可以走(函数执行)。

    • 一个不错的例子:

      //这是一个函数
      function hello(name) {
        //this:执行上下文,程序的运行环境
        //this当前是window,全局
        this.name = name;
        console.log(this.name);
      }
      hello("天才上单");
      
      //bind()可以改变函数中的this指向
      
      //这是一个对象
      const obj = {
        name: "天鹏下凡",
      };
      
      //bind()只绑定不执行
      let f1 = hello.bind(obj, "那就这样吧!");
      console.log(f1());
      
      • 输出:
        天才上单
        那就这样吧!
        undefined
      • 分析:
        1. 执行hello("天才上单"),打印的 this.name 是 hello 参数里的’天才上单’;
        2. 改变 this 指向,obj 就是 hello 改变 this 后的 this,成为 hello 函数里的那就这样吧!赋值给 this.name(也就是 obj.name),所以打印那就这样吧!不打印天鹏下凡
        3. f1 函数与 hello 函数的区别就是 f1()的 this 是 obj,因为 f1() 加了括号就执行了,所以 f1 是啥?是hello.bind(obj,"那就这样吧!"),所以打印的函数里的 this.name 出来的那就这样吧,也就是第 2 点。因为 hello 函数没有 return,所以 f1()一执行,打印 f1()这个东西就是 undefined,规定的所有函数没有 return 的话默认值是 undefined。
      • K.O.
        验证之后确实如此,可以在console.log(this.name);后加上 return 'ohehe is no undefined',实验以下,确实如此。
        所以读的犀牛书不错,很有用。

文章

简单来说就是兼容了老版本浏览器不支持原生的 bind 函数。同时兼容写法,对参数的多少做出了判断,使用call和apply实现,据说参数多适合用 apply,少用 call 性能更好。

扩展

面试官问:能否模拟实现 JS 的 call 和 apply 方法

面试官问:能否模拟实现 Js 的 bind 方法

17. toArray(把类数组转成真正的数组)

229-237

/**
 * Convert an Array-like object to a real Array.
 */
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;
}

文章

把类数组转换成数组,支持从哪个位置开始,默认从 0 开始。

// 例子:
function fn() {
  var arr1 = toArray(arguments);
  console.log(arr1); // [1, 2, 3, 4, 5]
  var arr2 = toArray(arguments, 2);
  console.log(arr2); // [3, 4, 5]
}
fn(1, 2, 3, 4, 5);

18. extend & toObject(合并 & 转对象)

242-260

/**
 * Mix properties into target object.
 */
function extend(to, _from) {
  for (var key in _from) {
    to[key] = _from[key];
  }
  return to;
}

/**
 * Merge an Array of Objects into a single Object.
 */
function toObject(arr) {
  var res = {};
  for (var i = 0; i < arr.length; i++) {
    if (arr[i]) {
      extend(res, arr[i]);
    }
  }
  return res;
}
  • extend(to, _from):to 是要混合成的对象,_from 是要被混合无的对象。
  • toObject(arr):把[{a:1},{b:2}]变成{a:1,b:2}

文章

// 例子:
const data = { name: "若川" };
const data2 = extend(data, { mp: "若川视野", name: "是若川啊" });
console.log(data); // { name: "是若川啊", mp: "若川视野" }
console.log(data2); // { name: "是若川啊", mp: "若川视野" }
console.log(data === data2); // true

// 数组转对象
toObject(["若川", "若川视野"]);
// {0: '若', 1: '川', 2: '视', 3: '野'}
  • 字符串是可迭代的。

19. noop(空函数)

269

/* eslint-disable no-unused-vars */

/**
 * Perform no operation.
 * Stubbing args to make Flow happy without leaving useless transpiled code
 * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/).
 */
function noop(a, b, c) {}
  • 初始化赋值

20. no(no 一直返回 false)

274

/**
 * Always return false.
 */
var no = function (a, b, c) {
  return false;
};

21. identity(返回参数本身)

281

/* eslint-enable no-unused-vars */

/**
 * Return the same value.
 */
var identity = function (_) {
  return _;
};

22. genStaticKeys(生成静态属性)

286-290

/**
 * Generate a string containing static keys from compiler modules.
 */
function genStaticKeys(modules) {
  return modules
    .reduce(function (keys, m) {
      return keys.concat(m.staticKeys || []);
    }, [])
    .join(",");
}

23. looseEqual(宽松相等,判断深度相等)

296-329

/**
 * Check if two values are loosely equal - that is,
 * if they are plain objects, do they have the same shape?
 */
function looseEqual(a, b) {
  if (a === b) {
    return true;
  }
  var isObjectA = isObject(a);
  var isObjectB = isObject(b);
  if (isObjectA && isObjectB) {
    try {
      var isArrayA = Array.isArray(a);
      var isArrayB = Array.isArray(b);
      if (isArrayA && isArrayB) {
        return (
          a.length === b.length &&
          a.every(function (e, i) {
            return looseEqual(e, b[i]);
          })
        );
      } else if (a instanceof Date && b instanceof Date) {
        return a.getTime() === b.getTime();
      } else if (!isArrayA && !isArrayB) {
        var keysA = Object.keys(a);
        var keysB = Object.keys(b);
        return (
          keysA.length === keysB.length &&
          keysA.every(function (key) {
            return looseEqual(a[key], b[key]);
          })
        );
      } else {
        /* istanbul ignore next */
        return false;
      }
    } catch (e) {
      /* istanbul ignore next */
      return false;
    }
  } else if (!isObjectA && !isObjectB) {
    return String(a) === String(b);
  } else {
    return false;
  }
}
  • 先判断是否值类型相等;
  • 再判断是否对象类型相等:对数组、日期、对象进行递归比对;
  • 最后判断:String(a) === String(b)
  • 否则不相等。

24. looseIndexOf(looseIndexOf 是宽松的 indexOf)

336-341

/**
 * Return the first index at which a loosely equal value can be
 * found in the array (if value is a plain object, the array must
 * contain an object of the same shape), or -1 if it is not present.
 */
function looseIndexOf(arr, val) {
  for (var i = 0; i < arr.length; i++) {
    if (looseEqual(arr[i], val)) {
      return i;
    }
  }
  return -1;
}

文章

该函数实现的是宽松相等。原生的indexOf是严格相等。

25. once(确保函数只执行一次)

346-354

/**
 * Ensure a function is called only once.
 */
function once(fn) {
  var called = false;
  return function () {
    if (!called) {
      called = true;
      fn.apply(this, arguments);
    }
  };
}
  • 利用 called 只执行一次

文章

利用闭包特性,存储状态

const fn1 = once(function () {
  console.log("哎嘿,无论你怎么调用,我只执行一次");
});

fn1(); // '哎嘿,无论你怎么调用,我只执行一次'
fn1(); // 不输出
fn1(); // 不输出
fn1(); // 不输出

26. LIFECYCLE_HOOKS(生命周期等)

源码

356-377

var SSR_ATTR = "data-server-rendered";

var ASSET_TYPES = ["component", "directive", "filter"];

var LIFECYCLE_HOOKS = [
  "beforeCreate",
  "created",
  "beforeMount",
  "mounted",
  "beforeUpdate",
  "updated",
  "beforeDestroy",
  "destroyed",
  "activated",
  "deactivated",
  "errorCaptured",
  "serverPrefetch",
];

/*  */

Vue 生命周期

收获

更加的深入理解各个函数的含义。

  1. 浏览的时候是一种理解,查资料的时候是一种理解,写出来笔记又是一种理解。
  2. 查资料的时候感觉记住了,过了一段时间,记笔记时,查的资料感觉有点遗忘,所以又记忆了一遍。

加深了基础知识的理解

  1. 之前学习的基础知识属于理论性的,本次阅读源码属于实践性的;
  2. 实践是检验理论正确性的唯一标准。

多借鉴优秀的开源作品,应用于实践,提升开发效率

  • 取其精华,去其糟粕。因为鄙人的基础不扎实,所以基本全是精华了。

最后

就跟写论文似的,第一次读嘛,不得感谢一下老师同学,这里没有老师同学,就感谢一下若川和群里的群友们。

感谢若川及其源码共度活动,让我有参与到源码阅读中来,以前看过的一些知识,在这里得到了巩固,以前学的只是理论,现在看看源码,有一层深的理解,然后工作中再遇到了问题,可以往这里想,给我提供一些思路,然后就可以试试用用了,实践检验真理。

感谢大家问问题,让我看一看学到东西。

读的时候确实要对照源码读的,后面也有很多用例。

PS:
猴年马月了,11 月 16 日,终于写完了。。。
第一次写此文章,比较繁琐,如果有读者阅读,还望海涵。
快速预览吧~
(/≧▽≦)/

参考链接

【若川视野 x 源码共读】第 24 期 | vue2 工具函数

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

JavaScript 对象所有 API 解析【2020 版】

MDN Web Docs Object.freeze()

js 对象中什么是可枚举性(enumerable)?

JavaScript 属性的可迭代、可修改和可配置特性

Javascript properties are enumerable, writable and configurable

Vue 性能提升之 Object.freeze()

参考文献

《Javascript 高级程序设计(第 4 版)》

《JavaScript 权威指南(原书第 7 版)》

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
相关推荐
  • 暂无相关文章
  • 评论 抢沙发
    头像
    欢迎您留下宝贵的见解!
    提交
    头像

    昵称

    取消
    昵称表情代码图片

      暂无评论内容