1、Object.create(proto, defineProperties)
//利用工厂函数返回一个对象
//Object.create(obj)
// 参数1:proto 为新对象的原型指向
// 参数2:defineProperties 为新对象添加的自定义属性
Object.myCreate = function (proto, defineProperties) {
// 实例化创建对象
function F() {}
F.prototype = proto;
let obj = new F();
// 添加自定义属性
defineProperties && Object.defineProperties(obj, defineProperties);
return obj;
};
let obj = { name: "张三", age: 18 };
let createObj = Object.myCreate(obj, {
foo: {
writable: true,
configurable: true,
value: "hello",
},
});
console.log(createObj);
2、call & apply & bind
2.1 call
利用 函数执行上下文 原理 : that.myFn(...arg)
Function.prototype.newCall = function (that, ...arg) {
that = that || window;
that.myFn = this;
const result = that.myFn(...arg); // es6语法
delete that.myFn;
return result;
};
const obj = {
name: "joy",
};
function getName(a, b) {
console.log(this.name, a, b);
}
getName.newApply(obj, 1, 2); // joy 1 2
纯Es5语法
Function.prototype.myCall = function (context) {
//生成独一无二的id
function mySymbol(obj) {
let unique = Math.random() + new Date();
if (obj.hasOwnProperty(unique)) {
return mySymbol(obj);
} else {
return unique;
}
}
// 生成唯一id
let uniqueName = mySymbol(context);
// 获取参数
let args = [];
for (let i = 1; i < arguments.length; i++) {
// 使用逗号包裹,避免eval执行时;作为变量
args.push("arguments[" + i + "]");
}
// 给传入的对象,绑定函数
context[uniqueName] = this;
//执行函数
//1、如果直接 context[uniqueName]( args.join(",") )
// 只当做一个字符串处理,不是多个参数
//2、使用eval
// 原理:eval直接执行,js字符串
let result = eval("context[uniqueName](" + args.join(",") + ")");
//删除临时方法
delete context[uniqueName];
//返回处理后的结果
return result;
};
var obj1 = {
getName: function (name) {
return `${this.age},${name}`;
},
};
var obj = { age: 18 };
var detail = obj1.getName.myCall(obj, "张三");
console.log(detail);
2.2 apply
Function.prototype.newApply = function (that, arg) {
if (typeof this !== "function") {
throw this + "is not a function";
}
that = that || window; // 因为第一个参数如果不传就会绑定到window上面
arg = arg || [];
that.myFn = this;
const result = that.myFn(...arg); //解构赋值把参数传进来,先把结果存起来
delete that.myFn; //再删除,否则就有副作用了
return result;
};
const obj = {
name: "joy",
};
function getName(a, b) {
console.log(this.name, a, b);
}
getName.newApply(obj, [1, 2]); // joy
纯Es5语法
Function.prototype.myApply = function (context, args) {
function mySymbol(obj) {
let unique = Math.random() + new Date();
if (obj.hasOwnProperty(unique)) {
return mySymbol(obj);
} else {
return unique;
}
}
let uniqueName = mySymbol(context);
let arr = [];
for (let i = 0; i < args.length; i++) {
arr.push("args[" + i + "]");
}
context[uniqueName] = this;
let result = eval("context[uniqueName](" + arr.join(",") + ")");
delete context[uniqueName];
return result;
};
var obj1 = {
getName: function (name) {
return `${this.age},${name}`;
},
};
var obj = { age: 18 };
var detail = obj1.getName.myApply(obj, ["张三"]);
console.log(detail);
2.3 bind
Function.prototype.myBind = function (ctx) {
var originFn = this,
args = [].slice.call(arguments, 1),
_tempFn = function () {};
// 继承实例上的属性和方法
var newFn = function () {
var newArgs = [].slice.call(arguments);
var newCtx = this instanceof newFn ? this : ctx;
return originFn.apply(newCtx, args.concat(newArgs));
};
// 新的实例对象获取Foo上的原型
_tempFn.prototype = this.prototype;
newFn.prototype = new _tempFn(); // 继承原型上的方法
return newFn; // 最终返回一个函数
};
3、 new Fn()
new的过程做了哪些事
- 调用这个函数,创建一个空对象( 函数对象 )
- 获取参数:args
- 让函数中的this指向这个新对象 ( 创建出来的对象绑定this )
- 默认返回这个对象 ( 即:创建的实例就是return的对象 )
-
- 默认返回this对象
- return 1; // 如果手动添加返回不是对象,不影响 this
- return fn; // 如果手动返回一个对象,响应 this
// **** 构造函数不能作为构造函数,没有自己的this ****
function Cat(name, color) {
this.name = name;
this.color = color;
return null;
}
Cat.prototype.miao = function () {
console.log("喵~!");
};
// factory 工厂函数
// constructor 为函数对象
function newFactory(constructor) {
// 1、创建对象
let obj = Object.create(constructor.prototype);
// 2、获取参数:arguments是类数组,不能直接就是用 slice 方法
let args = Array.from(arguments).slice(1);
// 3、执行函数
let result = constructor.apply(obj, args);
// 4、返回结果
return typeof result === "object" ? result : obj;
}
let cat = newFactory(Cat, "大毛", "橘色");
cat.miao();
4、节流 & 防抖
节流 - 间隔执行、完成当前才能执行下一次
function throttle(func, wait) {
let timmer = null;
return function () {
if (!timmer) {
timmer = setTimeout(() => {
clearTimeout(timmer);
timmer = null;
func.apply(this, arguments);
}, wait);
}
};
}
function throttle(func, wait) {
var prev = 0;
return function () {
let now = Date.now();
if (now - prev > wait) {
func.apply(this, arguments);
prev = now;
}
};
}
防抖 - 延迟执行、清除上一次、重新开计时
function debounce(func, wait) {
let timmer = null;
return function () {
if (timmer) clearTimeout(timmer), timmer == null;
timmer = setTimeout(() => {
func.apply(this, arguments);
}, wait);
};
}
5、深拷贝
function deepCp(resource) {
let target;
if (typeof resource == "object") {
target = Array.isArray(resource) ? [] : {};
// for...in 遍历,数组和对象都可以
for (let key in resource) {
if (Object.prototype.hasOwnProperty(resource, key)) {
if (typeof resource[key] == "object") {
target[key] = deepCp(resource[key]);
} else {
target[key] = resource[key];
}
}
}
} else {
target = resource;
}
return target;
}
deepCp([
{ a: "aaa", b: { bb: "bbb" } },
{ a: "aaa", b: { bb: "bbb" } },
]);
6、图片懒加载
// 获取当前页面全部的 image 标签
var oImg = document.getElementsByTagName("img");
fn();
window.onscroll = function () {
fn(); // 滚轮滚动事件( 可以使用防抖技术 )
};
function fn() {
// 判断图片是否在可视区内
for (var i = 0; i < oImg.length; i++) {
// 当前元素顶部距离最近父元素顶部的距离( 默认指向body )
//offsetTop:,除非自己制定父元素为relative、absolute
var oImgTo = oImg[i].offsetTop;
// 网页可见区域高
var clientH = document.documentElement.clientHeight;
// 滚动条的垂直距离( 向上滚动隐藏的高度 )
var scrollT = document.documentElement.scrollTop || document.body.scrollTop;
// 可视区顶部距离页面顶部的距离
if (clientH + scrollT >= oImgTo && !oImg[i].isLoaded) {
oImg[i].src = oImg[i].getAttribute("_src");
oImg[i].setAttribute("isLoaded", true);
}
}
}
7、发布 & 订阅
// 事件触发器
class EventEmitter {
constructor() {
this.subs = {};
}
// 注册事件
$on(eventType, handler) {
this.subs[eventType] = this.subs[eventType] || [];
this.subs[eventType].push(handler);
}
// 触发事件
$emit(eventType) {
if (this.subs[eventType]) {
this.subs[eventType].forEach((handler) => {
handler();
});
}
}
}
// 测试
let em = new EventEmitter();
em.$on("click", () => {
console.log("click1");
});
em.$on("click", () => {
console.log("click2");
});
em.$emit("click");
8、观察者模式
-
观察者模式
- 由具体目标(发布者) 调度(Dep) ,订阅者更新(watcher )
- 观察者模式的订阅者与发布者之间存在 依赖关系
-
发布/订阅者模式
- 包含事件中心( 统一调用中心 )
- 去除发布者和订阅者的依赖( 组件A订阅事件,组件B中触发 )
// 发布者-目标 收集订阅者、通知订阅者更新
class Dep {
constructor() {
// 记录所有的订阅者
this.subs = [];
}
// 添加订阅者
addSub(sub) {
if (sub && sub.update) {
this.subs.push(sub);
}
}
// 发布通知
notify() {
this.subs.forEach((sub) => {
sub.update();
});
}
}
// 订阅者-观察者 被收集、
class Watcher {
update() {
console.log("update");
}
}
// 测试
let dep = new Dep();
let watcher = new Watcher();
dep.addSub(watcher);
dep.notify();
9、控制并发量
function sendRequest(chunks, limit = 4) {
return new Promise((resolve, reject) => {
let isStop = false;
let start = async function () {
if (isStop) return;
let work = chunks.shift();
if (work) {
let [form, index] = work;
try {
await axios.post("/update", form);
if (chunks.length) start();
else resolve();
} catch (error) {
if (error.count < 3) {
error.count++;
chunks.unshift(work);
start();
} else {
isStop = true;
reject();
}
}
}
};
while (limit) {
start();
limit--;
}
});
}
10、实现单点登录
原理:统一登陆SSO,cookie共享(顶级域名)
- 已被登陆
- 初次访问当前项目,header中会被设置cookie
- 请求时也会自动携带过去
- 此时会验证通过
- 未登录
- 访问次项目,无cookie
- 重定向到统一的登陆页面
- 登陆成功后,自动设置cookie( doman:顶级域名, path:/)
- 子域名会共享顶级域名的cookie
import axios from "axios";
import store from "@/store";
import { Message } from "element-ui";
import router from "@/router";
import qs from "qs";
const request = axios.create({
baseURL: "/api",
});
function redirectLogin() {
router.push({
name: "login",
query: {
redirect: router.currentRoute.fullPath,
},
});
}
// refresh_token 只能使用1次
function refreshToken() {
return axios.create()({
method: "POST",
url: "/front/user/refresh_token",
data: qs.stringify({
refreshtoken: store.state.user.refresh_token,
}),
});
}
// 请求拦截器 - 设置token
request.interceptors.request.use(
function (config) {
const { user } = store.state;
if (user && user.access_token) {
config.headers.Authorization = user.access_token;
}
return config;
},
function (error) {
return Promise.reject(error);
}
);
// 响应拦截器
let isRfreshing = false; // 控制刷新 token 的状态
let requests: Array<() => void> = []; // 存储刷新 token 期间过来的 401 请求
request.interceptors.response.use(
function (response) {
// 1、状态码为 2xx 都会进入这里
// 如果是自定义错误状态码,错误处理就写到这里
return response;
},
async function (error) {
// 2、超出 2xx 状态码都都执行这里
if (error.response) {
const { status } = error.response;
if (status === 400) {
Message.error("请求参数错误");
} else if (status === 401) {
// token无效 或 过期
// user 不存直接从新登录
if (!store.state.user) {
redirectLogin();
return Promise.reject(error);
} // 刷新 token :只能刷新一次
if (!isRfreshing) {
isRfreshing = true; // 开启刷新状态 // 尝试刷新获取新的 token
return refreshToken()
.then((res) => {
if (!res.data.success) {
throw new Error("刷新 Token 失败");
} // 刷新 token 成功了
store.commit("setUser", res.data.content); // 执行 requests 队列中的请求
requests.forEach((cb) => cb()); // 重置 requests 数组
requests = [];
return request(error.config);
})
.catch(() => {
Message.warning("登录已过期,请重新登录");
store.commit("setUser", null);
redirectLogin();
return Promise.reject(error);
})
.finally(() => {
isRfreshing = false; // 重置刷新状态
});
} // 1、refreshToken 的请求是异步的 // 2、页面调用接口时是同步的,此时会收集所有接口放入 requests 数组中 // 3、等token重新刷新后,依次重新执行requests中的接口 request(error.config)
return new Promise((resolve) => {
requests.push(() => {
resolve(request(error.config));
});
});
} else if (status === 403) {
Message.error("没有权限,请联系管理员");
} else if (status === 404) {
Message.error("请求资源不存在");
} else if (status >= 500) {
Message.error("服务端错误,请联系管理员");
}
} else if (error.request) {
// 请求发出去没有收到响应
Message.error("请求超时,请刷新重试");
} else {
// 在设置请求时发生了一些事情,触发了一个错误
Message.error(`请求失败:${error.message}`);
} // 把请求失败的错误对象继续抛出,扔给上一个调用者
return Promise.reject(error);
}
);
export default request;