直接上题
1、new 一个箭头函数
- 会报错,提示: function is not a constructor;
- babel 编译时,会把 this 转成 (void 0);(面试补充说这句,表明自己了解babel这一块,对安全的undefined也了解)
2、哪些不能用 箭头函数?
- arguments
- yield
- 构造函数的原型方法上
const Person = function (age, name) {
this.age = age;
this.name = name;
//arguments是个伪数组
const obj = { '0': 18, '1': 'luyi', '2': 'teacher', '3': 'esnext' };
console.log(arguments.callee);//指向函数本身
obj.length = 4;
console.log(Array.prototype.slice.call(obj, 2));
}
const p = new Person(18, "luyi", "teacher", "esnext")
console.log(Array.prototype.slice.call(Person, 2));
// arguments / callee / caller
//callee
//arguments 它包含着所有传入函数中的参数。这也让我们明白了 arguments 的主要用途
//就是保存函数参数。`callee`是`arguments`对象的一个属性,可以获取函数自身。
//该属性是一个指针,指向拥有这个 arguments 对象的函数。
//caller
//`caller`返回函数被谁调用了。
const fibonacci = function (num) {
if (num <= 2) return 1;
return arguments.callee.caller(num - 1) + arguments.callee.caller(num - 2)
}
console.log(fibonacci(4))
const fibonacci = (num) => {//改成箭头函数会爆炸
if (num <= 2) return 1;
return arguments.callee.caller(num - 1) + arguments.callee.caller(num - 2)
}
3、复杂的模板字符串语法
const consoleList = function (student, teacher) {
console.log(`hello ${student}, I am ${teacher}, nice 2 meet U`)
// console.log("hello " + student + ", I am " + teacher + ", nice 2 meet U")
}
const consoleString = function (stringTemplate, ...restVal) {
console.log(
stringTemplate.reduce(
(total, item, index) => total + item +(restVal[index] || ''),
'')
)
}
const stu = "my students";
const tea = "luyi";
consoleString(['hello', ', I am ', ', nice 2 meet U'], stu, tea);
//结果为:hello my students, I am luyi, nice 2 meet U
// 复杂的模板字符串语法
consoleString`hello ${stu}, I am ${tea}, nice 2 meet U`
const consoleList = function (student, teacher) {
//换行也没问题,打印出来也是换行之后的样子,不会报错
console.log(`hello ${student},
I am ${teacher},
nice 2 meet U`)
// console.log("hello " + student + ", I am " + teacher + ", nice 2 meet U")
}
consoleList('my students', 'luyi')
4、数组和对象
数组和对象的细节
// 数组的细节
// 需要使用 Array.from 或者 .fill(0)
const funcGenerator = (num) => Array.from(new Array(num)).map(item => params => console.log(params));//打印12345678910
const funcGenerator = (num) => new Array(num).fill(0).map(item => params => console.log(params));//打印12345678910
// funcGenerator(10).map((func, index) => func(index));
// 对象的细节
console.log(NaN === NaN);//false
console.log(Object.is(NaN, NaN));//true
// 原因:
// ES next 采用了 SameValueZero() 的比较。是一个引擎内置的比较方式。
console.log([NaN].indexOf(NaN)) //-1
console.log([NaN].includes(NaN)) //true ,因为includes也采用了 SameValueZero
// JS runtime : browser / node
// 想跑js文件,不仅仅用浏览器,还可以命令行node xxx.js,直接看打印结果
5、Object.assign
// Object.assign
// 深拷贝还是浅拷贝?
let dist = { foo: "foo" };
let bar = { bar: { bar: "bar" } };
let baz = { baz: "baz" };
const res = Object.assign(dist, bar, baz);
bar.bar.bar = "newBar";
baz.baz = "newBaz"
// 第一层是深拷贝,第二层是浅拷贝;
console.log(res); // { foo: 'foo', bar: { bar: 'newBar' }, baz: 'baz' };
// res -- dist;
console.log(res === dist);//true,真神奇
6、get / set
class Person {
constructor() {
}
_age = "";
get age() {
console.log(`actually I am ${this._age} years old~`)
return "17"
}
set age(val) {
console.log(" It is useless to set my age, I am 17!");
this._age = val;
}
}
// const luyi = new Person();
// luyi.age = "35";
// console.log("luyi is", luyi.age);
// java -- mumber private
// Proxy 天生的代理模式
// Vue2 Vue3
7、Proxy
Proxy 也就是代理,可以帮助我们完成很多事情,例如对数据的处理,对构造函数的处理,对数据的验证,说白了,就是在我们访问对象前添加了一层拦截,可以过滤很多操作,而这些过滤,由你来定义。
const luyi = {
age: 35
}
const luyiProxy = new Proxy(luyi, {
get: function (target, propKey, receiver) {
console.log("GET:", target, propKey)
return Reflect.get(target, propKey, receiver);
},
set: function (target, propKey, value, receiver) {
console.log("SET:", target, propKey, value)
return Reflect.set(target, propKey, value, receiver);
}
})
console.log(luyiProxy.age = 35)
8、如何实现断言函数(背下来)
// 如何去实现一个断言函数?
const assert = new Proxy({}, {
set(target, warning, value) {
if (!value) {
console.error(warning);//The teacher is Luyi!!!
}
}
})
const teacher = "luyi";
// 如果断言的内容是假的,我就打印
assert['The teacher is Luyi!!!'] = (teacher === "yunyin");
9、receiver
const luyi = {
age: 35
}
const luyiProxy = new Proxy(luyi, {
get: function (target, propKey, receiver) {
return receiver; // luyi本身
},
set: function (target, propKey, value, receiver) {
console.log("SET:", target, propKey, value)
return Reflect.set(target, propKey, value, receiver);
}
})
// receiver 指向原始的读操作所在的那个对象, 一般情况下,就是 Proxy 的实例。
console.log(luyiProxy.age)//{age: 35}即luyi本身
console.log(luyiProxy.age === luyiProxy) // true
10、 Reflect
方法允许精确添加或修改对象上的属性。
Reflect.defineProperty 静态方法 Reflect .defineProperty() 基本等同于 Object.defineProperty() 方法,唯一不同是返回 Boolean 值。
- 将 Object 上一些明显属于语言内部的方法,放到 Reflect 对象上,现在 Object 和 Reflect 一同部署;
- 修改某些 Object 方法的返回结果,让其更合理;
const teacher = {
age: 18, name: "luyi"
}
Reflect.defineProperty(teacher, 'lessions', {
writable: false,
enumerable: false,
configurable: false,
value: 'vue'
})
const res = Reflect.defineProperty(teacher, 'lessions', {
writable: true,
enumerable: true,
configurable: true,
value: ['es6', 'esnext']
})
console.log(res);
// writable: false 时,打印为false,说明设置不成功,因为不可被修改
// writable: true 时,打印为true,说明设置成功,因为可被修改
// Object.defineProperty直接报错: Cannot redefine property: lessions
// Reflect.defineProperty 给 true or false
11、Set、Map、WeakSet和WeakMap的区别
具体看之前的笔记
- Weak 表示作为唯一的部分,必须是一个对象;
- Weak 是一个弱引用,不用考虑 GC;
const foos = new WeakSet();
class Foo {
constructor() {
foos.add(this);
}
method() {
if (!foos.has(this)) {
throw new TypeError(" Foo.prototype.method 只能在实例上调用");
} else {
console.log("using methods")
}
}
}
let f = new Foo();
let b = {};
Foo.prototype.method.call(b)//报错
//这个例子主要为了说明,weakSet弱引用,不用考虑回收问题
12、迭代器,Iterator
- 迭代器是一个接口,为各种不同的数据提供统一的访问机制。任何数据结构只要部署了 Iterator 接口,就可以完成遍历操作:
- 本质:指针。
- 该 接口主要供
for...of消费。
let m = new Map();
m.set('a', 'foo');
m.set('b', 'bar');
m.set('c', 'baz');
let k = m.keys();
console.log(k.next());//{value: "a", done: false}
console.log(k.next());//{value: "b", done: false}
console.log(k.next());//{value: "c", done: false}
let arr = [1, 2, 3, 4, 5];
let k = arr[Symbol.iterator]();//这个就是上面执行的本质,返回的是一个迭代器对象
console.log(k.next());//{value: "1", done: false}
console.log(k.next());//{value: "2", done: false}
console.log(k.next());//{value: "3", done: false}
console.log(k.next());//{value: "4", done: false}
console.log(k.next());//{value: "5", done: false}
console.log(k.next());//{value: undefined, done: true}
// generator生成器函数
原生具备 Iterator 的数据结构有:AASSMNT
- Array Map Set String TypedArray arguments NodeList
用到generator生成器函数的地方:LRU缓存
红绿灯问题:可用callback,generator,promise,async-await
13、Object.entries
const obj = { a: 11, b: 22, c: 33 }
console.log(Object.entries(obj)); // [ [ 'a', 11 ], [ 'b', 22 ], [ 'c', 33 ] ]
console.log(Object.keys(obj)); // [ 'a', 'b', 'c' ]
console.log(Object.values(obj)); // [ 11, 22, 33 ]
// 非 generator 的方法
function entries(obj) {
let arr = [];
for (let key of Object.keys(obj)) {
arr.push([key, obj[key]])
}
return arr;
}
// generator 的方法
function* entires(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
const k = entires(obj);
console.log(k.next());//{value: ["a": 11], done: false}
console.log(k.next());//{value: ["b": 22], done: false}
console.log(k.next());//{value: ["c": 33], done: false}
console.log(k.next());//{value: undefined, done: true}
for (let item of k) {
console.log(item);
//依次打印出:
//["a", 11]
//["b", 22]
//["c", 33]
//这也是yield的返回结果
}
14、 promise.allSettled
Promise.allSettled 与 Promise.all ,都应用于批量处理异步任务的一个场景
Promise.allSettled 接受promise数组,返回promise对象,且其结果状态永远成功
//手写promise.allSettled
<script>
function allSettled(array) {
return new Promise((resolve, reject) => {
if (!(array instanceof Array)) return reject(new Error(" not Array!"))
const res = [];
let count = 0;
array.forEach((func, index) => {
Promise.resolve(func).then(value => {
res[index] = {
status: 'fulfilled', value
}
}, (reason) => {
res[index] = {
status: 'rejected', reason
}
})
.finally(() => {
++count === array.length && resolve(res);
})
})
})
}
推荐书籍
- 《你所不知道的 JS》
答疑
老师能归纳数组的扩展里的api 在使用上的一下特点吗
- map =>
- reduce
- filter
手写实现new问得多吗
new 关键字干了什么
ES6的面试重点和难点就是今天讲的这些吗?
- Promise
红杉资本怎么样啊
请教老师一下面试问题,建议是准备差不多了开始面 还是 直接面试,边面试边复盘
- 边面试边复盘
老师仔细说一下 definPropropt 和 Proxy 深层次的区别吗 碰到写框架的 老问这个
- Vue2: 数组、新增、内存消耗大、Proxy lazy 来代理。
老师老师,校招的话大厂比较看重什么,算法吗?
- 算法,聪明
老师刚才断言那个 后面 = yunyin那个可以再说一下嘛
promise class这些API,是c++实现,还是用现有旧语法模拟实现提供出来的
快速提点
一堆后端服务器框架
express
Express 是一个保持最小规模的灵活的 Node.js Web 应用程序开发框架,为 Web 和移
动应用程序提供一组强大的功能;
使用回调函数;
内置了很多中间件。
koa
Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用
和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函
数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件,
而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序;
使用async / await ;
中间件需要第三方引用。
restana
待补录
nest (next.js, nuxt.js)
待补录
【洋葱模型】与【中间件(middleware / compose)】
中间件
在 Koa 中,中间件就是普通的函数,该函数接收两个参数:context 和 next。其中 context 表示上下文对象,而 next 表示一个调用后返回 Promise 对象的函数对象。
问:中间件一般用来做啥业务?
典型的中间件有:
koa-cors 提前做跨域处理
koa-bodyparser 提前做权限认证数据解析
koa-helmet 提前准备安全策略
spring 层层的control services model这种典型的MVC框架流程,少不了中间件
洋葱模型
洋葱模型,也即是中间件的能力之强大众所周知,现在在 Web 社区发挥极大作用的 Redux、Express、Koa,开发者利用其中的洋葱模型,构建无数强大又有趣的 Web 应用和 Node 应用。更不用提基于这三个衍生出来的 Dva、Egg 等。
1. redux 的中间件编排方式由 reduce()来实现;
2. koa 的中间件编排方式由 dispatch 递归函数来实现;
3. axios // [ ] req -> unshift(); res -> push()
用express实现洋葱模型
为了说明演示【洋葱模型】
利用node.js,搭一个express后端服务器,
安装:npm install express --save 或者 yarn add express
创建:npm init 或者 yarn init
老师没有用命令行创建,而是手动新建src/index.js
跑起来:nodemon ./src/index.js
备注:
介绍一个辅助工具nodemon:
nodemon用来监视node.js应用程序中的任何更改并自动重启服务,非常适合用在开发环境
中。nodemon将监视启动目录中的文件,如果有任何文件更改,nodemon将自动重新启动
node应用程序。
//src/index.js
const express = require("express");
const app = express();
app.use((req, res, next) => {
console.log("querying strat level 1");
next();
console.log("querying end level 1");
});
app.use((req, res, next) => {
console.log("querying strat level 2");
next();
console.log("querying end level 2");
});
app.use((req, res, next) => {
console.log("querying strat level 3");
next();
console.log("querying end level 3");
});
app.get('/', (req, res) => {
res.send("hello world!!!")
});
app.listen(3001, () => {
console.log(" Express server is running in 3001")
})
//不用重新命令行编译,自动更新,并监听服务
//以上整个打印结果为:
//querying strat level 1
//querying strat level 2
//querying strat level 3
//querying end level 3
//querying end level 2
//querying end level 1
//这就是中间件
用koa实现洋葱模型
koa 基本用法
前端启动
yarn install
yarn dev //看package.json的配置 npm则用npm run dev
后端启动
rollup -c -w (等待30s)
nodemon ./dist/bundle.js
// -c: 找根目录的 `rollup.config.js` 作为我的构建的配置;
// -w: watch 监听文件的变化,实时构建;
// 或者,Node.js要求ES6模块化采用.mjs后缀的文件名,这样不用打包,
// 直接跑起来:nodemon ./src/index.js 也能支持了import语法了
前端启动 后端启动 有什么区别???
自己心里清楚
后端 - node 框架
待补录
基本写法
const Koa = require("koa");
const app = new Koa();
const main = ctx => {
ctx.body = "hello Koa";
}
app.use(main);
app.listen(3002, () => {
console.log('Koa server is running in 3002')
})
koa-router
// index.js
import Koa from 'koa';
import Router from 'koa-router';
//注意:这里用了ES6的语法,在node里直接跑nodemon ./src/index.js已经不行了,需要
//借助rollup,在控制台输入命令:rollup -c -w,打包之后,应该跑的是打包好的文件,
//即:nodemon ./dist/bundle.js
// 或者,Node.js要求ES6模块化采用.mjs后缀的文件名,这样不用打包也能支持了
// 直接跑起来:nodemon ./src/index.js 也能支持了import语法了
// const Koa = require("koa");
// const Router = require('koa-router');
import movie from './movie';
import user from './user';
const app = new Koa();
const router = new Router();
// router.get('/api', (ctx, next) => {
// ctx.type = "application/json";
// ctx.body = { data: "hello Api" };
// })
[movie, user].forEach(route => {
app.use(route.routes());
app.use(route.allowedMethods());
})
app.listen(3002, () => {
console.log('Koa server is running in 3002')
})
// movie.js
import Router from 'koa-router';
// koa-cors
// koa-bodyparser
// koa-helmet
const router = new Router();
router.get('/movie', async (ctx, next) => {
ctx.body = "Movie"
})
export default router;
// user.js类似
import Router from 'koa-router';
const router = new Router();
router.get('/user', async (ctx, next) => {
ctx.body = "User"
})
export default router;
//rollup.config.js
import babel from 'rollup-plugin-babel'
export default {
input: './src/index.js',
output: {
file: './dist/bundle.js',
format: "umd",
},
treeshake: false,
plugins: [
babel({
runtimeHelpers: true,
extensions: [".js", ".ts"],
exclude: "node_modules/**",
externalHelpers: true
})
]
}
{
"name": "back_end",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "nodemon ./dist/bundle.js"
"dev": "rollup -w -c"//改成这样,可直接跑npm run dev、或者直接rollup
//而不用跑nodemon ./dist/bundle.js
},
"author": "",
"license": "ISC",
"dependencies": {
"@babel/core": "^7.17.9",
"@babel/plugin-proposal-class-properties": "^7.16.7",
"@babel/plugin-proposal-decorators": "^7.17.9",
"@babel/preset-env": "^7.16.11",
"babelrc-rollup": "^3.0.0",
"core-js": "^3.22.0",
"koa": "^2.13.4",
"koa-bodyparser": "^4.3.0",
"koa-router": "^10.1.1",
"rollup": "^2.70.2",
"rollup-plugin-babel": "^4.4.0"
}
}
写一个装饰器
实现类似于Java里注解的形式,优化上面的的功能逻辑,理清中间件、装饰器、闭包、发布订阅之间的关系。
大概思路:url收集,url参数拼接,url定位,函数执行。
粗暴理解,中间件就是个拦截器,装饰器就是注入
先写后端服务器代码
//别忘了在这文件的plugins里配置好才可识别装饰器
// .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"modules": false,
"loose": true,
"targets": "node 16",
"useBuiltIns": "usage",
"corejs": { "version": "3.22", "proposals": true }
}
]
],
"plugins": [
//添加两个插件才可以使用装饰器
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": true }]
]
}
//index.js
import Koa from 'koa';
import Router from 'koa-router';
import bodyParser from 'koa-bodyparser';
import { controllersArray } from './utils/decorator';
import R from './controller/index'
const router = new Router();
const app = new Koa();
//使用中间件
// cors
app.use(async (ctx, next) => {
ctx.set('Access-Control-Allow-Origin', '*');
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Accept');
ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
ctx.set('Content-Type', 'application/json;charset=utf-8');
if (ctx.request.method.toLowerCase() == 'options') {
ctx.state = 200;
} else {
await next();
}
})
//使用中间件
app.use(bodyParser());
controllersArray.forEach(item => {
let { url, constructor, method, handler } = item;
const { prefix } = constructor;
if (prefix) url = `${prefix}${url}`;
router[method](url, handler)
})
app.use(router.routes());
app.listen(3003, () => {
console.log("server running 3003")
})
// ./controller/index
import Movie from './movieController';
import User from './userController';
export default [
Movie, User
]
// ./movieController
import { RequestMapping, Controller, RequestMethod } from "../utils/decorator";
import data from '../../mock/data';
@Controller('/movie')
export default class MovieController {
@RequestMapping(RequestMethod.GET, '/all')
async getAllMovies(ctx) {
// 1 -> 0,9 // 2 -> 10, 19
const [key, page] = ctx.querystring.split('=');
ctx.body = {
data: data._embedded.episodes.slice((page - 1) * 10, page * 10 - 1),
count: data._embedded.episodes.length
}
}
@RequestMapping(RequestMethod.GET)
async id(ctx) {
ctx.body = 'getAllMovies'
}
}
// /movie/all
//./userController
import { RequestMapping, Controller, RequestMethod } from "../utils/decorator";
@Controller('/user')
export default class MovieController {
@RequestMapping(RequestMethod.GET, '/all')
async getUser(ctx) {
ctx.body = 'user'
}
}
// /user/getAllbooks
// ../utils/decorator
export const RequestMethod = {
"GET": 'get',
"POST": 'post',
"PUT": 'put',
"DELETE": 'delete',
"OPTION": 'option',
"PATCH": 'patch'
}
export const controllersArray = [];
// 装饰器对类的行为的改变,是代码编译时发生的,而不是运行时;
// 所以,在之前就干了这么一件事
// movieController --> movieController.prefix = "/movie"
export function Controller(prefix = "") {
return function (target) {
target.prefix = prefix;
}
}
export function RequestMapping(method = "", url = "") {
return function (target, name, descriptor) {
let path = '';
if (!url) {
path = '/' + name;
} else {
path = url;
}
// 创建 router 需要的数据
const item = {
url: path,
method: method,
handler: target[name],
constructor: target.constructor,
}
controllersArray.push(item)
}
}
// ../../mock/data
省略
然后写前端代码
跑起来,用 yarn dev
npm 则用 npm run dev
// package.json
{
"name": "svelte-app",
"version": "1.0.0",
"private": true,
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w",
"start": "sirv public --no-clear"
},
"devDependencies": {
"@fullhuman/postcss-purgecss": "^4.1.3",
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-node-resolve": "^11.0.0",
"autoprefixer": "^10.4.2",
"postcss": "^8.4.5",
"postcss-load-config": "^3.1.1",
"rollup": "^2.3.4",
"rollup-plugin-css-only": "^3.1.0",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-svelte": "^7.0.0",
"rollup-plugin-terser": "^7.0.0",
"svelte": "^3.0.0",
"svelte-preprocess": "^4.10.6",
"tailwindcss": "^3.0.12"
},
"dependencies": {
"rollup-plugin-postcss": "^4.0.2",
"sirv-cli": "^2.0.0",
"smelte": "^1.1.8",
"svelte-spa-router": "^3.2.0",
"tailwindcss": "^3.0.23"
}
}
// main.js
import './app.css';
import App from './App.svelte';
const app = new App({
target: document.body,
props: {
name: 'world',
},
});
export default app;
// App.svelte
<script>
import "./app.css";
import "smelte/src/tailwind.css";
import Router from "svelte-spa-router";
import { routes } from "./routes.js";
</script>
<main>
<Router {routes} />
</main>
<style>
</style>
// routes.js
import Home from './views/home/index/main.svelte'
export const routes = {
'/': Home,
}
知识总结
前端
- svelte(类似于react / vue)
- rollup(类似于webpack)
- smelte(类似于antd / element)
- tailwind CSS (类似于bootstrap / css / postcss)
后端
- Sequelize(类似于java-mybatis)
其他
Rollup
rollup -c -w
- c: 找根目录的
rollup.config.js作为我的构建的配置; - w: watch 监听文件的变化,实时构建;
中间件
- redux-compose 中间件相当于把函数串联起来
- 路由守卫也是一个中间件
- 中间件是面向切面的
function(arr) {
return arr.reduce((a, b) => (...args) => a(b(...args)) )
}
大白话,就是 一个数组 里面都是函数 一个函数的结果 给下一个函数作为参数
然后依次调用 最后一个函数输出结果
这就是redux-compose底层实现