# lx-study **Repository Path**: xierge/lx-study ## Basic Information - **Project Name**: lx-study - **Description**: js基础知识的进阶 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2022-07-13 - **Last Updated**: 2023-03-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 无敌的李鹏玺and刘思齐 ## javascipt深入理解 ### 函数式编程 #### 什么是高阶函数 * 函数作为参数 * 函数作为另一个函数的返回值 ##### 函数作为参数 **lx_forEach** ```javascript function lx_forEach(array, fn) { for (var i = 0; i < array.length; i++) { fn(arr[i]); } } let arr = [1, 2, 4, 5, 6]; lx_forEach(arr, function (item) { console.log(item); }); ``` **lx_filter** ``` function lx_filter(array, fn) { let results = []; for (var i = 0; i < array.length; i++) { if (fn(arr[i])) { results.push(arr[i]); } } return results; } let arr = [1, 2, 4, 5, 6]; console.log( lx_filter(arr, function (item) { return item % 2 == 0; }) ); ``` **lx_map** ``` function lx_map(array, fn) { let results = []; for (const value of array) { results.push(fn(value)); } return results; } let arr = [1, 3, 5, 7, 9]; let map = lx_map(arr, (item) => item * 2); console.log(map); ``` **lx_some** ``` function lx_some(array, fn) { let flag = false; for (const value of array) { if (fn(value)) { flag = true; break; } } return flag; } let arr = [1, 3, 5, 6, 9]; let some = lx_some(arr, (item) => item % 2 === 0); console.log(some); ``` **lx_every** ```javascript function lx_every(array, fn) { let flag = true; for (const value of array) { if (!fn(value)) { flag = false; break; } } return flag; } let arr = [1, 3, 5, 7, 9]; let every = lx_every(arr, (item) => item % 2 === 1); console.log(every); ``` ##### 函数作为返回值 **base** ``` function base() { let msg = "hello hanghang"; return function () { console.log(msg); } } ``` **lx_once** ``` function lx_once(fn) { let done = false; return function () { if (!done) { done = true; return fn.apply(this, arguments); } }; } let pay = lx_once((money) => { console.log("支付了" + money + "元"); return money; }); const money = pay(1000); console.log(money, "moneymoney"); pay(1000); ``` ##### 闭包 闭包:函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包。 闭包的本质:函数在执行的时候会放到一个执行栈上,当函数执行完毕之后会从执行栈上移除;但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员。 ``` function makePower(power) { return function (number) { return Math.pow(number, power); }; } const power2 = makePower(2); console.log(power2(3)) ``` ##### 纯函数 纯函数:相同的输入得到相同的输出 ,即映射。 优点:可以通过memorize减少 复杂代码二次执行的事件。 ``` // getNumber为纯函数 let getNumber = (a) => { console.log(new Date().getTime()); let e = 0; for (var i = 0; i < 1000000; i++) { e += i; e * i; } return a; }; // memorize做缓存处理 let memorize = (fn) => { let obj = {}; return function () { let key = JSON.stringify(arguments); obj[key] = obj[key] || fn.apply(fn, arguments); return obj[key]; }; }; const _memo = memorize(getNumber); console.log(_memo(1)); console.log(_memo(1)); console.log(_memo(1)); console.log(_memo(1)); ``` 纯函数的副作用:当一个变量在外面,纯函数中用到外部变量,外部变量的改变会导致纯函数不是纯函数。 ``` let num = 18; function is(n) { return n >= num; } ``` ##### 函数柯里化 底层就是 函数嵌套函数 return一个函数 调用的时候形成闭包, 可以使得参数一个一个传 ,可形成不同的闭包。 ``` const getSum = (a, b, c) => a + b + c; function curry(fn) { return function curried(...args) { // 判断形参的个数和传参的个数 if (args.length < fn.length) { return function () { return curried(...args.concat(Array.from(arguments))); }; } return fn(...args); }; } const testCurry = curry(getSum); console.log(testCurry(1)(2, 3)); console.log(testCurry(1)(2)(3)); ``` ##### 函数组合 ``` function compose(...args) { return function (a) { return args.reverse().reduce((pre, current) => current(pre), a); }; } let str = "hello lsq"; const split = (a) => a.split(" "); const reverse = (a) => a.reverse(); const log = (res) => { console.log(res); return res; }; const composeFn = compose(reverse, log, split); ``` #### 函子 函子中存储一个值,这个值就是我们要处理的一个值, 如果想要处理这个值,只能通过 map 方法传入一个处理函数来处理(这个处理函数必须是纯函数) ##### base函子 ``` class Container { static Of(value) { return new Container(value); } constructor(value) { this._value = value; } map(fn) { return Container.Of(fn(this._value)); } } const c = new Container(5).map((i) => i * i); console.log(c); ``` ##### Maybe函子 如果value是null或者undefined直接返回,否则执行方法。 ``` class Maybe { static Of(value) { return new Maybe(value); } constructor(value) { this._value = value; } isNull() { return this._value === undefined || this._value === null; } map(fn) { return Maybe.Of(this.isNull() ? null : fn(this._value)); } } const m = new Maybe(null).map((i) => i * i); console.log(m); ``` ##### Either 函子 Either 函子需要声明两个对象,一种用于处理正常情况,一种用于处理异常情况。 ``` class Left { static Of(value) { return new Left(value); } constructor(value) { this._value = value; } map() { return this; } } class Right { static Of(value) { return new Right(value); } constructor(value) { this._value = value; } map(fn) { return Right.Of(fn(this._value)); } } const jsonparse = (str) => { try { return Right.Of(JSON.parse(str)); } catch (e) { return Left.Of({error: e.message}); } }; let r = jsonparse('{ "name": "zs" }').map((x) => x.name.toUpperCase()); console.log(r); ``` ### ECMAScript ECMAScript 为 Javascript 提供了一些规则。 * web中 js包括 bom dom js * nodejs中 js包括nodeapi js #### ES6(ES2015)新增 * let 块级作用域 只能在所声明的代码块中访问到 * const 常量 块级作用域 * 数组的解构 按照位置解构 * 对象的解构 按照属性名字解构 * 模版字符串 * 带标签的模版字符串 ``` fun`str${name}` fun有多个参数,如事例第一个参数为["str"],第二个为name ``` * 字符串扩展方法 includes startsWith以...开头 endWith以...结尾 * 参数默认值 * Proxy对象 删除属性也可以监测到,修改数组可以监测到 * Reflect对象 ``` 方法1:Reflect.ownKeys() 方法2:Reflect.has() 方法3:Reflect.get() 方法4:Reflect.set() ``` * Promise对象 解决回调地狱 * class 类 * Set 集合对象 ``` 可以使用forEach循环或者 for...of 循环 add() delete() has() clear() size属性获取长度 ``` * Map对象 ``` set() get() forEach((key,value)=>{}) clear() ``` * symbol/bigint ``` Symbol.for()比较字符串是否相同 会默认转化为字符串比较 for in 循环拿不到sysbol属性的key Object.keys() JSON.stringify() symbol属性会被忽略 getOwnPropetySysbols() 可以获取到symbol的key Reflect.ownKeys() 可以获取到所有的key值 ``` * for of 循环 * iterable * generator * ESmodule #### ES7 * arr.includes() * **=== Math.pow() #### ES8 * Object.values() * Object.entries() * Object.getOwnPropertyDescriptors() * async/await ### 异步编程 #### generator ``` function* fn() { let s = yield "hello"; yield "hi"; console.log(s); } let f = fn(); f.next(); // 函数开始执行 在第一个yield停止 获取yield后面的值 f.next("lpx"); // 第二哥yield停止,并把括号中的值传给第一个yield的返回值。 f.next(); // 打印出s ``` #### 手写promise ``` const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected"; class LxPromise { value = undefined; reason = undefined; status = PENDING; successCallback = []; failCallback = []; constructor(executor) { executor(this.resolve, this.reject); } static resolve(value) { if (value instanceof LxPromise) return value; return new LxPromise((resolve) => resolve(value)); } static reject(reason) { if (reason instanceof LxPromise) return reason; return new LxPromise((resolve, reject) => reject(reason)); } static all(array) { return new LxPromise((resolve) => { let arr = []; let index = 0; function setArr(key, value) { arr[key] = value; index++; if (index === array.length) { resolve(arr); } } for (let i = 0; i < array.length; i++) { const current = array[i]; if (current instanceof LxPromise) { current.then((res) => { setArr(i, res); }); } else { setArr(i, current); } } }); } resolve = (value) => { if (this.status !== PENDING) return false; this.value = value; this.status = FULFILLED; while (this.successCallback.length) this.successCallback.shift()(value); }; reject = (reason) => { if (this.status !== REJECTED) return false; this.reason = reason; this.status = REJECTED; while (this.failCallback.length) this.failCallback.shift()(reason); }; then = (successCallback, failCallback) => { successCallback = successCallback ? successCallback : (value) => value; failCallback = failCallback ? failCallback : (reason) => { throw reason; }; let promise2 = new LxPromise((resolve, reject) => { if (this.status === FULFILLED) { let x = successCallback(this.value); disposePromiseThen(x, resolve, reject); // resolve(successCallback(this.value)); } else if (this.status === REJECTED) { let x = failCallback(this.reason); disposePromiseThen(x, resolve, reject); // reject(failCallback(this.reason)); } else { this.successCallback.push(() => { let x = successCallback(this.value); disposePromiseThen(x, resolve, reject); }); this.failCallback.push(() => { let x = failCallback(this.reason); disposePromiseThen(x, resolve, reject); }); } }); return promise2; }; catch(failCallback) { return this.then(undefined, failCallback); } } /** * 判断上一个promise返回的是 promise对象 还是 变量值 * */ function disposePromiseThen(x, resolve, reject) { if (x instanceof LxPromise) { x.then(resolve, reject); } else { resolve(x); } } let lx_promise = new LxPromise((resolve, reject) => { console.log("开始"); setTimeout(() => { resolve(1); }, 2000); }); lx_promise .then((res) => { console.log(res); let p1 = new LxPromise((resolve) => { resolve("测试p1"); }); return p1; }) .then((res) => { console.log(res); }); ``` ### js内存管理以及性能优化 #### 内存管理介绍 内存:由可读写单元组成,表示一片可操作空间 管理:人为的去操作一片空间的申请 使用 和释放 内存管理:开发者主动申请空间 使用空间 释放空间 **js中的内存管理** * 申请 * 使用 * 释放 #### js中的垃圾回收 **js中的垃圾** * js中内存管理是自动的 * 对象不再被引用 * 对象不能从根上访问 js中的可达对象:可以访问到的对象就是可达对象 #### GC算法 * GC垃圾回收的简写 * GC可以找到内存的垃圾并回收 ##### 常见的GC算法 * 引用计数 * 标记清楚 * 标记整理 * 分代回收 ##### 引用计数算法的原理 设置引用数 判断引用数是否为0,引用关系改变时 修改引用数字 优点 * 发现垃圾的时候立即回收 * 最大限度减少程序暂停 缺点 * 无法回收循环引用的对象 * 时间开销大 ##### 标记清除法的原理 分标记和清除两个阶段完成。 优点:解决重复引用不清除 缺点:空间碎片化,不会立即回收 ##### 标记整理算法 标记清除的增强。 清除阶段 先执行整理 再清除 #### V8引擎 * V8引擎是一款主流的js执行引擎。 * V8采用即时编译 * V8内存设限 (64位不超过1.5G,32位不超过800MB) * V8垃圾回收策略:采用分代回收的思想,内存分为新生代、老生代。 ##### V8常用GC算法 * 分代回收 * 空间复制 * 标记清除 * 标记整理 * 标记增量 ##### V8内存分配 * 内存一分为2 * 小空间用于储存新生代对象(32M|16M) * 新生代指的是存活时间较短的对象 ##### 新生代对象回收实现 * 回收过程采用复制算法+标记整理 * 新生代内存区分为两个等大小空间 * 使用空间为From,空闲空间为To * 标记整理后将活动对象拷贝至To * From 与 To 交换空间完成释放 > 一轮GC后还存活的新生代会晋升到老生代,To空间使用率超过25% ##### 老生代对象回收 * 内存大小 64位1.3G 32位700M * 标记清除(主要方式) 标记整理(新生代晋升内存不足时采用) 增量标记 #### 性能优化 * 慎用全局变量 * 缓存全局变量 * 通过原型新增方法 * 避开闭包陷阱 * 避免属性方法的使用 * for循环 length先获取 * 最优for循环 * 优化节点添加 (createDocumentFragment) ### Javascript类型检查 #### 语言类型分类 * 类型安全角度分为 强类型和弱类型 强类型 不存在隐式转换 * 类型检查角度分为 动态类型和静态类型 动态类型在运行的时候才能知道类型是什么 > 弱类型 动态类型语言,大规模应用 typescript更实用 #### 弱类型的问题和强问题的优势 * 错误更早爆露 * 代码更加智能 编码更加准确 * 重构更牢靠 * 减少不必要的类型判断 #### Flow javascript的类型检查器 ##### 快速入门 安装flow yarn add flow-bin --dev 文件顶端添加 //@flow 变量添加注解 yarn flow init 初始化flowconfig文件 yarn flow 进行文件编译 yarn flow stop 停止编译的服务 vscode 设置中 javascript validate 关闭 ##### 编译文件生成在指定文件夹 方式一: 安装flow-remove-types yarn add flow-remove-types 执行命令 yarn flow-remove-types . -d dist dist为指定的目录名称 .为当前目录 方式二: 安装 @babel/core @babel/cli @babel/preset-flow yarn add @babel/core @babel/cli @babel/preset-flow --dev 新建 .babelrc 文件, 文件内容 { "presets": ["@babel/preset-flow"] } 执行命令 yarn babel src -d dist ##### 插件安装 > Flow Language Support 文件路径不能有中文 ``` //@flow //类型推断 根据代码推断出 变量的类型 function square(n: number) { return n * n; } square(5); //类型注解 标记变量的类型 函数返回值 let num: number = 100; function getSum(): number { return 1; } // 原始类型 // string number undefined null symbol const a: string = "lx"; const b: number = NaN; // NaN 100 Infinity const c: null = null; const d: boolean = false; const e: void = undefined; const f: symbol = Symbol(); //数组类型 const arr1: Array = [1]; const arr2: number[] = [1]; const arr3: [string, number] = ["1", 2]; //元祖 // 对象类型 const obj1: { name: string, age?: number } = { name: "lx" }; const obj2: { [key: string]: string } = {}; //函数类型 function foo(callback: (number, string) => void) { callback(18, "lx"); } foo((age, name) => {}); //特殊类型 // 字面量类型 const a1: "foo" = "foo"; const type: "success" | "danger" | "warning" = "success"; type StringOrNumber = string | number; const b1: StringOrNumber = "lx"; const gener: ?number = null; //maybe 类型 // mixed 与 any // mixed 表示所有类型的联合类型 会有安全检查 // any表示忽略检查 function f1(str: mixed) { // str * str; } f1("lx"); f1(24); ``` #### Typescript javascript的超集 ##### 快速入门 安装typescript yarn add typescript --dev 编译文件 yarn tsc 文件名 // tsc为typescrit compare ##### 配置文件 tsc --init ##### 原始类型 string number undefined null symbol boolean ``` const hello = (name:string)=>{ console.log(`hello,${name}`) } hello("lx") // 原始数据类型 const a: string = "lx"; const b: number = 18; const c: boolean = false; const d: null = null; const e: void = undefined; //es6才有 tsconfig文件配置设置为es6 const f: symbol = Symbol(); const obj: { name: string } = { name: "lx" }; // 数组 const arr1: Array = [1, 2, 3]; const arr2: number[] = [4, 5, 6]; // 元祖 const arr3: [number, string] = [18, "lx"]; // 枚举 enum const status = { pendding: 0, fullfiled: 1, reject: 2, }; // 声明enum前面加const 编译后不会新增代码 ,只能取key,相反 编译后代码会有key value. const enum Status { pendding = 0, fullfiled = 1, reject = 2, } console.log(Status.pendding); // 函数类型 function fun1(a: number, b: number): string { return "fun1"; } fun1(1, 23); const fun2: (a: number, b: number) => string = (a, b) => { return "fun2"; }; // 任意类型 function stringify(value: any) { return JSON.stringify(value); } // 隐式类型断言 let foo; foo = 8; // 类型断言 let arr = [1, 34, 5]; // const res = arr.find((item) => item > 0) as number; const res = arr.find((item) => item > 0); // jsx下不使用 const square = res * res; // interface interface PostType { title: string; content: string; subtitle?: string; readonly summary: string; } function printPost(post: PostType) { console.log(post.title); console.log(post.content); } interface Cache { [key: string]: string; } // class class Student { protected name: string; private age: number; protected readonly sex: boolean; constructor(name: string, age: number) { this.name = name; this.age = age; this.sex = true; } getName() { return this.name; } } class P extends Student { constructor(name: string, age: number) { super(name, age); } getName(): string { return super.name; } } const student = new Student("lx", 18); const p = new P("lsq", 19); student.getName(); // proteced可以在继承中访问,实例对象中private protected都不能访问 // class使用interface interface Eat { eat(food: string): void; } interface Run { run(instance: string): void; } class Person implements Eat, Run { eat(food: string) { console.log(`优雅的吃${food}`); } run(distance: string) { console.log("直立行走" + distance); } } class Animal implements Eat, Run { eat(food: string) { console.log(`趴着吃${food}`); } run(distance: string) { console.log("趴着走" + distance); } } // substract类 abstract class Animal { eat(food: string) { console.log("吃" + food); } abstract run(distance: string): void; } class Dog extends Animal { constructor() { super(); } run(distance: string): void { console.log(distance); } } const dog = new Dog(); // 泛型 function fun(value: T):void { console.log(value) } fun("lsq") ``` ## 前端工程化 ### 脚手架工具 常见的脚手架工具 create-react-app vue-cli 等。 脚手架的本质作用: - 创建项目基础结构,提供项目规范和约定 - 相同的组织结构 - 相同的开发范式 - 相同的模块依赖 - 相同的工具配置 - 相同的代码基础 #### Yeoman 官方文档 https://yeoman.io/ ##### Yeoman的安装 ``` yarn add global yo ``` ##### Yeoman的基本使用 // 使用generator-node yarn add global generator-node 运行 yo node 生成 yo node:cli yarn link ##### Yeoman的常规使用步骤 1. 确定需求 2. 找到合适的Generator 3. 全局范围安装Generator 4. 通过yo 运行 Generator 5. 通过命令行交互填写选项 6. 生成所需要的项目结构 ##### Yoeman 自定义 generator-lx ``` // 目录 generators app templates lx.txt index.js ``` > index.js文件为 generator 的核心入口 > > templates下的文件为ejs模版文件 第一步:安装 yeoman-generator ``` yarn add yeoman-generator ``` 第二步:在index.js文件引入,并且导出继承于yeoman-generator的类 ``` const Genrator = require('yeoman-generator'); module.exports = class extends Genrator { write(){ this.fs.write("./lx.txt",Math.random()+"") } } ``` 第三步:npm link 将自定义generator储存在全局 第四步:yo lx 执行 generator-lx,可在文件中看到新增一个lx.txt的文件,文件中有一个随机数 第五步:使用ejs模版生成文件 ``` module.exports = class extends Genrator { write() { // 获取文件所在路径 默认指向templates文件夹 const tml = this.templatePath('lx.txt') // 编写文件输出路径 const output = this.destinationPath('lx.txt') // 传入模版的变量 const context = { username: "lx", age: "18" } // 生成文件 this.fs.copyTpl(tml, output, context) } } ``` 第六步:询问用户问题 ``` // 询问用户,可在prompting中return返回this.prompt()方法 prompting(){ return this.prompt([ { type:"input", name:"username", message:"请输入你的姓名", default:this.appname // appname 为项目生成的目录名称 }, { type:"input", name:"age", message:"请输入你的年龄" }, ]).then(answer=>{ this.answer = answer }) } ``` 第七步:发布 ``` nrm use npm //换源 yarn publish 或 npm publish username:xizige email:2899952565@qq.com password:lpx666888. ``` 附加:yeoman-gennerator除prompting,wirting外的api ``` module.exports = class extends Generator{ initianlizing(){ //获取当前项目状态,获取基本配置参数等 } prompting(){ //向用户展示交互式问题收集关键参数 } configuring(){ //保存配置相关信息且生成配置文件(名称多为'.'开头的配置文件,例如.editorconfig) } default(){ //未匹配任何生命周期方法的非私有方法均在此环节*自动*执行 } writing(){ //依据模板进行新项目结构的写操作 } conflicts(){ //处理冲突(内部调用,一般不用处理) } install(){ //使用指定的包管理工具进行依赖安装(支持npm,bower,yarn) } end(){ //结束动作,例如清屏,输出结束信息,say GoodBye等等 } } ``` #### Plop 小型脚手架工具,类似于yeoman中的sub generator,重复创建相同文件的时候可以使用。 第一步:安装plop ``` yarn add plop ``` 第二步:根目录新建plopfile.js ``` module.exports = plop => { plop.setGenerator("component",{ description:"create a component", prompts:[ { type:"input", name:"name", message:"component name", default:"LxComponent" } ], actions:[ { type:"add", path:"src/components/{{name}/{{name}.js", templateFile:"template/js.hbs" } ] }) } ``` 第三步:模版文件为 .hbs文件 ``` import react from "react"; const {{name}} = ()=>{ return

