预习课(架构)
2021 第一期 Vue3 架构课
任务 1:1.vue3 变化介绍
任务2:2.vue3架构组织
monorepo
项目结构
任务3:3.根据需要打包的信息进行打包
pnpm i typescript rollup rollup-plugin-typescript2 @rollup/plugin-node-resolve @rollup/plugin-json execa
// scripts/build.js
// 把packages下所有包都进行打包
const fs = require('fs')
const execa = require('execa') // 开启子进程 进行打包 最终使用rollup打包
// 找到packages下的所有目录
const targets = fs.readdirSync('packages').filter(f => {
return fs.statSync(`packages/${f}`).isDirectory();
})
// 打包(并行)
// 每个模块每次打包执行的方法
async function build(target) {
// rollup -c --environment TARGET:shared
await execa('rollup', ['-c', '--environment', `TARGET:${target}`],
// 子进程打包信息共享给父进程
{stdio: 'inherit'})
}
function runParallel(targets, iteratorFn) {
const res = []
for (const item of targets) {
const p = iteratorFn(item)
res.push(p)
}
return Promise.all(res)
}
runParallel(targets, build)
// rollup.config.js
import path from 'path'
// 根据环境变量的target属性, 获取对应模块中的 package.json
const packagesDir = path.resolve(__dirname, 'packages') // packages
// packageDir 打包的基准目录
const packageDir = path.resolve(packagesDir, process.env.TARGET) // packages/xxx
const resolve = (p) => path.resolve(packageDir, p)
const pkg = require(resolve('package.json'))
const name = path.basename(packageDir) // xxx
// 对打包类型做一个映射表, 根据package.json的formats来格式化需要打包的内容
const outputConfig = { // 自定义
'esm-bundler': {
file: resolve(`dist/${name}.esm-bundler.js`),
format: 'es'
},
'cjs': {
file: resolve(`dist/${name}.cjs.js`),
format: 'cjs'
},
'global': {
file: resolve(`dist/${name}.global.js`),
format: 'iife' // 立即执行函数
}
}
function createConfig(format, output) {
output.name = options.name
output.sourcemap = true // 生成sourcemap
// 生成rollup配置
}
const options = pkg.buildOptions // 在package.json中自定义的属性
// rollup需要导出配置
export default options.formats.map(format => createConfig(format, outputConfig[format]))
任务4:4.实现打包流程
npx tsc --init
// tsconfig.json
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "ESNEXT",
/* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "ESNEXT",
/* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": true,
/* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": false,
/* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true,
/* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true,
/* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true,
/* Disallow inconsistently-cased references to the same file. */
"moduleResolution": "node",
"baseUrl": ".",
"paths": {
"@vue/*": [
"packages/*/src"
]
}
}
}
// rollup.config.js
import path from 'path'
import json from "@rollup/plugin-json";
import nodeResolve from '@rollup/plugin-node-resolve'
import ts from 'rollup-plugin-typescript2'
// 根据环境变量的target属性, 获取对应模块中的 package.json
const packagesDir = path.resolve(__dirname, 'packages') // packages
// packageDir 打包的基准目录
const packageDir = path.resolve(packagesDir, process.env.TARGET) // packages/xxx
const resolve = (p) => path.resolve(packageDir, p)
const pkg = require(resolve('package.json'))
const name = path.basename(packageDir) // xxx
// 对打包类型做一个映射表, 根据package.json的formats来格式化需要打包的内容
const outputConfig = { // 自定义
'esm-bundler': {
file: resolve(`dist/${name}.esm-bundler.js`),
format: 'es'
},
'cjs': {
file: resolve(`dist/${name}.cjs.js`),
format: 'cjs'
},
'global': {
file: resolve(`dist/${name}.global.js`),
format: 'iife' // 立即执行函数
}
}
function createConfig(format, output) {
output.name = options.name
output.sourcemap = true // 生成sourcemap
// 生成rollup配置
return {
input: resolve(`src/index.ts`),
output,
plugins: [
json(),
ts({
tsconfig: path.resolve(__dirname, 'tsconfig.json')
}),
nodeResolve() // 解析第三方模块
]
}
}
const options = pkg.buildOptions // 在package.json中自定义的属性
// rollup需要导出配置
export default options.formats.map(format => createConfig(format, outputConfig[format]))
// scripts/dev.js
// 只针对具体的某个包打包
const fs = require('fs')
const execa = require('execa') // 开启子进程 进行打包 最终使用rollup打包
// 找到packages下的所有目录
const target = 'reactivity'
// 打包(并行)
// 每个模块每次打包执行的方法
async function build(target) {
// rollup -c --environment TARGET:shared
// await execa('rollup', ['-c', '--environment', `TARGET:${target}`],
await execa('rollup', ['-cw', '--environment', `TARGET:${target}`],
// 子进程打包信息共享给父进程
{stdio: 'inherit'})
}
build(target)
// scripts/build.js
// 把packages下所有包都进行打包
const fs = require('fs')
const execa = require('execa') // 开启子进程 进行打包 最终使用rollup打包
// 找到packages下的所有目录
const targets = fs.readdirSync('packages').filter(f => {
return fs.statSync(`packages/${f}`).isDirectory();
})
// 打包(并行)
// 每个模块每次打包执行的方法
async function build(target) {
// rollup -c --environment TARGET:shared
await execa('rollup', ['-c', '--environment', `TARGET:${target}`],
// 子进程打包信息共享给父进程
{stdio: 'inherit'})
}
function runParallel(targets, iteratorFn) {
const res = []
for (const item of targets) {
const p = iteratorFn(item)
res.push(p);
}
return Promise.all(res)
}
runParallel(targets, build)
任务5:5.reactiveApi实现
202103Node架构课(全)
任务1:1.高阶函数逇使用
// 1_高阶函数.js
// 高阶函数的概念:1.一个函数返回一个函数2.一个函数可以参数接受一个函数高阶函数
// 这两个条件满足任何一个均可promisel内部肯定也是回调函数(内部包含着高阶函数
// 扩展方法 会用到高阶函数
function core(...args) { // 核心代码
console.log('core', ...args)
}
// 给core函数增加一些额外的逻辑但是不能更改核心代码
Function.prototype.before = function (cb) { // 扩展原型链
// this = core (箭头函数, 谁调用, this就指向谁)
// ...args 剩余运算符 可以把多个参数转化成数组
return (...args) => { // newCore
cb()
// ... 可以把数组展开
this(...args)
}
}
let newCore = core.before(() => {
console.log('before')
})
newCore('a', 'b')
// 高阶函数可以用来扩展已有函数
// 2_函数柯里化.js
// 函数柯里化 多个参数的传入 把他转化成 n个函数 可以暂存变量
// 一般柯里化参数要求 都是一个个的传 => 偏函数
function fn(a) {
return function a(b) {
return function a(c) {
}
}
}
let a = 1, b = 2, c = 3
fn(a, b, c)
let f1 = fn(a)
let f2 = fn(b)
let f3 = fn(c)
// 判断一个变量的类型 (代码的实现 类型是基本条件)
// typeof我们一般用于判断基础类型
// instanceof xxx是谁的实例 原理
// Object.prototype.toString.call 判断具体类型返回的是一个字符串
// coustructor 深拷贝
function isType(val, typing) {
return Object.prototype.toString.call(val) == `[object ${typing}]`
}
// // 如果用户的typing字段写错了? 这种常量, 我们不希望用户自己写
// console.log(isType('null', 'String'))
// console.log(isType(null, 'String'))
// 我们的柯里化登场:让函数变得更具体一些,反柯里化:让函数范围变的更大一些
// function isString(typing) {
// return function (val) {
// return Object.prototype.toString.call(val) == `[object ${typing}]`
// }
// }
// function isString(typing) {
// return function (val) {
// return isType(val, typing)
// }
// }
//
// let myIsString = isString('String')
// console.log(myIsString('abc'));
// console.log(myIsString(123));
// 实现通用的柯里化函数:高阶函数
// 要记录每次调用时传入的参数,并且和函数的参数个数进行比较,
// 如果不满足总个数就返回新函数,如果传入的个数和参数一致执行原来的函数
function curring(fn) {
// 存储每次调用的时候传入的变量
const inner = (args = []) => { // 存储每次调用时传入的参数
return args.length >= fn.length ? fn(...args)
// args保存到新函数里了
: (...userArgs) => inner([...args, ...userArgs]) // 递归
}
return inner()
}
function isType_r(typing, val) {
return Object.prototype.toString.call(val) == `[object ${typing}]`
}
function sum(a, b, c, d) {
return a + b + c + d
}
let sum1 = curring(sum)
let sum2 = sum1(1)
let sum3 = sum2(2, 3)
let result = sum3(4)
console.log(result)
let isString = curring(isType_r)('String')
console.log(isString('123'))
console.log(isString(123))
let isNumber = curring(isType_r)('Number')
console.log(isNumber('123'))
console.log(isNumber(123))
let util = {}; // 加分号, 下面的[]才能被识别为数组
// let types = ['String', 'Number', 'Boolean', 'Null', 'Undefined']
['String', 'Number', 'Boolean', 'Null', 'Undefined'].forEach(type => {
util['is' + type] = curring(isType_r)(type)
})
console.log(util.isNumber('1223'));
任务2:2.发布订阅模式和观察者模式
// 3_并发问题.js
const fs = require('fs')
// let arr = [];
// function out() {
// if (arr.length == 2) {
// console.log(arr)
// }
// }
function after(times, callback) {
let arr = []
return (data, index) => {
// arr.push(data) // 保证顺序可以用索引
arr[index] = data
if (--times === 0) { // 多个请求并发需要靠计数器来实现
callback(arr)
}
}
}
let out = after(2, (arr) => {
console.log(arr)
})
fs.readFile('./a.txt', 'utf8', function (err, data) {
// console.log(data)
out(data, 1)
})
fs.readFile('./b.txt', 'utf8', function (err, data) {
// console.log(data)
out(data, 0)
})
// 4_发布订阅模式.js
const fs = require('fs');
// 发布订阅模式 核心就是把多个方法先暂存起来,最后一次执行 -> 解耦
// 事件中心
let events = {
_events: [],
on(fn) {
this._events.push(fn)
},
emit(data) {
this._events.forEach(fn => fn(data))
}
}
// 订阅有顺序, 可以用数组控制
events.on(() => {
console.log('每读取一次 就触发一次')
})
let arr = []
events.on((data) => {
arr.push(data)
})
events.on(() => {
if (arr.length === 2) { // 计数器
console.log('读取完毕')
console.log(arr)
}
})
fs.readFile('./a.txt', 'utf8', function (err, data) {
events.emit(data)
})
fs.readFile('./b.txt', 'utf8', function (err, data) {
events.emit(data)
})
// 观察者模式 vue2 基于发布订阅的 (发布订阅之间是没有依赖关系的)
// 对于我们的观察者模式 观察者 被观察者 vue3中没有使用class
// 5_观察者模式.js
class Subject { // 被观察者 被观察者需要将观察者收集起来
constructor(name) {
this.name = name
this.state = '开心'
this.observers = []
}
// 收集依赖 events.on
attach(o) {
this.observers.push(o)
}
// 发布新状态(依赖触发) events.emit
setState(newState) {
this.state = newState
this.observers.forEach(o => o.update(this.name, newState))
}
}
class Observer { // 观察者
constructor(name) {
this.name = name
}
// 更新状态
update(s, state) {
console.log(s, state)
}
}
// vue 数据变了(状态)视图要更新 (通知依赖的人)
let s = new Subject('小宝宝')
let o1 = new Observer('爸爸')
let o2 = new Observer('妈妈')
s.attach(o1)
s.attach(o2)
s.setState('不开心了')
任务3:3.promise基本实现
// source/1.promise.js
// 1. promise(es6)是一个类, 无需考虑兼容性
// 2.当使用promise的时候会传入一个执行器,此执行器是立即执行
// 3.当前excutor给了两个函数可以来描述当前promisef的状态。
// promisel中有三个状态 成功态 失败态 等待态(默认)
// 如果调用resolve会走到成功态,如果调用reject或者发生异常会走失败态
// 4.每个promise实例都有一个then方法
// 5.promise一旦状态变化后不能更改
// promise还是基于回调的
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
constructor(executor) {
this.status = PENDING // 默认状态
this.value = undefined // 成功原因
this.reason = undefined // 失败原因
const resolve = (value) => {
if (this.status === PENDING) {
this.value = value
this.status = FULFILLED
}
}
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason
this.status = REJECTED
}
}
try {
// 立即执行传进来的函数
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value)
}
if (this.status === REJECTED) {
onRejected(this.reason)
}
}
}
module.exports = Promise
// 6_promise.js
let Promise = require('./source/1.promise')
let promise = new Promise((resolve, reject) => {
console.log('promise')
// resolve('成功')
reject('失败')
// throw new Error('error')
})
promise.then((value) => {
console.log('success')
console.log(value)
}, (reason) => {
console.log('err')
console.log(reason)
})
console.log('ok')
任务4:4.promise链式调用的实现
// 7_promise异步.js
let Promise = require('./source/2.promise')
let promise = new Promise((resolve, reject) => {
// 1s后才指向resolve, 但是此时已经调用完executor, 结束了promise
setTimeout(() => {
resolve('ok')
}, 1000)
})
// 当用户调用then方法的时候此时promisei可能为等待态,先暂存起来,因为后续可能会调用resolve和
// reject,.等会再触发对应onFulfilled或者onRejected
promise.then((value) => { // then是异步的
console.log('success1')
console.log(value)
}, (reason) => {
console.log('err1')
console.log(reason)
})
promise.then((value) => { // then是异步的
console.log('success2')
console.log(value)
}, (reason) => {
console.log('err2')
console.log(reason)
})
// 链式调用
let p2 = new Promise((resolve, reject) => {
resolve(1)
}).then(data => {
throw new Error(data)
return 'x'
}, (err) => {
return 'xx'
})
p2.then(data => {
console.log(data)
}, err => {
console.log('error', err)
})
// 返回promise
let p3 = new Promise((resolve, reject) => {
resolve(1)
}).then(data => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
}, 1000)
})
})
p3.then(data => {
console.log(data)
}, err => {
console.log('error', err)
})
// source/2.promise.js
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
constructor(executor) {
this.status = PENDING // 默认状态
this.value = undefined // 成功原因
this.reason = undefined // 失败原因
this.onResolvedCallbacks = [] // 存放成功的回调方法
this.onRejectedCallbacks = [] // 存放失败的回调方法
const resolve = (value) => {
if (this.status === PENDING) {
this.value = value
this.status = FULFILLED
// 发布
this.onResolvedCallbacks.forEach(fn => fn())
}
}
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason
this.status = REJECTED
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
// 立即执行传进来的函数
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
// 用于链式调用
let promise = new Promise((resolve, reject) => {
// 异步executor不会改变状态, 所以我们暂存callback函数
if (this.status === PENDING) { // executor是异步调用resolve或rejecte的情况
// 订阅模式
this.onResolvedCallbacks.push(() => { // AOP
let x = onFulfilled(this.value)
// x 可能是 promise
// todo
resolve(x)
})
this.onRejectedCallbacks.push(() => {
reject(onRejected(this.reason))
})
}
// executor非异步直接出结果改变状态, 直接执行
if (this.status === FULFILLED) {
try {
resolve(onFulfilled(this.value))
} catch (e) {
reject(e)
}
}
if (this.status === REJECTED) {
try {
reject(onRejected(this.reason))
} catch (e) {
reject(e)
}
}
})
return promise
}
}
module.exports = Promise
任务5:5.promise中的x处理
webpack(202011)(架构)(全)
任务 1:1.201122.webpack 的核心概念_
npm i webpack webpack-cli --save-dev
可以–config 指定配置文件,默认找根目录下的 webpack.config.js
{
"scripts": {
"build": "webpack --config webpack.config.js"
}
}
join 是相对路径,resolve 是绝对路径
console.log(111);
let title = require("./hello.txt");
console.log(title);
const {resolve} = require("path");
module.exports = {
// mode 当前的运行模式
// development:
// none
// production:
mode: "development",
// 入口文件
entry: "./src/index.js",
output: {
// 指定输出目录(绝对路径)
// __dirname当前文件所在目录
path: resolve(__dirname, "dist"),
// 指定输出文件名
filename: "main.js",
},
};
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Document</title>
</head>
<body>
<script src="main.js"></script>
</body>
</html>
raw-loader 原生 loader
npm i raw-loader -D
const {resolve} = require("path");
module.exports = {
// ...
module: {
rules: [
// 正则匹配以txt结尾的走这个loader
// loader本质是转换器,可以把别的类型的文件转换成loader
{test: /\.txt$/, use: "raw-loader"},
],
},
};
loader
/**
* 本质上是一个函数
* 接收源文件,返回一个js模块代码
*/
function loader(source) {
return `module.exports = "${source}"`;
}
module.exports = loader;
const {resolve} = require("path");
module.exports = {
// ...
module: {
rules: [
// 正则匹配以txt结尾的走这个loader
// loader本质是转换器,可以把别的类型的文件转换成loader
// { test: /\.txt$/, use: 'raw-loader' }
{test: /\.txt$/, use: resolve(__dirname, "loaders", "raw-loader.js")},
],
},
};
plugin
npm i html-webpack-plugin -D
const {resolve} = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin({
// 指定模板
template: "./src/index.html",
}),
],
};
自动引入了 main.js
webpack 运行有很多事件,插件就在其中某个事件里作用
mode
任务 2:2.201122.开发服务器 webpack-dev-server 的配置
npm install webpack-dev-server --save-dev
#npm install webpack-dev-server -D
// webpack.config.js
const {resolve} = require('path')
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
// ...
devServer: {
// 服务器上部署哪些文件
// contentBase:resolve(__dirname,"dist")
// webpack5改为这个
// 如果output目录找不到,就从这个静态资源目录找
// 其实静态文件根目录可以有多个
static: resolve(__dirname, "static"),
compress: true, // 压缩
port: 8080,
// writeToDisk: true, // webpack4
devMiddleware: {
// 打包的文件也会输出到磁盘(不止在内存)
writeToDisk: true
},
open: true,// 自动打开浏览器
}
}
{
"scripts": {
"build": "webpack",
"start": "webpack serve"
}
}
任务3:3.201122.支持css、less和sass
const {resolve} = require('path')
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
// webpack.config.js
output: {
// 指定输出目录(绝对路径)
// __dirname当前文件所在目录
path: resolve(__dirname, 'dist'),
// 指定输出文件名
filename: 'main.js',
// 打包后的文件插入html文件时,src=publicPath+filename
// /assets/main.js
publicPath: "/assets"
},
devServer: {
// ...
devMiddleware: {
// ...
publicPath: '/', // 静态文件访问目录
},
}
}
支持css
npm i style-loader css-loader -D
# 如果报错就先安装这个
npm install node-gyp@latest
npm i less less-loader node-sass sass-loader -D
// webpack.config.js
const {resolve} = require('path')
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
// ...
module: {
rules: [
// ...
// 多个loader,从右往左执行
{test: /\.css$/, use: ['style-loader', 'css-loader']},
// less/scss -> css -> style
{test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader']},
{test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader']}
]
},
}
// less.less
@color: blue;
#less-container {
color: @color
}
// sass.scss
$color: orange;
#less-container {
color: $color
}
/* index.css */
@import "bg.css";
body {
color: indianred;
}
/*bg.css*/
body {
background-color: skyblue;
}
// index.js
import './index.css'
import './less.less'
import './sass.scss'
console.log(111);
let title = require('./hello.txt')
console.log(title);
// document.write(title.default)
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webpack</title>
</head>
<body>
<!--可以访问到开发服务器的静态资源-->
<img src="/logo.png" alt="logo.png">
123
<div id="less-container">less-container</div>
<div id="sass-container">sass-container</div>
<script src="index.js"></script>
</body>
</html>
任务4:4.201122.支持图片
npm i file-loader url-loader html-loader -D
遇到bug了
// webpack.config.js
const {resolve} = require('path')
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.(jpg|png|gif|bmp)$/, use: [
{
// loader: 'file-loader',
// 对file-loader有增强
loader: "url-loader",
options: {
// 该loader会把文件复制到output目录,并且重命名
name: '[hash:10].[ext]',
// 如果为true,则需要通过img.default取值
esModule: false,
// url-loader多的属性
// 如果文件体积小于limit,就转为base64字符串内嵌到html
// 如果大于,就和file-loader的行为一致
limit: 32 * 1024
}
}]
},
// 可以解析html中的相对路径引入
// file-loader实现的
{test: /\.html$/, use: ['html-loader']},
]
},
}
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webpack</title>
</head>
<body>
<!--可以访问到开发服务器的静态资源-->
<img src="/logo.png" alt="logo.png">
123
<div id="less-container">less-container</div>
<div id="sass-container">sass-container</div>
<div id="image-container">image-container</div>
<img src="./images/thenx.jpg">
<script src="./index.js"></script>
</body>
</html>
/*index.css*/
@import "bg.css";
body {
color: indianred;
}
#image-container {
border: 1px solid black;
width: 400px;
height: 103px;
background-image: url('./images/thenx.jpg');
}
import './index.css'
import './less.less'
import './sass.scss'
// ...
let img = require("./images/thenx.jpg")
let image = new Image()
// image.src = img.default // esModule: true
image.src = img // esModule: false
document.body.appendChild(image)
任务5:5.201122.JS的兼容性
npm i babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/polyfill @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties -D
# 都是17版本的(17.0.1)
npm i react react-dom -S
// index.js
/*import React from "react";
import ReactDOM from 'react-dom'
ReactDOM.render(<h1>hello</h1>, document.getElementById("root"))
let sum = (a, b) => a + b*/
/**
*
* @param target 装饰的目标
* @param key 装饰的key
* @param descriptior 属性描述器
*/
function readonly(target, key, descriptior) {
descriptior.writable = false
}
class Person {
// 装饰器: @xxx -> @babel/plugin-proposal-decorators 处理
// 类属性: PI -> @babel/plugin-proposal-class-properties 处理
@readonly PI = 3.14;
}
let p = new Person();
p.PI = 31.5
console.log(p)
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webpack</title>
</head>
<body>
<div id="root"></div>
<script src="./index.js"></script>
</body>
</html>
// webpack.config.js
const {resolve} = require('path')
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
// ...
module: {
rules: [
{
test: /\.jsx?$/, use: [
{
loader: "babel-loader",
options: {
// presets包含很多插件
presets: [
// 可以转换JS语法
"@babel/preset-env",
// 可以转换JSX语法
"@babel/preset-react"
],
plugins: [
['@babel/plugin-proposal-decorators', {legacy: true}],
['@babel/plugin-proposal-class-properties', {legacy: true}]
]
}
}
]
},
// ...
]
},
}
// jsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true
}
}
任务6:6.201122.path、usage和babel参数
任务7:7.201122.eslint代码风格检查
npm i eslint eslint-loader babel-eslint -D
npm i eslint-config-airbnb eslint-loader eslint eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y -D
出bug,网上说eslint-loader已经弃用(‘getFormatting’ count not find),我的解决方案
npm install eslint-webpack-plugin --save-dev
// .eslintrc.js
module.exports = {
// root: true, // 根配置文件
extends: 'airbnb',
// parser: 'babel-eslint', // 需要一个解析器来把源代码转为AST
parser: '@babel/eslint-parser',
// 指定解析器选项
parserOptions: {
// 报错 Parsing error: No Babel config file detected
"requireConfigFile": false,
sourceType: "module",
// es2015
ecmaVersion: 2015,
// ecmaVersion: 2017,
},
// 指定脚本运行环境
env: {
browser: true,
},
// 启用的规则及其各自的错误级别
rules: {
"linebreak-style": "off",
"indent": "off", // 缩进风格
// "indent": ["error", 3], // 不等于3个缩进就报错
"quotes": "off",// 引导类型
// "no-console": "error",// 禁止使用console
"no-param-reassign": "off",
}
}
// webpack.config.js
const {resolve} = require('path')
const HtmlWebpackPlugin = require("html-webpack-plugin")
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
module.exports = {
plugins: [
new HtmlWebpackPlugin({
// 指定模板
template: './src/index.html'
}),
new ESLintWebpackPlugin({
fix: true, // 启动自动修复
files: resolve(__dirname, 'src')
})
],
}
为了解决eslint解析装饰器语法的报错
{
"presets": [
"@babel/preset-env"
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
]
}
任务8:8.201122.sourcemap
任务9:9.201122.如何打包第三方类库
npm i lodash -S
npm i expose-loader -D
方法2
// webpack.config.js
const {resolve} = require('path')
const HtmlWebpackPlugin = require("html-webpack-plugin")
const webpack = require('webpack')
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
module.exports = {
plugins: [
// 无法全局用(html script)
// 可以在js里用
new webpack.ProvidePlugin({
_: 'lodash',
})
],
}
方法3
// webpack.config.js
const {resolve} = require('path')
const HtmlWebpackPlugin = require("html-webpack-plugin")
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
module.exports = {
module: {
rules: [
{
test: require.resolve('lodash'),
loader: 'expose-loader',
options: {
exposes: {
globalName: '_',
override: true,
}
}
},
]
}
}
// index.js
// import _ from 'lodash'
require("lodash");
alert(_.join(['a', 'b', 'c'], '_'));
方法4
方法5
// webpack.config.js
const {resolve} = require('path')
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
module.exports = {
plugins: [
// ...
new HtmlWebpackExternalsPlugin({
externals: [
{
module: 'lodash',
entry: 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js',
global: '_' // 全局变量名
}
]
}),
],
}
任务10:10.201122.环境变量配置
任务11:11.201125.开发和线上环境配置
任务12:12.201125.polyfill和runtime
任务13:13.201125.sourcemap
任务14:14.201127.watch、clean、copy、proxy_
任务22:22.20201129_webpack同步加载打包文件分析
预习课(架构)
任务1:1.ES6
// 1_let&const.js
// var 要求全部改用 const let
// 1 var 声明的变量 污染全局变量
var a = 1
console.log(window.a)
let b = 2
console.log(window.b)
console.log(b)
// 2 var 可能导致变量提升(还没声明就能使用)
console.log(a)
var a = 1
console.log(c)
let c = 3
// 3 var 可以重复声明
var a = 1
var a = 2
var a = 3
// let 可以解决
let a = 1
let a = 2
let a = 3
// Identifier 'a' has already been declared
// 4 var 作用域的问题 (常见作用域: 全局/函数)
{
var a = 1;
}
console.log(a)
// Uncaught ReferenceError: a is not defined
{
let a = 2
}
console.log(a)
// let 不同作用域可以重复声明
let a = 100
{
// babel编译为es5后不会报错(es5是用var声明的)
// Uncaught ReferenceError: Cannot access 'a' before initialization
console.log(a) // 暂存死区
let a = 200
}
for (var i = 0; i < 10; i++) {
// (function (i){
// setTimeout(function () {
// console.log(i)
// })
// })(i)
setTimeout(function () {
console.log(i)
})
}
for (var i = 0; i < 10; i++) {
// 限制i的作用域
(function (i) {
setTimeout(function () {
console.log(i)
})
})(i)
}
for (let i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i)
})
}
// const 常量 不能改变(地址不变即可)
let pi = '3.14'
pi = 3.15
// TypeError: Assignment to constant variable.
const PI = '3.14'
PI = 3.15
const P = {r: 3.14}
PI.r = 3.15
// 1_let&const.js
// var 要求全部改用 const let
// 1 var 声明的变量 污染全局变量
var a = 1
console.log(window.a)
let b = 2
console.log(window.b)
console.log(b)
// 2 var 可能导致变量提升(还没声明就能使用)
console.log(a)
var a = 1
console.log(c)
let c = 3
// 3 var 可以重复声明
var a = 1
var a = 2
var a = 3
// let 可以解决
let a = 1
let a = 2
let a = 3
// Identifier 'a' has already been declared
// 4 var 作用域的问题 (常见作用域: 全局/函数)
{
var a = 1;
}
console.log(a)
// Uncaught ReferenceError: a is not defined
{
let a = 2
}
console.log(a)
// let 不同作用域可以重复声明
let a = 100
{
// babel编译为es5后不会报错(es5是用var声明的)
// Uncaught ReferenceError: Cannot access 'a' before initialization
console.log(a) // 暂存死区
let a = 200
}
for (var i = 0; i < 10; i++) {
// (function (i){
// setTimeout(function () {
// console.log(i)
// })
// })(i)
setTimeout(function () {
console.log(i)
})
}
for (var i = 0; i < 10; i++) {
// 限制i的作用域
(function (i) {
setTimeout(function () {
console.log(i)
})
})(i)
}
for (let i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i)
})
}
// const 常量 不能改变(地址不变即可)
let pi = '3.14'
pi = 3.15
// TypeError: Assignment to constant variable.
const PI = '3.14'
PI = 3.15
const P = {r: 3.14}
PI.r = 3.15
// 2_spread.js
// ... 展开运算符
// 把两个数组或对象合并
let arr1 = [1, 2, 3]
let arr2 = [4, 5, 6]
console.log(arr1.concat(arr2))
// es6
let arr3 = [...arr1, ...arr2]
console.log(arr3)
let school = {name: 'zfpx'}
// let my = {age: 18 }
// let my = {age: 18, name: 'jw'} // 该name会覆盖school的name
// 嵌套对象 -> 传递的是被嵌套对象的指针 -> 修改原对象的值,all的值也会变
// 深拷贝(拷贝值,地址不同)和浅拷贝(传地址)
let my = {age: {count: 18}, name: 'jw'}
let all = {...school, ...my}
my.age.count = 100
console.log(all)
// 原来的my放进新对象,用一个新的age把原来的age也拷贝
let newMy = {...my, age: {...my.age}}
let all22 = {...school, ...newMy}
my.age.count = 200
console.log(all22)
// ... 只能拷贝一层
// 可以把对象先转为字符串,再把字符串转对象
let school1 = {name: 'zfpx'}
let my1 = {age: {count: 18}, name: 'jw'}
let all1 = JSON.parse(JSON.stringify({...school1, ...my1}))
my.age.count = 100
console.log(all1)
// 这个方法的问题: func
let school2 = {
name: 'zfpx', fn: function () {
}, aa: undefined, bb: null, arr: [1, 2, 3, [4, 5]]
}
let my2 = {age: {count: 18}, name: 'jw'}
let all2 = JSON.parse(JSON.stringify({...school2, ...my2}))
my.age.count = 100
console.log(all2)
// Object.assign = ...
// 都是浅拷贝
let obj1 = {name: 'zfjg', age: 18}
let newObj1 = Object.assign(obj1)
console.log(newObj1)
let obj2 = {name: 'zfjg', age: 18}
let newObj2 = {}
// obj,{c:18} -> newObj
Object.assign(newObj2, obj2, {c: 18})
console.log(newObj2)
// 自己实现深拷贝的方法 (递归拷贝 一层一层地拷贝)
// 类型判断 => 防止传null/undefined/非对象
// typeof instanceof Object.prototype.toString.call constructor
function deepClone(obj, hash = new WeakMap()) { // 判断是null或undefined
// null == undefined
if (obj == null) return obj
// 日期
if (obj instanceof Date) return new Date(obj)
// 正则
if (obj instanceof RegExp) return new RegExp(obj)
// 不是对象,不用拷贝
if (typeof obj !== "object") return obj
// 数组或对象
if (hash.has(obj)) return hash.get(obj) // 如果weakMap中有对象就直接返回
// construct []->[] ({})->{}
let cloneObj = new obj.constructor
// clone完就存好,再次拷贝直接返回clone完的对象
hash.set(obj, cloneObj) // 防止循环依赖
for (let key in obj) {
// 防止遍历原型链上的属性
if (obj.hasOwnProperty(key)) {
// 深拷贝
// 如果赋予的值是对象,就把对象放到weakMap
cloneObj[key] = deepClone(obj[key], hash)
}
}
return cloneObj
}
let obj = {age: {name: 123}}
// 循环引用
obj.xxx = obj
let n = deepClone(obj)
obj.age.name = 456
console.log(n)
let n1 = deepClone([1, 2, 3])
console.log(n1)
// 3_set&map.js
// set map 是两种存储结构
// set 集合 不能放重复的值(放了白放)
let s = new Set([1, 2, 3, 4, 1, 2, 3, 4])
// 基础类型 string number boolean undefined object symbol
console.log(typeof s)
console.log(s)
// 添加和删除 set没有顺序
s.add('5')
s.delete('5')
console.log(s.entries());
console.log(s.keys());
console.log(s.values());
s.forEach(item => {
console.log(item)
})
// promise symbol.iterator
// 可被迭代,所以可以展开
let arr = [...s]
console.log(arr)
// 面试: 并集 交集 差集
let s01 = [1, 2, 3, 1, 2, 6]
let s02 = [3, 4, 5, 1, 2]
function union() {
let s1 = new Set(s01)
let s2 = new Set(s02)
console.log([...s1, ...s2]);
console.log(...new Set([...s1, ...s2]));
}
union()
function intersection() {
// 返回true表示留下
return [...new Set(s01)]
.filter(function (item) {
return new Set(s02).has(item);
})
}
console.log(intersection());
function diff() {
// 返回true表示留下
return [...new Set(s01)]
.filter(function (item) {
return !new Set(s02).has(item);
})
}
console.log(diff())
// map k-v 不能重复
let m = new Map()
m.set("name", "zfjg")
m.set("name", "123")
// 这个obj的引用的空间被set所引用
let obj = {name: 1}
m.set(obj, '456')
// obj为null,空间还在
obj = null
console.log(obj)
console.log(m)
// weakMap的key必须是对象类型
let wm = new WeakMap()
// Invalid value used as weak map key
// wm.set("name", "zfjg")
// wm.set("name", "123")
// 这个obj的引用的空间被set所引用
let obj1 = {name: 1}
wm.set(obj1, '456')
// 在ide里: WeakMap { <items unknown> }
// 在浏览器里: WeakMap{{…} => '456'}
console.log(wm)
// obj为null,空间还在
obj1 = null // 可以被销毁
console.log(obj1)
console.log(wm)
```
## 任务2:2.ES6
![](8fded88e.png)![](fc42ee40.png)![](dbef9d08.png)![](5139ba8e.png)
```js
// 4_defineProperty.js
// let obj = {name: 'zfjg'}
// obj.name = 123
// Object.defineProperty es5 vue2
// 通过它定义属性,可以增加拦截器
let obj = {}
let other = 'other'
// 不可枚举
// 函数的原型 Array.prototype 也不可枚举
// defineProperty只能用在对象上,数组也不行
Object.defineProperty(obj, 'name', {
enumerable: true, // 可枚举
configurable: true, // 可配置(可以被delete)
// writable: true, // 可以被写(修改)
// value: 'hello',
// 用了get和set就不能用value和writable
get() {
console.log('-----')
return other
},
set(v) {
other = v
}
})
console.log(obj)
console.log("obj.properties:")
for (let key in obj) {
console.log(key)
}
console.log(obj.name)
obj.name = 'world'
console.log(obj.name)
delete obj.name
console.log(obj)
// 对象的setter和getter
let iobj = {
other: '123',
get name() {
return this.other
},
set name(v) {
this.other = v
}
}
console.log(iobj.name)
iobj.name = 456
console.log(iobj.name)
```
```js
// 5_mvvm.js
// vue的数据劫持 (把所有的属性都改成get和set)
// 模拟的更新方法
function update() {
console.log('更新视图')
}
let data = {
name: 'zfpx',
age: 18,
address: {
location: 'zh_cn'
}
}
// 重写数组方法
let methods = ['push', 'slice', 'pop', 'sort', 'reverse', 'unshift']
// 变异方法 push shift unshfit reverse sort splice pop
let oldProto = Array.prototype
let proto = Object.create(oldProto) // 克隆了一分
methods.forEach(item => {
proto[item] = function () {
update();
oldProto[item].apply(this, arguments);
}
})
// .forEach(method => {
// // AOP 面向切片 装饰器
// // 拿到原有方法
// // let oldMethod = Array.prototype[method]
// // 增强原有方法
// Array.prototype[method] = function () {
// // console.log(oldMethod)
// update()
// // oldMethod.call(this,...arguments)
// Array.prototype[method].call(this, ...arguments)
// }
// })
// 观察者
function observer(_obj) {// proxy reflect
if (Array.isArray(_obj)) {
// AOP
return _obj.__proto__ = proto;
// 重写 这个数组里的push shift unshfit reverse sort splice pop
}
if (typeof _obj !== 'object') return _obj;
for (let key in _obj) {
defineReactive(_obj, key, _obj[key])
}
}
function defineReactive(_obj, key, value) {
// 递归调用,将嵌套对象的key也变为响应式
observer(value)
Object.defineProperty(_obj, key, {
get() {
return value
},
set(v) {
if (v !== value) {
// 如果新的val是对象,也进行数据劫持
observer(v)
update()
value = v
}
}
})
}
observer(data)
data.name = 'jw'
// data.address.location = 'en'
data.address = {
location: '北京'
}
data.address.location = 'en'
data.a = 1
data.address = [1, 2, 3]
// data.address[2] = 100
// 调用数组方法无法触发set
// data.address.push(1000)
data.address.push(4)
data.address.push(4)
data.address.reverse()
// Vue.prototype.$set = function (obj, key, callback) {
// Object.defineProperty(obj, key, {
// get: callback
// })
// }
```
```js
// 6_arrowFn.js
// 箭头函数 没有this 没有arguments
// 没用let,可能变量提升
function a() {
}
// 用了let
let a1 = function (x) {
}
a1(1)
// 只有一个参数,可以省略
let a2 = x => {
}
a2(1)
// 方法体只有返回语句,可以省略
let a3 = (x, y) => x + y
console.log(a3(1, 2));
// 返回的是对象,省略的写法
let a4 = (x, y) => ({total: x + y})
console.log(a4(1, 2))
let a5 = function (x) {
return function (y) {
return x + y
}
}
console.log(a5(1)(2));
// 返回值是函数,省略的写法
let a6 = x =>
y => x + y
console.log(a6(1)(2))
// this的问题,看.前面的是谁 this就是谁
let v = 1
let obj = {
v: 2,
// fn:function () {
fn() { // this = obj
setTimeout(function () {
// undefined
console.log(this.v)
})
setTimeout(() => {
// 2
console.log(this.v)
})
console.log(this.v)
},
fn1: () => {
setTimeout(function () {
// undefined
console.log(this.v)
})
setTimeout(() => {
// undefined
console.log(this.v)
})
// undefined
console.log(this.v)
}
}
// obj.fn()
console.log("arrow fn")
obj.fn1()
```
## 任务3:3.ES6数组方法
![](5235e940.png)
```js
// 1.proxy.js
// Object.defineProperty 不支持数组的更新 push slice ...
// 希望数组变化就能更新视图
let arr = [1, 2, 3]
function update() {
console.log("更新视图")
}
// proxy可以监控到数组、对象的变化
let proxy = new Proxy(arr, {
/**
*
* @param target 被代理的对象
* @param key 被修改的位置
* @param value 修改的值
*/
set(target, key, value) {
console.log(key, value)
// 数组变化会改变数组内容,然后改变数组length
// 为了防止报错,判断一下是不是length
// 但是这样的话,pop修改length,不会更新视图
if (key === 'length') return true
// reverse会更新多次,因为元素的位置改变(中间的那个位置不变,更新次数=length-1(单))
update()
return Reflect.set(target, key, value)
// update()
// target[key] = value
},
get(target, key) {
// return target[key]
return Reflect.get(target, key)
}
})
proxy[0] = 100
proxy.pop()
proxy.push(123)
proxy.reverse()
console.log(proxy[0])
console.log(proxy)
```
```js
// 2.arr.js
// 数组方法 es5 forEach reduce map filter some every
// es6 find findIndex
// es7 includes
// reduce 收敛
// 求和
let r = [1, 2, 3, 4, 5].reduce((a, b) => {
console.log(a, b)
return a + b
})
console.log(r)
let r1 = [
0,
{price: 100, count: 1},
{price: 200, count: 2},
{price: 300, count: 3},
].reduce((a, b) => {
console.log(a, b)
// 中间数500,会导致无法获取price和count,所以第0项设为0
return a + b.price * b.count
})
console.log(r1)
let r2 = [
{price: 100, count: 1},
{price: 200, count: 2},
{price: 300, count: 3},
].reduce((a, b) => {
console.log(a, b)
return a + b.price * b.count
// 中间数500,会导致无法获取price和count,所以第0项设为0
}, 0) // 设置第0项
console.log(r2)
// reduce常见功能 多个数据变成一个
let keys = ['name', 'age']
let values = ['jw', 18]
let obj = keys.reduce((a, b, index, current) => {
a[b] = values[index]
return a
}, {})
console.log(obj)
let obj1 = keys.reduce((a, b, index, current) => (a[b] = values[index], a), {})
console.log(obj1)
// reduce redux compose 方法
function sum(a, b) {
return a + b
}
function toUpper(str) {
return str.toUpperCase()
}
function add(str) {
return "***" + str + "***"
}
function toLen(str) {
return str.length
}
console.log(add(toUpper(sum('zfpx', 'jw'))));
// function compose(...fns) {
// return function (...args) {
// let lastFn = fns.pop();
// return fns.reduceRight((a, b) => {
// // 从后往前执行
// return b(a)
// }, lastFn(...args))
// }
// }
function composeForward(...fns) {
return fns.reduce((a, b) => {
return (...args) => { // add(toUpper(sum(
return a(b(...args))
}
})
}
let composeForwardSimple = (...fns) => fns.reduce((a, b) => (...args) => a(b(...args)))
let compose =
(...fns) => (...args) => {
let lastFn = fns.pop()
return fns.reduceRight((a, b) => b(a), lastFn(...args))
}
// 从后往前执行
let res_c = compose(add, toUpper, sum)('zfpx', 'jw')
console.log(res_c)
let res_c_f = composeForward(add, toUpper, sum)('zfpx', 'jw')
console.log(res_c_f)
let res_c_f_s = composeForward(add, toUpper, sum)('zfpx', 'jw')
console.log(res_c_f_s)
Array.prototype.reduce = function (callback, prev) {
// this = [1,2,3]
for (let i = 0; i < this.length; i++) {
if (prev == undefined) {
prev = callback(this[i], this[i + 1], i + 1, this)
i++
} else {
prev = callback(prev, this[i], i, this)
}
}
return prev
};
console.log([1, 2, 3].reduce((a, b) => {
return a + b
}));
console.log([1, 2, 3].reduce((a, b) => {
return a + b
}, 100));
// map 映射
// 遍历每一项*2
console.log([1, 2, 3].map(item => item * 2));
// filter 过滤
// 过滤为2的元素
console.log([1, 2, 3].filter(item => item !== 2));
// some
// 有为2的元素就返回true
console.log([1, 2, 3].some(item => item === 2));
// every
// 都为1的就返回true
console.log([1, 2, 3, 4, 5].every(item => item === 1));
// find
// 找到后返回找到的项,找不到返回undefined
console.log([1, 2, 3].find(item => item === 2));
//
console.log([1, 2, 3].indexOf(1))
console.log([1, 2, 3].includes(1))
```
## 任务4:4.ES6中的类
![](926ec9e5.png)![](16c98b74.png)![](21967ff4.png)![](b61b941a.png)![](7d6a6ac8.png)
```js
// 4.es6.class.js
class Animal {
// es7支持静态属性
// static flag = 123 // es7写法
// es6只支持静态方法
static flag() {
return 123
}
constructor(name) {
this.name = name
this.eat = '吃'
// 如果返回引用类型,子类new后会变成该返回值
// return {a:1}
// 如果是普通类型,不会影响new出来的结果
return 123
}
// 原型上的方法
say() {
console.log('say')
// console.log(this)
}
}
// 类不能当函数用
let a = new Animal()
// this: Animal { name: undefined, eat: '吃' }
a.say()
// this: {}
a.__proto__.say()
// es6里,如果单独调用原型上的方法,this不存在
let say = a.say
// this: undefined
say()
console.log(Animal.flag())
// 实例+原型属性都有
class Tiger extends Animal {
// 没写constructor, 则默认将参数传给父类constructor
constructor(name) {
super(name); // Animal.call(this)
}
}
// let tr = new Tiger('tiger')
// console.log(tr) // {a:1}
let t = new Tiger('tiger')
t.say()
console.log(t.name)
// 父类的静态方法要通过类(不是实例)获取
console.log(Tiger.flag())
// 可以用babel官网转换代码
```
```js
// 3.class.js
// es6 类 es5 构造函数
function Animal(name) {
// 属性 分为两种 实例上的属性/公有属性
// 这样定义的都是实例上的属性
this.name = name
this.arr = [1, 2, 3]
this.eat = '吃'
}
// 定义在原型链上,是公有的
Animal.prototype.address = {location: '山沟沟'}
let a1 = new Animal('猴子')
let a2 = new Animal('小鸡')
console.log(a1, a2)
console.log(a1.arr === a2.arr)
console.log(a1.address === a2.address)
// 每个实例都有一个 __proto__ 指向所属类的原型
console.log(a1.__proto__ === Animal.prototype)
console.log(a1.constructor === Animal)
console.log(Animal.__proto__ === Function.prototype)
console.log(a1.__proto__.__proto__ === Object.prototype)
console.log(Object.prototype.__proto__) // null
// 类的继承
function Tiger(name) {
this.name = name
this.age = 10
// 继承父类属性
// name.log == undefined
// Animal.call(this)
// name.log == name
// Animal.call(this, ...arguments)
Animal.apply(this, arguments)
}
Tiger.prototype.say = function () {
console.log('say')
}
// 继承父类公共属性/方法
// Tiger.prototype.__proto__ = Animal.prototype
// Object.create es5
// Tiger.prototype = Object.create(Animal.prototype)
// Tiger.prototype = Object.create(Animal.prototype, {constructor: {value: Tiger}})
// function create(parentPrototype) {
// let Fn = function () {
// }
// Fn.prototype = parentPrototype
// let fn = new Fn() // 只有父类的prototype
// // 之前fn创建会是Animal,这里修改之后创建的是tiger
// fn.constructor = Tiger
// return fn
// }
// Tiger.prototype = create(Animal.prototype)
// 等价于 // Tiger.prototype.__proto__ = Animal.prototype
Object.setPrototypeOf(Tiger.prototype, Animal.prototype) // es7
// Tiger.prototype = new Animal() // 不能用 不能给父类传递参数
// 继承父类实例上的属性
let tiger = new Tiger('tiger');
console.log(tiger.constructor)
console.log(tiger.eat)
console.log(tiger.arr)
console.log(tiger.name)
console.log(tiger.address)
// 使用: call+Object.create或call+setPrototypeOf
任务5:5.ES6类装饰器
npm i @babel/plugin-transform-class-properties @babel/cli @babel/core @babel/preset-env @babel/plugin-proposal-decorators --save-dev
// 1.class.js
// es7 语法,node并不支持 webpack+babel -> es5
class Animal {
static flag = '111'
constructor() {
this.name = 'xxx'
}
// 实例上的属性
age = 18
say() {
console.log('say')
}
}
// 2.class-decorator.js
// 用了装饰器 mobx nest vue
// 装饰器可以修饰 类 类的属性 类的原型上的方法
// 修饰的时候 就是把这个 类 属性... 传递给修饰的函数
@flag('哺乳类2015') class Animal {
@readonly
PI = 3.14
// 实例上的属性
age = 18
@before say(a) {
console.log('say', a)
}
}
// 类的静态属性
// function flag(constructor) {
// constructor.type = '哺乳类'
// }
function flag(value) {
return function (constructor) {
constructor.type = value
}
}
console.log(Animal.type)
// 类的属性(实例上的)
function readonly(target, property, descriptor) {
descriptor.writable = false
// setTimeout(() => {
// console.log(target == Animal.prototype)
// })
}
let animal = new Animal()
// animal.PI = 3.15
function before(target, property, descriptor) {
// 加在函数上,value是该函数
let oldSay = descriptor.value
// aop
descriptor.value = function () {
console.log('before')
// target是Animal原型
oldSay.call(target, ...arguments)
}
}
animal.say(animal.PI)
.babelrc
{
"presets": [
[
"@babel/preset-env",
{
"loose": true
}
]
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
[
"@babel/plugin-transform-class-properties",
{
"loose": true
}
]
]
}
package.json
{
"scripts": {
"babel1": "npx babel ./1.class.js -o es5-class.js && node es5-class.js",
"babel2": "npx babel ./2.class-decorator.js -o es5-class-decorator.js && node es5-class-decorator.js"
}
}
任务6:1.before方法
(plus) 专题课
编译原理专题课(架构)
任务1:1.编译器工作流
// ast-my/doc/1.tokenizer.js
let esprima = require('esprima')
let estraverse = require('estraverse-fb')
let sourceCode = `<h1 id="title"><span>hello</span>world</h1>`
let ast = esprima.parseModule(sourceCode, {
// 是否为jsx
jsx: true,
// 是否打印token
tokens: true
})
console.log(ast)
let ident = 0
function padding() {
return ' '.repeat(ident)
}
// 遍历
estraverse.traverse(ast, {
enter(node) {
console.log(padding() + node.type + '进入')
ident += 2
},
leave(node) {
ident -= 2
console.log(padding() + node.type + '离开')
}
})
任务2:2.有限状态机
// ast-my/doc/2.lexer.js
/**
* 分词
* 状态机
*/
let NUMBERS = /[0-9]/
const Numeric = 'Numeric'
const Punctuator = 'Punctuator'
let tokens = []
/**
* 开始状态,接收一个字符,返回下一个状态函数
* @param char
*/
let currentToken
// 当前token收集完毕,存入tokens
function emit(token) {
// 还原token
currentToken = {type: '', value: ''}
tokens.push(token)
}
function start(char) {// 1
// 如果是数字
if (NUMBERS.test(char)) {
currentToken = {type: Numeric, value: ''}
}
// 进入新状态/捕获数字
return number(char)
}
function number(char) {
if (NUMBERS.test(char)) {
currentToken.value += char
// return -> state -> number(char)
// 继续执行number状态,直到下一个char不是数字
// 然后进入state重新判断状态
return number
} else if (char === '+' || char === '-') {
// 当前number token收集完毕
emit(currentToken)
// 这里的处理我感觉不是很好
emit({type: Punctuator, value: char})
currentToken = {type: Numeric, value: ''}
return number
}
}
function tokenizer(input) {
// start状态
let state = start
for (let char of input) {
state = state(char)
}
// 收集剩余的token
if (currentToken.value.length > 0) {
emit(currentToken)
}
}
tokenizer("10+20+30-10")
console.log(tokens)
任务3:3.词法分析
// tokenizer.js
const {
LeftParentheses,
JSXIdentifier,
AttributeKey,
AttributeVal, RightParentheses, JSXText, BackSlash, AttributeExpressionValue
} = require('./tokenTypes')
const LETTERS = /[a-z0-9]/
// const curToken = {type: '', value: ''}
let curToken = {type: '', value: ''}
let tokens = []
// 当前token收集完毕,存入tokens
function emit(token) {
// 还原token
// curToken.type = curToken.value = ""
curToken = {type: '', value: ''}
tokens.push(token)
}
// 开始解析 <html
function start(char) {
if (char === '<') {
emit({type: LeftParentheses, value: '<'})
return foundLeftParentheses // 找到了<
}
throw new Error('第一个字符必须是<')
}
// 结束
function eof() {
if (curToken.value.length > 0) emit(curToken)
}
// 找到<状态
function foundLeftParentheses(char) { // char h1
// char是小写字母0-9
if (LETTERS.test(char)) {
curToken.type = JSXIdentifier
curToken.value += char
return jsxIdentifier // 继续收集标识符
} else if (char === '/') {
// 双标签结束 -> 单标签又如何做
emit({type: BackSlash, value: '/'})
return foundLeftParentheses
}
throw new Error('该字符不是<')
}
// 收集标识符
function jsxIdentifier(char) {
if (LETTERS.test(char)) {
curToken.value += char
return jsxIdentifier
} else if (char === ' ') {
// 遇到空格 -> 标识符收集结束
emit(curToken)
// 标签名称解析完, 接着解析标签内属性 <h1 id=xxx
return attribute
} else if (char === '>') {
// todo: malred
emit(curToken)
emit({type: RightParentheses, value: '>'})
return foundRightParentheses
}
return eof
}
// 标签内属性
function attribute(char) {
if (LETTERS.test(char)) {
curToken.type = AttributeKey
curToken.value += char
return attributeKey
} else if (char === '>') {
// todo malred
emit({type: RightParentheses, value: ">"})
return foundRightParentheses
}
throw new Error('key标识符语法错误')
}
// 收集key token
function attributeKey(char) {
if (LETTERS.test(char)) {
curToken.value += char
return attributeKey
} else if (char === '=') {
emit(curToken)
return attributeValue
}
throw new Error('value标识符语法错误')
}
// 解析标签内属性的值
function attributeValue(char) {
if (char === '"') {
curToken.type = AttributeKey
// 要不要加"val"
// curToken.value += char
curToken.value += char
return attributeStringValue
} else if (char === '{') {
curToken.type = AttributeExpressionValue
curToken.value += char
return attributeExpressionValue
}
throw new Error('value标识符语法错误')
}
// name={xxx}
function attributeExpressionValue(char) {
if (LETTERS.test(char)) {
curToken.value += char
return attributeExpressionValue
} else if (char === '}') {
// 属性解析结束了
curToken.value += char
emit(curToken)
return tryLeaveAttribute
}
}
// 读取标签内字符串属性值
function attributeStringValue(char) {
// todo 字符串的值应该不止0-9a-z
if (LETTERS.test(char)) {
curToken.value += char
return attributeStringValue
} else if (char === '"') {
// 属性解析结束了
curToken.value += char
emit(curToken)
return tryLeaveAttribute
}
}
// 标签内属性解析完后试图离开属性解析(属性可能有多个)
function tryLeaveAttribute(char) {
// 隔了一个空格可能是新属性
if (char === ' ') {
return attribute
} else if (char === '>') {
// 标签结束
emit({type: RightParentheses, value: '>'})
return foundRightParentheses
}
}
// 标签结束标记找到
function foundRightParentheses(char) {
if (char === '<') {
// 新标签解析
emit({type: LeftParentheses, value: '<'})
return foundLeftParentheses
} else {
// 可能是文本
curToken.type = JSXText
curToken.value += char
return jsxText
}
}
// 解析标签内文本
function jsxText(char) {
// 新标签
if (char === '<') {
emit(curToken)
emit({type: LeftParentheses, value: '<'})
return foundLeftParentheses
} else {
curToken.value += char
return jsxText
}
}
function tokenizer(input) {
// let tokens = []
let state = start // 开始状态
for (let char of input) {// 遍历字符
if (state === eof) {
break
}
state = state(char)
}
return tokens
}
module.exports = {
tokenizer
}
let source_code = `<h1 id="title" name="malred" ><span>hello</span>world</h1>`
console.log(tokenizer(source_code))
let source_code1 = `<h1 id="title" name="malred" token={xxx}><span>hello</span>world</h1>`
console.log(tokenizer(source_code1))
// 无法解析 单标签 -> src里的/ -> 报错
// let source_code2 = `<img src="./index.css" />`
let source_code2 = `<!--<img src="index.css" />-->`
// console.log(tokenizer(source_code2))
// 无法解析 无 / 的标签
// let source_code1 = `<!DOCTYPE html>
// <html lang="en">
// <head>
// <meta charset="UTF-8">
// <title>Title</title>
// </head>
// <body>
//
// </body>
// </html>`
// console.log(tokenizer(source_code1))
let source_code4 = `<head>
<title>Title</title>
</head>
<body>
</body>
</html>`
console.log(tokenizer(source_code4))
/*
[
{type: 'Punctuator', value: '<'},
{type: 'JSXIdentifier', value: 'h1'},
{type: 'JSXIdentifier', value: 'id'},
{type: 'Punctuator', value: '='},
{type: 'String', value: '"title"'},
{type: 'Punctuator', value: '>'},
{type: 'Punctuator', value: '<'},
{type: 'JSXIdentifier', value: 'span'},
{type: 'Punctuator', value: '>'},
{type: 'JSXText', value: 'hello'},
{type: 'Punctuator', value: '<'},
{type: 'Punctuator', value: '/'},
{type: 'JSXIdentifier', value: 'span'},
{type: 'Punctuator', value: '>'},
{type: 'JSXText', value: 'world'},
{type: 'Punctuator', value: '<'},
{type: 'Punctuator', value: '/'},
{type: 'JSXIdentifier', value: 'h1'},
{type: 'Punctuator', value: '>'}
]
*/
exports.LeftParentheses = 'LeftParentheses' // <
exports.RightParentheses = 'RightParentheses' // >
exports.JSXIdentifier = 'JSXIdentifier' // 标识符
exports.JSXText = 'JSXText' // 文本
exports.BackSlash = 'BackSlash' // /
exports.AttributeKey = 'AttributeKey' // 标签属性key
exports.AttributeVal = 'AttributeVal' // 标签属性value
exports.AttributeExpressionValue = 'AttributeExpressionValue' // 标签属性value
// const LeftParentheses = 'LeftParentheses' // <
// const JSXIdentifier = 'JSXIdentifier' // 标识符
// const AttributeKey = 'AttributeKey' // 标签属性key
// const AttributeVal = 'AttributeVal' // 标签属性value