1, Foreword
This article is the first article in the source code series since the author has been engaged in the front-end for two years. Usually, when building the infrastructure of the project, it is essential to add utils tool functions. How does Vue source code write tool functions? With questions, the essay walks into the first tool function of Vue source code
2, Environmental preparation
In order to reduce the difficulty, we can choose to package according to the method in the contribution guide and convert ts into js
node -v #v16.9.0 # Clone project git clone https://github.com/vuejs/vue-next.git cd vue-next # Global install yarn npm install --global yarn yarn install # Package build code yarn build
You can get Vue next / packages / shared / dist / shared.esm-bundler.js
Or directly see Vue next / packages / shared / SRC / index.ts
3, Tool function
1. babelParserDefaultPlugins babel resolves the default plug-in
/** * List of @babel/parser plugins that are used for template expression * transforms and SFC script transforms. By default we enable proposals slated * for ES2020. This will need to be updated as the spec moves forward. * Full list at https://babeljs.io/docs/en/next/babel-parser#plugins */ const babelParserDefaultPlugins = [ "bigInt", "optionalChaining", "nullishCoalescingOperator", ];
Here are some default plug-ins. If you are interested, you can see
2. EMPTY_OBJ empty object
const EMPTY_OBJ = process.env.NODE_ENV !== "production" ? Object.freeze({}) : {}; // Object.freeze() is used to freeze objects and cannot be added, modified or deleted (including prototypes) // The outermost layer of a frozen object cannot be modified. // example: const EMPTY_OBJ_1 = Object.freeze({}); EMPTY_OBJ_1.name = "Tool function"; console.log(EMPTY_OBJ_1.name); // undefined const EMPTY_OBJ_2 = Object.freeze({ info: { name: "Tool function" } }); EMPTY_OBJ_2.info.nick = "function"; EMPTY_OBJ_2.obj = "props2"; console.log(EMPTY_OBJ_2.info.name); // 'tool function ' console.log(EMPTY_OBJ_2.obj); // undefined console.log(EMPTY_OBJ_2); /** * { * info: { name: "Tool function ", nick:" function "} * } */
process.env.NODE_ENV is an environment variable in a node project. It is generally defined as development and production. Write code according to the environment. For example, the development environment has information such as error reports, but the production environment does not need these error warnings.
3. EMPTY_ARR empty array
const EMPTY_ARR = process.env.NODE_ENV !== "production" ? Object.freeze([]) : []; // example: EMPTY_ARR.push(1); // An error is reported, that is, the production environment freezes the array EMPTY_ARR.length = 3; console.log(EMPTY_ARR.length); // 0
4. NOOP null function
// Usage scenario: 1. Easy to judge, 2. Easy to compress const NOOP = () => {}; // 1. Condition judgment const dev = true; if (dev) { instance.render = function () { console.log("render"); }; } // Can be used as a judgment. if (instance.render === NOOP) { console.log("i"); } // 2. Convenient compression. If it is function() {}, it is not convenient to compress code
5. NO function that always returns false
/** * Always return false. */ const NO = () => false; // Advantages of compressed code
6. isOn judges that it starts with on, and the first letter after on is not a lowercase letter
const onRE = /^on[^a-z]/; const isOn = (key) => onRE.test(key); // example: isOn("onChange"); // true isOn("onchange"); // false isOn("on1"); // true
onRE is regular, ^ on is the beginning of on, ^ a-z is not a-z lowercase letter
7. isModelListener listener
const isModelListener = (key) => key.startsWith("onUpdate:"); // example: isModelListener("onUpdate:change"); // true isModelListener("1onUpdate:change"); // false
Determine whether the string starts with onUpdate:
The startsWith() method detects whether the string starts with the specified prefix.
8. extend consolidation
const extend = Object.assign; // example: const data = { name: "tool", age: 30 }; const data2 = extend(data, { mp: "Front stream", name: "Tool function" }); console.log(data); // {name: "tool function", mp: "front stream", age: 30} console.log(data2); // {name: "tool function", mp: "front stream", age: 30} console.log(data === data2); // true
Merge two objects, such as the same attribute, the latter overrides the former, and there is an inheritance relationship
9. remove deletes an item in the array
const remove = (arr, el) => { const i = arr.indexOf(el); if (i > -1) { arr.splice(i, 1); } }; // example: const arr = ["a", 2, "c"]; remove(arr, "c"); console.log(arr); // ['a', 2]
10. Is hasown an attribute owned by itself
const hasOwnProperty = Object.prototype.hasOwnProperty; const hasOwn = (val, key) => hasOwnProperty.call(val, key); // API related to prototype operation // Object.getPrototypeOf // Object.setPrototypeOf // Object.isPrototypeOf // . call is used to redefine this object // . call example: // Cats eat fish, dogs eat meat, // One day the dog wanted to eat fish // Cat. Eat fish. Call (dog, fish) // The dog ate the fish // example: hasOwn({ name: "Front stream" }, "name"); // true hasOwn({ name: "Front stream" }, "a"); // false hasOwn({ name: "Front stream" }, "toString"); // false hasOwn({ __proto__: { name: "Front stream" } }, "name"); // false
hasOwn can determine whether its own attributes are, excluding the methods on the prototype
11. isArray determines whether it is an array
const isArray = Array.isArray; // example: isArray([1, 2, 3, 4]); // true var obj = { a: 1, b: 2, }; isArray(obj); // false isArray(new Array()); // true isArray("Array"); // false const fakeArr = { __proto__: Array.prototype, length: 0 }; isArray(fakeArr); // false fakeArr instanceof Array; // true // So instanceof is not accurate
12. isMap determines whether it is a Map object
const isMap = (val) => toTypeString(val) === "[object Map]"; // example: const map = new Map(); const o = { p: "Hello World" }; map.set(o, "content"); map.get(o); // 'content' isMap(map); // true
ES6 adds Map and Set. For details, please see
13. isSet determines whether it is a Set object
const isSet = (val) => toTypeString(val) === "[object Set]"; // example: const set = new Set(); isSet(set); // true
More Set objects are used in array de duplication
14. isDate determines whether it is a Date object
const isDate = (val) => val instanceof Date; // example: isDate(new Date()); // true // `instanceof ` will look up according to the prototype chain isDate({ __proto__: new Date() })( // true { __proto__: [] } ) instanceof Array; // true // Array.isArray should be used when judging arrays
15. isFunction determines whether it is a function
const isFunction = (val) => typeof val === "function"; // example: isFunction(() => {}); // true
16. isString determines whether it is a string
const isString = (val) => typeof val === "string"; // example isString(12); // false isString(""); // true isString("12"); // true
17. isSymbol judge whether it is a Symbol
const isSymbol = (val) => typeof val === "symbol"; // example: let s = Symbol(); typeof s; // "symbol"
Symbol represents a unique value
18. isObject determines whether it is an object
const isObject = (val) => val !== null && typeof val === "object"; // example: isObject({}); // true isObject(null); // false // The reason why it is not null is that typeof null is actually an object
19. isPromise judge whether it is Promise
const isPromise = (val) => { return isObject(val) && isFunction(val.then) && isFunction(val.catch); }; // example const p1 = new Promise(function (resolve, reject) { resolve("Ruokawa"); }); isPromise(p1); // true
Judge whether the parameter is an object, and there are. then function and. catch function
20. objectToString object to string
const objectToString = Object.prototype.toString;
21. toTypeString object to string
const toTypeString = (value) => objectToString.call(value); // example: toTypeString({ name: "Zhang San" }); // "[object Object]"
22. The torawtype object is converted to the last few bits of string interception
const toRawType = (value) => { // extract "RawType" from strings like "[object RawType]" return toTypeString(value).slice(8, -1); }; // example: toRawType({ name: "Zhang San" }); // "Object"
23. Isplanobject determines whether it is a pure object
const isPlainObject = (val) => toTypeString(val) === "[object Object]"; isPlainObject({}); // true isPlainObject([]); // false const Person = function () { this.name = "Front stream"; }; isPlainObject(new Person()); // The true constructor is an instance object
24. isIntegerKey determines whether it is a numeric string Key
const isIntegerKey = (key) => isString(key) && key !== "NaN" && key[0] !== "-" && "" + parseInt(key, 10) === key; // The second parameter of parseInt is hexadecimal. // example: isIntegerKey(""); // false isIntegerKey("12"); // true isIntegerKey("012"); // false isIntegerKey("a"); // false
Judge the string type, and it is not NaN, and it does not start with '-', and can be parseInt
25. Makemap & & judge whether it is a pure object
makeMap is to generate a map (key value pair) object from a comma separated string, and return a function to detect whether the key value exists. The second parameter is whether to convert the parameter to lowercase
/** * 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" ); // example: isReservedProp("key"); // true isReservedProp("ref"); // true isReservedProp("onVnodeBeforeMount"); // true isReservedProp("onVnodeMounted"); // true isReservedProp("onVnodeBeforeUpdate"); // true isReservedProp("onVnodeUpdated"); // true isReservedProp("onVnodeBeforeUnmount"); // true isReservedProp("onVnodeUnmounted"); // true
26. cacheStringFunction cache
const cacheStringFunction = (fn) => { const cache = Object.create(null); return (str) => { const hit = cache[str]; return hit || (cache[str] = fn(str)); }; }; let add = (a, b) => a + b; let calculate = cacheStringFunction(add); calculate(10, 20); //30 calculate(10, 20); // With the same parameters, the data is fetched from the cache instead of being recalculated the second time var count = 0; var fn = function (n) { count++; return n < 2 ? n : fn(n - 1) + fn(n - 2); }; fn = cacheStringFunction(fn); for (var i = 0; i <= 10; i++) { fn(i); } console.log(count); // 12 // The count without cache function is 453
Cache function refers to caching the last calculation result. If the same parameter is encountered in the next call, it will directly return the data in the cache
27. camelize hyphen to small hump
// \w is a 0-9a-zA-Z_; number consisting of upper and lower case letters and underscores const camelizeRE = /-(\w)/g; /** * @private */ const camelize = cacheStringFunction((str) => { return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : "")); }); camelize("on-click"); // onClick
28. hyphenate small hump to hyphen
const hyphenateRE = /\B([A-Z])/g; /** * @private */ const hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE$1, "-$1").toLowerCase() ); // example: hyphenate("onClick"); // on-click
29. capitalize string to capitalize after on
// Initial to capital const capitalize = cacheStringFunction( (str) => str.charAt(0).toUpperCase() + str.slice(1) ); /** * @private */ const toHandlerKey = cacheStringFunction((str) => str ? `on${capitalize(str)}` : `` ); // example: toHandlerKey("click"); // onClick
30. hasChanged judge whether there is a change
const hasChanged = (value, oldValue) => !Object.is(value, oldValue); // Object.is a new method added in ES6 to compare whether two values are strictly equal // example: hasChanged(1, 2); // true has changed
31. invokeArrayFns executes the functions in the array
const invokeArrayFns = (fns, arg) => { for (let i = 0; i < fns.length; i++) { fns[i](arg); } }; // example: let arr = [ function (val) { console.log(`I am ${val}`); }, function (val) { console.log(`I'm 18 years old`); }, ]; invokeArrayFns(arr, "Front stream"); /** * I'm the front stream * I'm 18 years old */
32. def defines object attributes
const def = (obj, key, value) => { Object.defineProperty(obj, key, { configurable: true, enumerable: false, value, }); }; // configurable: can this attribute be deleted // enumerable: whether the attribute will be enumerated in the for in loop. // Value: the value returned when getting the property.
33. To number to number
If parseFloat returns NaN, it returns itself. NaN returns true. Otherwise, it returns true
const toNumber = (val) => { const n = parseFloat(val); return isNaN(n) ? val : n; }; // example: toNumber("12"); // 12 toNumber("a12"); // 'a12' parseFloat("a12"); // NaN isNaN(NaN); // true
34. getGlobalThis global object
let _globalThis; const getGlobalThis = () => { return ( _globalThis || (_globalThis = typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {}) ); };
Get the global this point
The first execution of getGlobalThis is undefined, and the following assignment statement is executed
self is the Web Worker global object
window is the browser global object
Global is the Node global object
4, Summary
We have learned functions such as empty object, empty array, empty function, isMap, isSet and isPromise, which complement and expand the native JavaScript. Functions such as camelize, hyphenate and toHandlerKey are the processing of Vue related event names and declaration cycle names
When building the basic framework of our project, we can learn from the tool functions of the source code to make our tool functions more reusable and extensible