lx

} ``` 第四步:yarn plop + plopfile.js文件中通过setGenerator定义的名字 ``` yarn plop component ``` #### 脚手架的工作原理 第一步:初始化package.json文件,并且修改bin对应的入口文件 ``` yarn init -y ``` package.json文件 ``` { "name": "lx-cli", "version": "1.0.0", "main": "index.js", "license": "MIT", "bin":"cli.js" } ``` 第二步:cli.js文件, ``` #!/usr/bin/env node // mac或linux需要 chmod 755 cli.js 修改文件的读写权限 // 安装inquirer8.0版本,并且引入 const inquirer = require("inquirer"); // 安装ejs模版,并且引入 yarn add ejs const path = require("path") const fs = require("fs") const ejs = require("ejs") // 通过inquirer.prompt询问,ejs渲染,fs写入并输出文件 inquirer.prompt([ { type:"input", name:"name", message:"project name" } ]).then(answer=>{ const tmlDir = path.join(__dirname,"templates") const outDir = process.cwd() fs.readdir(tmlDir,(err,files)=>{ if(err) throw err; files.forEach(file=>{ ejs.renderFile(path.join(tmlDir,file),answer,(error,res)=>{ if(error) throw error; fs.writeFileSync(path.join(outDir,file),res) }) }) }) }) ``` ### 自动化构建 #### sass_demo index.scss文件 ``` $body-color:red; $body-size:12px; body{ color: $body-color; font-size: $body-size; } ``` > 由于scss文件不被浏览器识别,所以要对文件进行编译。 安装 sass ``` yarn add sass -D ``` 通过sass,把scss文件编译成css文件 ``` yarn sass index.css lx.css ``` > 由于每次输入命令使开发繁琐,可以通过scripts编写命令,直接通过yarn 执行命令。 ``` // yarn dev 直接在浏览器启动 // 命令前加pre是执行命令前先加载的命令 "scripts": { "build":"yarn sass index.scss lx.css", "predev":"yarn build", "dev":"browser-sync ." } ``` ``` // 1.安装npm-run-all // 2.run-p 后面跟执行的命令 // 3.browser-sync . --files lx.css 监听文件变化 "scripts": { "build": "yarn sass index.scss lx.css --watch", // "dev": "browser-sync .", "dev": "browser-sync . --files lx.css", "start": "run-p build dev" } ``` 常见的自动化构建工具:Grunt Gulp FIS #### Grunt ##### Grunt的安装 第一步:新建文件夹,并且初始化package.json文件 ``` yarn init -y ``` 第二步:安装grunt ``` yarn add grunt ``` ##### Grunt的基本使用 第一步:根目录创建 gruntfile.js 文件 ``` module.exports = grunt => { grunt.registerTask("start", () => { console.log("start") }) grunt.registerTask("lx", "lsq的未婚夫", () => { console.log("lx666") }) grunt.registerTask("lsq", "lx的未婚妻", () => { console.log("lsq666") }) // grunt.registerTask("async-task", () => { // setTimeout(() => { // console.log(111) // }, 1000) // }) grunt.registerTask("async-task",function(){ const done = this.async() setTimeout(() => { console.log(111) done() }, 1000) }) grunt.registerTask("default", "默认的描述信息", ["start","lx", "lsq","async-task"]) } ``` > registerTask为注册任务,第一个参数为任务名称,第二个参数为描述信息(不必填),第三个参数为回调函数。 > >grunt默认不支持异步,通过 this.async 来解决。 第二步:执行构建任务 ``` yarn grunt <任务名称> ``` > 任务名称不填写,默认执行 default 任务。 > >可以通过 yarn grunt --help 查看任务描述信息。 ##### Grunt的标记任务失败 ``` module.exports = grunt => { grunt.registerTask("start", () => { console.log("start") }) grunt.registerTask("bad",()=>{ console.log("bad"); return false; }) grunt.registerTask("async-bad",function(){ const done = this.async() setTimeout(() => { console.log("async-bad") done(false) }, 1000) }) grunt.registerTask("default", ["start","async-bad","bad"]) } ``` > 标记任务失败:同步请求 return false,异步请求 done(false),遇到错误将不再继续执行,通过 --force 可以强制执行全部。 ##### Grunt的配置选项 ``` module.exports = grunt => { grunt.initConfig ({ lx:{ age:18 }, x:"lx" }) grunt.registerTask("default",()=>{ console.log(grunt.config()) console.log(grunt.config("x")) console.log(grunt.config("lx.age")) }) } ``` ##### Grunt的多目标任务 ``` module.exports = grunt => { grunt.initConfig({ lx: { options: { wife: "lsq" }, age: 18, likes: "apple" }, }) grunt.registerMultiTask("lx", function () { console.log("target :" + this.target) console.log("data :" + this.data) console.log("options :" + this.options().wife) }) } ``` ##### Grunt的插件 **清除** ``` module.exports = grunt => { grunt.initConfig({ clean: { lx: "lx" } }) grunt.loadNpmTasks("grunt-contrib-clean") } ``` **sass+babel+watch+loadNpmTasks** ``` const sass = require("sass") const loadGruntTasks = require("load-grunt-tasks") module.exports = grunt => { grunt.initConfig({ sass: { options: { implementation: sass, sourceMap: true }, main: { files: { "dist/css/lx.css": "scss/lx.scss" } } }, babel: { options: { sourceMap: true, presets: ["@babel/preset-env"] }, main: { files: { "dist/js/lx.js": "js/lx.js" } } }, watch: { js: { files: ["js/*.js"], tasks: ["babel"] }, css: { files: ["scss/*.scss"], tasks: ["scss"] } } }) // grunt.loadNpmTasks("grunt-babel") // 加载所有的任务 loadGruntTasks(grunt) grunt.registerTask("default", ['sass', "babel", "watch"]) } ``` #### Gulp ##### Gulp的安装 ``` yarn add gulp ``` ##### Gulp的基本使用 ``` // 4.0版本以上 function defaultTask(done) { // place code for your default task here console.log("lx") done(); } exports.default = defaultTask // 4.0版本以下 const gulp = require("gulp"); gulp.task("lx", done => { console.log("lsq") done() }) ``` ##### Gulp的组合任务 ``` const { series, parallel } = require("gulp"); const lx = done => { setTimeout(() => { console.log("lx"); done() }, 1000) } const lsq = done => { setTimeout(() => { console.log("lsq"); done() }, 1000) } // exports.default = series(lx, lsq) exports.default = parallel(lx, lsq) ``` > series串行,parallel并行 ##### Gulp的异步任务 ``` exports.callback = done => { console.log("callback"); done() } exports.callback_error = done => { console.log("callback_error"); done(new Error("callback_error")) } exports.promise = () => { console.log("promise") return Promise.resolve() } exports.promise_error = () => { console.log("promise_error") return Promise.reject("promise_error") } const timer = time => new Promise(resolve => setTimeout(resolve, time)) exports.async = async () => { await timer(1000); console.log("async") } const fs = require("fs") exports.stream = done => { const readStream = fs.createReadStream("package.json"); const writeStream = fs.createWriteStream("lx.txt") readStream.pipe(writeStream) // return readStream // 返回readStream的本质是对它的end方法进行监听 readStream.on("end", () => { done() }) } ``` ##### Gulp的构建过程 ```javascript const fs = require("fs"); const {Transform} = require("stream"); exports.default = () => { const read = fs.createReadStream("src/css/lx.css"); const write = fs.createWriteStream("src/css/lx.min.css") const transform = new Transform({ transform(chunk, encoding, callback) { let str = chunk.toString() let out = str.replace(/\s+/g, "").replace(/\/\*.+?\*\//g, ""); callback(null, out) } }) read.pipe(transform).pipe(write) return read } ``` ##### Gulp的api和插件使用-css ```javascript const {src, dest} = require('gulp'); const rename = require("gulp-rename") const cleanCss = require("gulp-clean-css") exports.default = () => { return src("src/css/*.css").pipe(cleanCss()).pipe(rename({ extname: ".min.css" })).pipe(dest("dist/css/")) } ``` ##### lx-gulp-demo ```javascript const {src, dest, parallel, series, watch} = require("gulp") const gulpLoadPlugins = require("gulp-load-plugins") const plugins = gulpLoadPlugins() const sass = require('gulp-sass')(require('sass')); const babel = require('gulp-babel'); const imagemin = require("gulp-imagemin") const del = require("del") const browserSync = require("browser-sync") const minifyCss = require("gulp-clean-css") const bs = browserSync.create() const data = { menus: [ { name: 'Home', icon: 'aperture', link: 'index.html' }, { name: 'Features', link: 'features.html' }, { name: 'About', link: 'about.html' }, ], pkg: require('./package.json'), date: new Date() } const style = () => { return src("src/assets/styles/*.scss", {base: "src"}) .pipe(sass({outputStyle: "compressed"}).on('error', sass.logError)) .pipe(dest("temp/")) .pipe(bs.reload({stream: true})) } const script = () => { return src("src/assets/scripts/*.js", {base: "src"}) .pipe(babel({ presets: ['@babel/preset-env'] })) .pipe(dest("temp/")) .pipe(bs.reload({stream: true})) } const html = () => { return src("src/*.html", {base: "src"}) .pipe(plugins.swig({data, defaults: {cache: false}})) .pipe(dest("temp/")) .pipe(bs.reload({stream: true})) } const image = () => { return src("src/assets/images/**", {base: "src"}) .pipe(imagemin()) .pipe(dest("dist/")) } const font = () => { return src("src/assets/fonts/**", {base: "src"}) .pipe(imagemin()) .pipe(dest("dist/")) } const ext = () => { return src("public/**").pipe(dest("dist/public")) } const clean = () => { return del(["dist", "temp"]) } const useref = () => { return src("temp/*.html", {base: "temp"}) .pipe(plugins.useref({searchPath: ["temp", "."]})) .pipe(plugins.if('*.js', plugins.uglify())) .pipe(plugins.if('*.html', plugins.htmlmin({ collapseWhitespace: true, minifyCSS: true, minifyJS: true }))) .pipe(plugins.if("*.css", minifyCss())) .pipe(dest("dist")) } const serve = () => { watch(["src/assets/styles/*.scss"], style) watch(["src/assets/scripts/*.js"], script) watch(["src/*.html"], html) watch([ "src/assets/images/**", "src/assets/fonts/**", "public/**" ], bs.reload) bs.init({ notify: false, port: 647, open: true, //默认 true // files: "dist/**", server: { baseDir: ["temp", "src", "public"], routes: { '/node_modules': "node_modules" } }, }) } const compile = parallel(style, script, html) const dev = series(compile, serve) const build = series(clean, parallel(series(compile, useref), image, font, ext)) module.exports = { dev, build, clean, } ``` > 通过del库清空dist目录 > > 通过gulp-sass和sass库编译压缩scss文件 > > 通过gulp-babel和@babel/core和@babel/preset-env库编译css文件 > > 通过gulp-imagemin库编译压缩图片文件 > > 通过gulp-load-plugins减少引入关于gulp的插件,例如swig > > 通过browser-sync库启动服务,port,open,notify(打开页面库相关提示),baseDir(从服务器取文件的目录,从前到后),routes,files等价于bs.reload( {stram:true}),bs.reload刷新 > > 通过gulp-useref吧html文件中引入的node_modules依赖项打包并引入,uglify,htmlmin,minifyCss对文件进行压缩 ##### lx-gulp-cli lib/index.js ``` const {src, dest, parallel, series, watch} = require("gulp") const gulpLoadPlugins = require("gulp-load-plugins") const plugins = gulpLoadPlugins() const sass = require('gulp-sass')(require('sass')); const babel = require('gulp-babel'); const imagemin = require("gulp-imagemin") const del = require("del") const browserSync = require("browser-sync") const minifyCss = require("gulp-clean-css") const bs = browserSync.create() const cwd = process.cwd() let config = { build: { src: "src", dist: "dist", temp: "temp", public: "public", paths: { styles: "assets/styles/*.scss", scripts: "assets/scripts/*.js", htmls: "*.html", images: "assets/images/**", fonts: "assets/fonts/**", } } } try { const loadConfig = require(`${cwd}/gulp.config.js`) config = Object.assign(config, loadConfig) } catch (e) { } const style = () => { return src(config.build.paths.styles, {base: config.build.src, cwd: config.build.src}) .pipe(sass({outputStyle: "compressed"}).on('error', sass.logError)) .pipe(dest(config.build.temp)) .pipe(bs.reload({stream: true})) } const script = () => { return src(config.build.paths.scripts, {base: config.build.src, cwd: config.build.src}) .pipe(babel({ presets: [require('@babel/preset-env')] })) .pipe(dest(config.build.temp)) .pipe(bs.reload({stream: true})) } const html = () => { return src(config.build.paths.htmls, {base: config.build.src, cwd: config.build.src}) .pipe(plugins.swig({data: config.data, defaults: {cache: false}})) .pipe(dest(config.build.temp)) .pipe(bs.reload({stream: true})) } const image = () => { return src(config.build.paths.images, {base: config.build.src, cwd: config.build.src}) .pipe(imagemin()) .pipe(dest(config.build.dist)) } const font = () => { return src(config.build.paths.fonts, {base: config.build.src, cwd: config.build.src}) .pipe(imagemin()) .pipe(dest(config.build.dist)) } const ext = () => { return src("**", {base: config.build.public, cwd: config.build.public}).pipe(dest(config.build.dist)) } const clean = () => { return del([config.build.dist, config.build.temp]) } const useref = () => { return src(`${config.build.temp}/${config.build.paths.htmls}`) .pipe(plugins.useref({searchPath: [config.build.temp, "."]})) .pipe(plugins.if('*.js', plugins.uglify())) .pipe(plugins.if('*.html', plugins.htmlmin({ collapseWhitespace: true, minifyCSS: true, minifyJS: true }))) .pipe(plugins.if("*.css", minifyCss())) .pipe(dest(config.build.dist)) } const serve = () => { watch(config.build.paths.styles, {cwd: config.build.src}, style) watch(config.build.paths.scripts, {cwd: config.build.src}, script) watch(config.build.paths.htmls, {cwd: config.build.src}, html) watch([ config.build.paths.images, config.build.paths.fonts, ], {cwd: config.build.src}, bs.reload) watch("**", {cwd: config.build.public}, bs.reload) bs.init({ notify: false, port: 647, open: true, //默认 true // files: "dist/**", server: { baseDir: [config.build.temp, config.build.src, config.build.public], routes: { '/node_modules': "node_modules" } }, }) } const compile = parallel(style, script, html) const dev = series(compile, serve) const build = series(clean, parallel(series(compile, useref), image, font, ext)) module.exports = { dev, build, clean, } ``` bin/index ``` #!/usr/bin/env node process.argv.push("--cwd") process.argv.push(process.cwd()) process.argv.push("--gulpfile") process.argv.push(require.resolve("../lib/index.js")) require('gulp-cli')(); ``` #### FIS ##### FIS3的安装 ``` yarn add FIS3 -g ``` ##### FIS的使用 ``` fis3 release -d output ``` ##### FIS的配置 新建 fis-conf.js 文件 ``` fis.match('*.{js,scss,png}', { release: '/assets/$0' }) fis.match('**/*.scss', { rExt: '.css', parser: fis.plugin('node-sass'), optimizer: fis.plugin('clean-css') }) fis.match('**/*.js', { parser: fis.plugin('babel-6.x'), optimizer: fis.plugin('uglify-js') }) ``` ### 模块化开发 #### 模块化的演变过程 1. 一个文件夹对应一个模块,污染全局作用域,会有命名冲突问题,完全依靠约定。 2. 每个模块封装成一个对象,减小命名冲突的概率,没有私有空间,依赖关系不明显。 3. 自执行函数,参数传依赖项,闭包实现私有空间。 #### 模块化的规范 **commonjs规范** * 加载方式:同步 * module.exports 导出,require 引入 * 文件可以被重复引用、加载。第一次加 载时会被缓存,之后再引用就直接读取缓存 **ESmodule规范** * 加载方式:异步,export导出,import引入 * 引入的值改变了,其他引用地址也会改变 * 每个ESmodule有独立的私有作用域 * 导出的是引用关系,引入的内容只读,不可修改 * 引入不能节省扩展名,不能默认指向文件夹的index.js * // 添加module相当于默认添加了defer,延迟执行。自动采用严格模式,省略use strict,引入的文件是通过cores请求到的,url引入百度的js会有跨域报错。每个ESmodule有独立的私有作用域。 ``` // 场景1:导出成员过多 import * as lx from "./lx.js" // 场景2:动态导入 import("./lx.js").then() // 场景3:引入默认导出和分别导出 import { lx, lsq, default as lpd } from "./lx.js" import lpd, { lx, lsq } // 场景4:直接导出导入的成员 export { lx, lsq } from "./lx.js" ``` > ie不支持****,可以使用browser-es-module-loader > > nodejs也可以使用esmodule,使用 .mjs后缀名文件 > > Package.json文件的type为module的时候,js文件默认为ESmodule模块 **AMD规范** require.js提供了AMD规范 ,define定义模块,require加载模块。 大多数第三方库都支持AMD规范,但是使用起来相对复杂,模块js的请求频繁。 **CMD规范** 最大的特点是懒加载,不需要在定义模块的时候声明依赖,可以在模块执行时动态加载依赖,并且同时支持同步和异步加载模块。 类似于commonjs规范,define定义模块,exports 或者 module.exports 导出。 ### webpack 优点:整合代码,代码分割,前端整体模块化 #### 快速入门 ``` // 安装 yarn add webpack webpack-cli -D ``` **触发资源加载的方式** * css的@import * import引入,require引入,AMD规范引入等 * css的background-image引入图片 * html中img的src ,a标签的href属性 ``` const path = require("path"); const {CleanWebpackPlugin} = require("clean-webpack-plugin"); module.exports = { entry: "./src/index.js", output: { filename: "lx.js", path: path.resolve(__dirname, "dist") }, module: { rules: [{ test: /\.css$/, use: ["style-loader", "css-loader"] }, { test: /\.(jpg|png|svg|gif)$/, // type: 'asset/source', use: { loader: "url-loader", options: { limit: 10 * 1024, esModule: false }, }, type: 'javascript/auto' }, { test: /\.js$/, use: { loader: "babel-loader", options: { presets: ["@babel/preset-env"] } } }, { test: /\.html$/, use: { loader: "html-loader", options: { sources: { list: [ { tag: "a", attribute: "href", type: "src" }, { tag: "img", attribute: "src", type: "src" } ] } } } }] }, plugins: [new CleanWebpackPlugin()], mode: "none" } ``` #### 语法提示 const {Configuration} = require("webpack") /** * @type {Configuration} */ #### loader的使用 例如编译css文件 module:{ rules:[ { test:/\.css$/, use:["style-loader","css-loader"] } ] } css-loader的作用是将css模块加载到js模块中。 style-loader的作用是使用css模块。 **相关loader** 编译转换、文件操作、代码检查 * file-loader * url-loader * babel-loader * style-loader * css-loader * sass-loader * postcss-loader * eslint-loader * vue-loader **自定义loader------markdown-loader** 对一种文件进行webpack编译的时候,最后执行的loader要返回js代码.source为文件内容 const {marked} = require("marked") module.exports = source=>{ const html = marked(source) const code = `module.exports = ${JSON.stringify(html)}` return code } #### plugin **常用的plugin** * clean-webpack-plugin * html-webpack-plugin ``` new HtmlWebpackPlugin({ title: "lsq666", meta: { viewport: "width=device-width" }, filename: "lsq.html", template: "./template.html", name: "lsq" }) ``` **自定义plugin------RemoveCommentsPlugin** ``` // 去除注释 class RemoveCommentsPlugin{ apply(compiler){ // emit钩子是输出 asset 到 output 目录之前执行 compiler.hooks.emit.tap("RemoveCommentsPlugin",compilation=>{ for ( const name in compilation.assets){ if(name.endsWith(".js")){ const contents = compilation.assets[name].source() const noComments = contents.replace(/\/\*{2,}\/\s?/g,"") compilation.assets[name] = { source:()=>noComments, size:()=>noComments.length } } } }) } } module.exports = RemoveCommentsPlugin ``` #### devServer ``` devServer: { proxy: { "/api": { target: "https://gitee.com", pathRewrite: { '^/api': "" }, changeOrigin: true } }, open: true, hot: true, // static: ["public"] }, ``` > webpack4是contentBase,webpack5是static,关闭false,默认public,开发时使用此属性,生产打包使用CopyWebpackPlguin #### devtool sourcemap:映射源代码和转化后代码的映射。 带module的模式是没有经历过loader加载的。 * eval模式 只能知道报错的文件路径,构建速度快。 * eval-source-map 报错可查到文件的某一行某一列,可以找到源文件 * source-map 报错可查到文件的某一行某一列,可以找到源文件 * cheap-source-map 报错可查到文件的某一行,可以找到babel转化后的文件 * cheap-module-source-map 报错可查到文件的某一行,可以找到源文件 * inline-source-map * hidden-source-map * nosources-source-map 开发选择cheap-module-source-map模式,可查看loader转化之前的源代码,只需要查看行代码报错,启动打包慢,通过webpack-dev-server监视模式下更新更快。 生产选择nosources-source-map,可以找到错误行,但查不到源代码。 sourcemap不是webpack特有功能,是webpack支持sourcemap。 #### 模块热更新(HMR) webpack4以上版本默认自动开启,解决页面自动刷新 ``` 热替换 js模块代码更新改变会刷新页面。可通过以下api监视文件变化,还原更新前的数据。 if(module.hot){ module.hot.accept(url, () => { // hot为true此处报错会使页面自动刷新 // hotonly为true时保存代码报错的时候不会刷新页面 console.log("文件改变了") }) } ``` #### DefinePlugin(定义变量) ``` plugins:[ webpack.DefinePlugin({ API_BASE_URL:'"www.xizige.cn"' // 也可以使用JSON.stringify() }) ] ``` #### tree-shaking sideEffects(副作用) ``` optimization:{ // 只打包导出外部被使用的方法 usedExports:true, // 压缩 minimize:true, // 合并模块 concatenateModules:true , sideEffects: true 或者 [文件路径] //package.json文件 sideEffects设置为false或数组 }, ``` > ESModule支持tree-shaking,babel-loader最新版本转化代码支持tree-shaking,commonjs不支持。 #### code spliting 和 动态导入 ``` optimization: { splitChunks: { chunks: "all", minSize:0 } } ``` #### 环境区分使用webpackconfig npm库 webpack-merge合并公共配置。 ``` const path = require("path") const prodConfig = require("./config/webpack.prod.js") const devConfig = require("./config/webpack.dev.js") const {merge} = require("webpack-merge") module.exports = (env, arg) => { let config = { entry: "./src/index.js", output: { filename: "lx.js", path: path.resolve(__dirname, "dist") }, module: { rules: [{ test: /\.css$/, use: ["style-loader", "css-loader"] }, { test: /\.(jpg|png|svg|gif)$/, // type: 'asset/source', use: { loader: "url-loader", options: { limit: 10 * 1024, esModule: false }, }, type: 'javascript/auto' }, { test: /\.js$/, use: { loader: "babel-loader", options: { presets: ["@babel/preset-env"] } } }, { test: /\.html$/, use: { loader: "html-loader", options: { sources: { list: [ { tag: "a", attribute: "href", type: "src" }, { tag: "img", attribute: "src", type: "src" } ] } } } }] } } if (env.prod) { config = merge(config, prodConfig) } if (env.dev) { config = merge(config, devConfig) } return config } ``` **dev** ``` const HtmlWebpackPlugin = require("html-webpack-plugin") module.exports = { mode: "development", devServer: { proxy: { "/api": { target: "https://gitee.com", pathRewrite: { '^/api': "" }, changeOrigin: true } }, open: true, hot: true, static: ["public"] }, plugins: [new HtmlWebpackPlugin({ title: "lx牛逼", meta: { viewport: "width=device-width" }, filename: "index.html" })], devtool: "cheap-module-source-map" } ``` **prod** ``` const {CleanWebpackPlugin} = require("clean-webpack-plugin"); const CopyWebpackPlugin = require("copy-webpack-plugin") module.exports = { plugins: [new CleanWebpackPlugin(), new CopyWebpackPlugin({ patterns: ["public"] })], mode: "production", devtool: false } ``` #### 生产模式常用插件 * webpack.definePlugin * mini-css-extract-plugin * optimize-css-assets-webpack-plugin * terser-webpack-plugin #### 多文件打包 ``` const { Configuration } = require("webpack") const HtmlWebpackPlugin = require("html-webpack-plugin") const { CleanWebpackPlugin } = require("clean-webpack-plugin") const path = require("path") /** * @type {Configuration} */ const config = { entry: { home: "/src/home.js", about: "/src/about.js" }, output: { path: path.resolve(__dirname, "./dist"), filename: "[name].bundle.js" }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ template: "./home.html", filename: "home.html", chunks: ["home"] }), new HtmlWebpackPlugin({ template: "./about.html", filename: "about.html", chunks: ["about"] }), ], optimization: { splitChunks: { chunks: "all", minSize:0 } }, mode:"none" } module.exports = config ``` ### Rollup **安装使用** ``` yarn add rollup -D yarn rollup ./src/index.js --format iife --file dist/bundle.js yarn rollup -C ``` > Package.json文件type设置为module **插件** ``` import json from "rollup-plugin-json" import resolve from "rollup-plugin-node-resolve" import commonjs from "rollup-plugin-commonjs" export default { input: ['src/index.js',"src/lx.js"] , output: { // file: 'dist/bundle.js', // format: 'iife' dir: "dist", format: "amd" }, plugins: [json(), resolve(), commonjs()] }; ``` **Parcel** 用法简单,自带动态导入,HMR等。 ``` yarn add paracel yarn parcel 文件 ``` > package.json文件的main删除,source属性为 入口文件地址 ### 规范化标准 #### Eslint **安装及使用** ``` yarn add eslint yarn eslint --init yarn eslint 文件路径 yarn eslint 文件路径 --fix ``` **配置项** ``` module.exports = { env: { browser: true, es2021: true }, extends: 'standard', overrides: [ ], parserOptions: { ecmaVersion: 'latest' }, rules: { 'no-alert': 'error' }, globals: { jquery: 'readonly' } } ``` > webpack使用eslint-loader,gulp使用gulp-eslint #### stylelint **安装及使用** ``` yarn add stylelint yarn add stylelint-config-standard yarn add stylelint-config-sass-guidelines ``` **配置项** ``` module.exports = { extends:'stylelint-config-standard' } ``` #### Prettier ``` yarn add prettier -D yarn prettier . --write ``` #### git hooks ``` yarn add husky -D "scripts": { "precommit":"lint-staged" }, "husky": { "hooks": { "pre-commit": "npm run precommit" } }, "lint-staged":{ "*.js":[ "eslint", "git add" ] } ``` ### Webpack源码 **webpack编译过程的三大阶段** 1. 配置初始化 2. 内容编译 3. 内容输出 #### 单文件导出 从 纯Commonjs模块化的结果可以看出,webpack4是一个自执行函数,参数为一个对象,其中key是文件的路径,可以理解为模版id,value是模块的内容,我把这个对象称为为模块信息,在函数体中有`__webpack_require__`获取模块信息的方法函数,和一些m、c、d、r等多种功能函数。Webpack5也是 一个自执行函数,但是没有把模块信息当作参数,而是在函数内部声明一个变量`__webpack_modules__`,webpack5的结果在这种情况更加的简单,少了一些功能函数,只有一个`__webpack_require__`获取模块信息的函数。 以下是几个功能函数的作用: `__webpack_require__.d`:通过esmodule方式`export const age = 18`导出的内容通过Object.defineProperty()设置getter函数。 `__webpack_require__.r`通过Symbol属性判断是否为ES6,并且给模块新增__esModule属性。 `__webpack_require__.o`通过Object.hasOwnProperty属性判断导出的模块是否已经通过`__webpack_require__.d`新增此属性。 `__webpack_require__`通过模块的id拿到模块的内容。首先判断模块是否加载,如果已经加载走缓存。 #### 手写功能函数 ``` (function (modules) { // 缓存的模块 let installedModules = {}; // 使用__webpack_require__来替换 ESModule和Commonjs的导入 function __webpack_require__(moduleId) { if (installedModules[moduleId]) { return inst n bmn alledModules[moduleId].exports } let module = installedModules[moduleId] = { exports: {}, i: moduleId, l: false } modules[moduleId].call(module.exports, module, module.exports, __webpack_require__) module.l = true return module.exports } // 定义当前模块是es6类型 __webpack_require__.r = function (exports) { if (typeof Symbol !== "undefined" && Symbol.toStringTag) { Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }) } Object.defineProperty(exports, "__esModule", { value: true }) } // 判断该模块是否含有此属性 __webpack_require__.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property) } // 模块定义相关属性,并且增加getter __webpack_require__.d = function (exports, name, getter) { if (!__webpack_require__.o(exports)) { Object.defineProperty(exports, name, { getter, enumerable: true }) } } // 根据模块化方式不同来设置返回值 __webpack_require__.n = function (module) { let getter = module && module.__esModule ? function getDefault() { return module["default"] } : function getModuleExports() { return module }; __webpack_require__.d(getter, "a", getter) return getter } // 执行入口文件 return __webpack_require__("./src/index.js") }) ({ "./src/home.js": (function (module, exports) { module.exports = "我是home" }), "./src/index.js": (function (module, exports, __webpack_require__) { console.log("我是index") const home = __webpack_require__("./src/home.js") console.log(home) module.exports = { name: "lx" } }) }) ``` #### 懒加载原理 import() 引入文件资源的时候,webpack会将引入的这个文件单独打包,当需要加载这个文件的时候,通过jsonp的方式动态创建一个script标签再中。 以下是单文件懒加载的相关功能函数~ ``` // 加载指定的value的模块内容 __webpack_require__.t = function (value, mode) { // value一般是模块id if (mode & 1) value = __webpack_require__(value) if (mode & 8) return value; if ((mode & 4) && typeof value === "object" && value && value.__esModule) { return value } let ns = Object.create(null); __webpack_require__.r(ns) Object.defineProperty(ns, "default", { enumerable: true, value: value }) if (mode & 2 && typeof value !== "string") { for (let key in value) { __webpack_require__.d(ns, key, function (key) { return value[key] }.bind(null, key)) } } return ns } // 合并模块定义, 处理promise function webpackJsonpCallback(data) { debugger let chunkIds = data[0] let moreModules = data[1] let resolves = [], chunkId, moduleId for (let i = 0; i < chunkIds.length; i++) { chunkId = chunkIds[i]; if (Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) { resolves.push(installedChunks[chunkId][0]) } installedChunks[chunkId] = 0 } for (moduleId in moreModules) { modules[moduleId] = moreModules[moduleId] } while (resolves.length) { resolves.shift()() } } // 实现jsonp加载内容 利用promise实现异步加载操作 __webpack_require__.e = function (chunkId) { let promises = [] let installedChunkData = installedChunks[chunkId] // 不等于0说明模块之前没有加载成功过 if (installedChunkData !== 0) { // 条件成立说明文件正在加载中,不成立说明首次加载 if (installedChunkData) { promises.push(installedChunkData[2]) } else { let promise = new Promise((resole, reject) => { installedChunkData = installedChunks[chunkId] = [resole, reject] }) promises.push(installedChunkData[2] = promise) let script = document.createElement("script") script.src = jsonpScriptSrc(chunkId) document.head.appendChild(script) } } return Promise.all(promises) } function jsonpScriptSrc(chunkId) { return __webpack_require__.p + "" + chunkId + ".bundle.js" } ``` #### tapable库 SyncHook, SyncBailHook, SyncWaterfallHook, SyncLoopHook, AsyncParallelHook, AsyncParallelBailHook, AsyncSeriesHook, AsyncSeriesBailHook, AsyncSeriesWaterfallHook官方文档提供的九个钩子。 **同步钩子函数** SyncHook ``` const { SyncHook } = require("tapable") const hook = new SyncHook(["name", "age"]) hook.tap("fn1", function (name, age) { console.log(`fn1=====>${name},${age}`) }) hook.tap("fn2", function (name, age) { console.log(`fn2=====>${name},${age}`) }) hook.call("lx", 25) ``` SyncBailHook ``` // 熔断 某个事件监听返回的不是undefined,将会中止执行下一个监听 const { SyncBailHook } = require("tapable") const hook = new SyncBailHook(["name", "age"]) hook.tap("fn1", function (name, age) { console.log(`fn1=====>${name},${age}`) }) hook.tap("fn2", function (name, age) { console.log(`fn2=====>${name},${age}`) return "res2" }) hook.tap("fn3", function (name, age) { console.log(`fn3=====>${name},${age}`) }) hook.call("lx", 25) ``` SyncWaterfallHook ``` // 下个监听函数的参数默认值是传递的参数,但可以接受上个监听函数的返回值 const { SyncWaterfallHook } = require("tapable") const hook = new SyncWaterfallHook(["name", "age"]) hook.tap("fn1", function (name, age) { console.log(`fn1=====>${name},${age}`) return "res1" }) hook.tap("fn2", function (name, age) { console.log(`fn2=====>${name},${age}`) return "res2" }) hook.tap("fn3", function (name, age) { console.log(`fn3=====>${name},${age}`) return "res3" }) hook.call("lx", 25) ``` SyncHook ``` const { SyncHook } = require("tapable") const hook = new SyncHook(["name", "age"]) hook.tap("fn1", function (name, age) { console.log(`fn1=====>${name},${age}`) }) hook.tap("fn2", function (name, age) { console.log(`fn2=====>${name},${age}`) }) hook.call("lx", 25) ``` AsyncParallelHook-------异步的三种方式 ``` const { AsyncParallelHook } = require("tapable") const hook = new AsyncParallelHook(["name", "age"]) // 第一种 // hook.tap("fn1", function (name, age) { // console.log(`fn1=====>${name},${age}`) // }) // hook.tap("fn2", function (name, age) { // console.log(`fn2=====>${name},${age}`) // }) // hook.callAsync("lx", 25, function () { // console.log("回调") // }) // 第二种 // console.time("time") // // callback函数表示结束 // hook.tapAsync("fn1", function (name, age, callback) { // setTimeout(() => { // console.log(`fn1=====>${name},${age}`) // callback() // }, 2000) // }) // hook.tapAsync("fn2", function (name, age, callback) { // setTimeout(() => { // console.log(`fn2=====>${name},${age}`) // callback() // }, 2000) // }) // hook.callAsync("lx", 25, function () { // console.log("回调") // console.timeEnd("time") // }) // 第三种 console.time("time") hook.tapPromise("fn1", function (name, age) { return new Promise(resolve => { setTimeout(() => { console.log(`fn1=====>${name},${age}`) resolve() }, 2000) }) }) hook.tapPromise("fn2", function (name, age) { return new Promise(resolve => { setTimeout(() => { console.log(`fn2=====>${name},${age}`) resolve() }, 2000) }) }) hook.promise("lx", 25).then(res => { console.log("回调") console.timeEnd("time") }) ``` AsyncParallelBailHook------通过callback传递参数表示错误,熔断 ``` const { AsyncParallelBailHook } = require("tapable") const hook = new AsyncParallelBailHook(["name", "age"]) console.time("time") // callback函数表示结束 hook.tapAsync("fn1", function (name, age, callback) { setTimeout(() => { console.log(`fn1=====>${name},${age}`) callback() }, 2000) }) hook.tapAsync("fn2", function (name, age, callback) { setTimeout(() => { console.log(`fn2=====>${name},${age}`) callback("err") }, 2000) }) hook.tapAsync("fn3", function (name, age, callback) { setTimeout(() => { console.log(`fn3=====>${name},${age}`) callback() }, 3000) }) hook.callAsync("lx", 25, function () { console.log("回调") console.timeEnd("time") }) ``` AsyncSeriesHook-------和同步任务除了多一个回调,没有别的区别 ``` const { AsyncSeriesHook } = require("tapable") const hook = new AsyncSeriesHook(["name", "age"]) console.time("time") hook.tapPromise("fn1", function (name, age) { return new Promise(resolve => { setTimeout(() => { console.log(`fn1=====>${name},${age}`) resolve() }, 2000) }) }) hook.tapPromise("fn2", function (name, age) { return new Promise(resolve => { setTimeout(() => { console.log(`fn2=====>${name},${age}`) resolve() }, 2000) }) }) hook.promise("lx", 25).then(res => { console.log("回调") console.timeEnd("time") }) ``` ## Vue ### Vue-Router的实现原理 **动态路由** 动态路由传值可以使用props传,只需要props设置为true ``` { path: '/about/:id', name: 'about', props: true, component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue') } ``` **两种路由的区别** hash模式基于锚点,使用onhashchange时间监听路由变化 history模式基于html5的History Api实现,history.pushState() history.replaceState() **nodejs处理history模式** 通过 connect-history-api-fallback 中间件处理。 **nginx处理history模式** 本地安装nginx ``` brew install nginx ``` nginx启动等相关命令 ``` brew services start nginx brew services stop nginx brew services restart nginx ``` 修改nginx.conf ``` location / { root html; index index.html index.htm; try_files $uri $uri/ /index.html; } ``` **简单实现vue-router** install方法:1.判断插件是否被引入。2.原型挂载$router。3.创建routerMap,创建router-view,router-link和重写popstate事件的监听 ``` let _Vue = null export default class LxRouter { constructor(options) { this.mode = options.mode || "hash"; this.routeMap = {}; this.options = options this.data = _Vue.observable({ current: "/" }) } init() { this.createRouterMap(); this.initComponents() this.initEvent() } static install(Vue) { // 1.判断插件是否已经安装 if (LxRouter.install.stalled) { return } LxRouter.install.stalled = true // 2.把Vue构造函数记录到全局变量 _Vue = Vue // 3.创建Vue实例时候传入的router对象注入到Vue实例上 _Vue.mixin({ beforeCreate() { if (this.$options.router) { _Vue.prototype.$router = this.$options.router; console.log(this.$options.router) this.$options.router.init() } } }) } createRouterMap() { this.options.routes.forEach( route => { this.routeMap[route.path] = route.component } ) console.log(this.routeMap) } initComponents() { const self = this _Vue.component("router-link", { props: { to: String }, // template: '', render(h) { return h("a", { attrs: { href: this.to, }, on: { click: this.clickHandler }, }, [this.$slots.default]) }, methods: { clickHandler(e) { history.pushState({}, "", this.to) this.$router.data.current = this.to e.preventDefault(); } } }) _Vue.component("router-view", { render(h) { const component = self.routeMap[self.data.current] return h(component) } }) } initEvent() { window.addEventListener("popstate", () => { this.data.current = window.location.pathname }) } } ``` ### 模拟Vue响应式原理 ``` // 1.将data数据通过_proxyData挂载到vue的实例上 // 2.通过Observer将data设置setter、getter // 3.Compiler类根据元素的所有节点 渲染指令 和 文本渲染 // 4.Dep 类收集修改后需要更新的方法 // 5.Watcher类观察者 class LxVue { constructor(options) { this.$options = options this.$data = options.data || {} this.$el = typeof options.el === "string" ? document.querySelector(options.el) : options.el this._proxyData(this.$data) new Observer(this.$data) new Compiler(this) } _proxyData(data) { for (let key in data) { Object.defineProperty(this, key, { enumerable: true, configurable: true, get() { return data[key] }, set(val) { data[key] = val }, }) } } } // 将$data数据变成响应式 class Observer { constructor(data) { this.walk(data) } // 遍历对象的所有属性 walk(data) { if (!data || typeof data !== "object") return Object.keys(data).forEach(key => { this.defineReactive(data, key, data[key]) }) } defineReactive(obj, key, val) { this.walk(val) let dep = new Dep() let self = this Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { Dep.target && dep.addSub(Dep.target) return val }, set(newValue) { if (newValue === val) { return } val = newValue self.walk(val) // 发送通知 dep.notify() } }) } } class Compiler { constructor(vm) { this.el = vm.$el this.vm = vm this.compile(this.el) } compile(el) { let childNodes = el.childNodes; Array.from(childNodes).forEach(node => { if (this.isTextNode(node)) { this.compileText(node) } else if (this.isElementNode) { this.compileElement(node) } if (node.childNodes && node.childNodes.length) { this.compile(node) } }) } compileElement(node) { Array.from(node.attributes).forEach(attr => { let attrName = attr.name if (this.isDirective(attr.name)) { attrName = attrName.substr(2) let key = attr.value this[`${attrName}Updater`] && this[`${attrName}Updater`](node, this.vm[key], key) } }) } textUpdater(node, value, key) { node.textContent = value new Wacther(this.vm, key, newValue => node.textContent = newValue) } modelUpdater(node, value, key) { node.value = value new Wacther(this.vm, key, newValue => node.value = newValue) node.addEventListener("input", (e) => { this.vm[key] = e.target.value }) } compileText(node) { let reg = /\{\{(.+?)\}\}/ let value = node.textContent if (reg.test(value)) { let key = RegExp.$1.trim() let oldValue = value node.textContent = value.replace(reg, this.vm[key]) new Wacther(this.vm, key, newValue => node.textContent = oldValue.replace(reg, newValue)) } } isDirective(attrName) { return attrName.startsWith("v-") } isTextNode(node) { return node.nodeType === 3 } isElementNode(node) { return node.nodeType === 1 } } class Dep { constructor() { this.subs = [] } addSub(sub) { sub && sub.update && this.subs.push(sub) } notify() { this.subs.forEach(sub => { sub.update() }) } } class Wacther { constructor(vm, key, cb) { this.vm = vm; this.key = key; this.cb = cb; Dep.target = this this.oldValue = vm[key] Dep.target = null } update() { let newValue = this.vm[this.key] this.cb(newValue) } } ``` ### vdom-snabbdom https://github.com/snabbdom/snabbdom/blob/HEAD/README-zh_CN.md **环境配置** ``` 1. yarn init -y 2. yarn add parcel-bundler -D 3. 配置package.json文件 "dev":"parcel index.html --open", "build":"parcel build index.html" 4. yarn add snabbdom@2.1.0 ``` **案例1** 渲染单节点 ``` import { init } from "snabbdom/build/package/init" import { h } from "snabbdom/build/package/h" let patch = init([]) // h函数的参数 // 第一个参数:标签+选择器 // 第二个参数:如果是字符串表示标签中的文本内容 let vnode = h("div#container.cls", "hello word") let app = document.querySelector("#app") // patch函数的参数 // 第一个参数:旧的VNode,也可以是DOM元素 // 第二个参数:新的VNode let oldVnode = patch(app, vnode) vnode = h("div#container.lx", "hello lx") patch(oldVnode, vnode) ``` **案例2** 渲染节点,包含子节点以及清空节点内容 ``` import { init } from "snabbdom/build/package/init" import { h } from "snabbdom/build/package/h" const patch = init([]) let vnode = h("div#container", [ h("h1", "hello lx"), h("p", "lsq") ]) const app = document.querySelector("#app") let oldVnode = patch(app, vnode) // 清空内容 setTimeout(() => { patch(oldVnode, h("!")) }, 2000) ``` **案例3** 使用style,eventListeners两个模块 ``` import { init } from "snabbdom/build/package/init"; import { h } from "snabbdom/build/package/h"; import { styleModule } from "snabbdom/build/package/modules/style"; import { eventListenersModule } from "snabbdom/build/package/modules/eventlisteners"; // 注册模块 const patch = init([styleModule, eventListenersModule]) const eventHandler = () => { console.log("click") } let vnode = h("div", [ h("h1", { style: { background: "red" } }, "hello lx"), h("p", { on: { click: eventHandler } }, "hello p") ]) const app = document.querySelector("#app") patch(app, vnode) ``` > h函数生成VNode > > Init 将第一个数组参数循环根据钩子的名称处理合并在cbs数组,并且返回一个patch函数 **diff算法流程** 1.通过sel和key判断新旧节点是否相同,相同的话通过patchVnode方法进行对比,不相同创建新节点。 2.若相同节点,判断新节点是否是text,若是,判断新旧都有children,若都有并且不相等,则通过updateChildren更新。如果新children存在,旧children不存在,则创建节点。如果旧children存在,新children不存在,则删除节点。 3.updateChildren方法以下四种情况比较,判断新旧的头是否为相同节点,判断新旧的尾是否为相同节点,判断新头旧尾是否为相同节点,判断新尾旧头是否为相同节点,如果以上四种情况都不满足,判断新节点在老children是否存在,不论是否存在则插入到未处理的节点前面,若存在将旧children的这一项设置为undefined。 4.最后根据新旧节点谁相交来判断是新增节点,还是删除节点。 ### Vue源码剖析 ``` vue inspect 命令可以输出vue cli的 webpack 配置文件 vue cli 创建的项目使用的vue运行时的版本 runtime ``` #### 首次渲染 * 初始化初始化,静态成员,实例成员 * new Vue() * this._init * Vm.$mount * Vm.$mount * mountComponent(this,el) * Watcher.get() #### 响应式原理 #### 虚拟dom24 #### 模版编译和组件化24 ### Vuex 24 ### 服务端渲染 25 ### NuxtJS 25 ### 搭建SSR 25 ### 静态站点生成 26 ### vuejs组件库26 ### vue3.026 ### composition Api 27 ### Vue3响应式原理 27 ### Vite 27 ### vue项目 28 ## React ### React基础 ### react简易版 1.通过.babelrc文件解析jsx代码,将代码转化为babel编译后的形式,并且将React.createElement修改为LxReact.createElement ``` { "presets": [ "@babel/preset-env", [ "@babel/preset-react", { "pragma":"LxReact.createElement" } ] ] } ``` 2.实现LxReact.createElement和render方法 ### VitualDOM和Diff算法 ### Fiber ### React核心源码 ### Redux ### Mobx6 ### Mobx5 ### Hook ### Formik ### CSS in JS ### React组件性能优化 ### ReactSSR ### Next ### Gatsby ## nodejs ### nodejs基础 #### nodejs的全局对象 **__dirname,__filename,this** ``` __dirname //脚本所在目录 __filename //文件的绝对路径 console.log(__filename); console.log(__dirname); // 默认情况下 this是空对象 console.log(this); console.log(this === global); (function () { console.log(arguments); console.log(this === global); })(); ``` **process** ``` //1. cpu内存 console.log(process.memoryUsage()); console.log(process.cpuUsage()); // 2.运行环境,node环境,cpu架构,用户环境,系统平台 // console.log(process.cwd()); // console.log(process.version); // console.log(process.versions); // console.log(process.arch); // console.log(process.env.NODE_ENV); // console.log(process.env.PATH); // console.log(process.env.HOME); //mac 使用HOME window使用USERPROFILE // console.log(process.platform); // 3.运行状态 启动参数、PID、运行时间 console.log(process.argv); console.log(process.argv0); console.log(process.pid); console.log(process.uptime()); // 4.时间监听 // process.on("exit", (code) => { // console.log("exit" + code); // }); // process.on("beforeExit", (code) => { // console.log("beforeExit" + code); // }); // console.log("代码执行完毕 "); // process.exit(); //只会走exit的监听事件 不走beforeExit // 5. 标准输出 输入 错误 // console.log = function (data) { // process.stdout.write("---" + data + "\n"); // }; const fs = require("fs"); fs.createReadStream("text.txt").pipe(process.stdout); // process.stdin.pipe(process.stdout); process.stdin.setEncoding("utf-8"); process.stdin.on("readable", () => { let chunk = process.stdin.read(); process.stdout.write("data" + chunk); }); ``` #### nodejs的核心模块 #### 通信 ### 数据库 #### mongodb #### Redis ### Express ### KOA ## 泛客户端 ### 小程序 ### unitApp ### React Native ### Flutter ### Electron ## 计算机网络 ### 计算机网络体系结构 计算机网络是一个将分散的、具有独立功能的计算机系统,通过通信设备与线路连接起来,由功能完善的软件实现资源共享和信息传递的系统。简而言之,计算机网络是一些互联的、自治的计算机系统的集合。 #### 计算机的组成 ##### 组成部分 从组成部分上看:一个完整的计算机网络主要由硬件、软件、协议三大部分组成,缺一不可。协议是计算机网络的核心。 1. 硬件 * 主机 * 通信链路(双绞线、光纤) * 交换设备(交换机、路由器) 2. 软件(主要在应用层) 3. 协议 * 语法:用来规定信息格式;数据及控制信息格式、编码及信号电平等 * 语义:控制信息每个部分的含义。发出何种控制信息,发出何种动作,发出何种响应。 * 规则:定义了何时进行通信,先讲什么,后讲什么,讲话的速度等。比如是采用同步传输还是异步传输。 ##### 工作方式 从工作方式上看:计算机网络(这里主要指Internet,即因特网)可分为边缘部分和核心部分。 ##### 功能组成 ### 物理层 ### 数据链路层 ### 网络层 ### 传输层 ### 应用层 ### 网络安全 ### 网络安全应用 ### 无线网络和移动网络 ## 掘金文章 ### JavaScript执行上下文 什么是执行上下文?我的理解,作用域。官方概念,代码被解析和执行时所在环境的抽象概念,JavaScript代码都是在执行上下文中运行 #### 执行上下文的类型 * 全局执行上下文 * 函数执行上下文 * eval执行上下文 #### 执行栈 顶端添加删除,后进先出(LIFO)。 用于储存代码执行期间创建的所有执行上下文。 js引擎首次读取脚本的时候,会创建一个全局执行上下文。每当一个函数发生调用,引擎会为该函数创建一个新的执行上下文,放在栈顶。 函数执行完后,其对应的执行上下文将会从执行栈中弹出。 #### 执行上下文是如何被创建的 两个阶段 1. 创建阶段 2. 执行阶段 ##### 创建阶段 1. 确定this的值。 2. 词法环境组件被创建。 3. 变量环境组件被创建。 ##### 词法环境 词法环境是一个包含标识符变量映射的结构。 在词法环境中,由环境记录和对外部环境的引用两部分组成。 环境记录是存储变量和函数声明的实际位置。 对外部环境的引用意味着它可以访问其外部词法环境。 词法环境有两种类型:全局环境和函数环境。 !!!!函数环境另外包括了一个arguments对象!!!! let const 的声明在词法环境中,创建的值为< uninitialized >,, var在创建的时候就会创建在变量环境中,并且为undefined,这就是变量的提升。 ### js的骚操作 #### 数组去重的方法 * 双层for循环 * indexOf去重 * set去重 #### 数组转化为对象 * for in 方法 key为index * 扩展运算符 {...arr} #### 转换为数字类型 * "100"*1 * +"100" * parseInt() Number() #### 转化为字符串类型 * toString() * value+"" #### 性能追踪 可以通过performace.now() 获取当前的时间计算时间差 #### 合并对象 * Object.assign() * 扩展运算符 #### 数组扁平化 * 递归 * reduce也是递归 * es6方法 arr.flat(x) x为深度 #### 求幂运算 * Math.pow() * 2 ** 10 = 1024 #### 浮点数转化为整数 * ~ >> << 等位运算符 * Math.floor() Math.round() Math.ceil() #### 获取数组最后一项 * 根据长度-1 * arr.slice(-1)[0] #### Object.create(null) var obj1 = {}; var obj2 = Object.create(null); 两者的区别,第一个有原型的属性 方法等 #### 拷贝数组 * slice(深) * 扩展运算符(深) * concat(深) #### Object.freeze() ## 设计模式 ### 发布订阅 ``` class EventEmitter { constructor() { this.subs = Object.create(null) } $on(eventType, handler) { if (this.subs[eventType] && this.subs[eventType].length) { this.subs[eventType].push(handler) } else { this.subs[eventType] = [handler] } } $emit(eventType, ...args) { this.subs[eventType] && this.subs[eventType]?.forEach(handler => { handler(...args) }) } $off(eventType, handler) { if (!this.subs[eventType]) return const findHandler = this.subs[eventType]?.findIndex(_handler => _handler = handler) findHandler !== null && findHandler !== undefined && this.subs[eventType].splice(findHandler, 1) } } ``` ### 观察者 ``` // 发布者 class Dep { constructor() { this.subs = [] } addSub(sub) { sub && sub.update && this.subs.push(sub) } notify() { this.subs?.forEach(sub => sub.update()) } } // 订阅者-观察者 class Watcher { update() { console.log("update") } } ```