共计 18243 个字符,预计需要花费 46 分钟才能阅读完成。
防抖函数
原理: 事件被触发 n 秒后再执行回调,n 秒内又被触发, 则从新计时 (相似 lol 按 b 键回城)。
// 函数防抖的实现
function debounce(fn, wait) {
let timer = null;
return function () {
let context = this,
args = arguments;
// 如果此时存在定时器的话,则勾销之前的定时器从新记时
if (timer) {clearTimeout(timer);
timer = null;
}
// 设置定时器,使事件间隔指定事件后执行
timer = setTimeout(() => {fn.apply(context, args);
}, wait);
};
}
实用场景:
- 按钮提交场景:避免屡次提交按钮, 只执行最初提交的一次
- 窗口滚动条的监听:滚动条触发次数太过频繁并且通常只须要获取最初一次的地位
在线运行 / 预览地址
节流函数
防抖函数原理: 规定在一个单位工夫内,只能触发一次函数。如果这个单位工夫内触发屡次函数,只有一次失效。(lol 技能冷却)
应用工夫戳
// 函数节流的实现;
function throttle(fn, delay) {let curTime = Date.now();
return function () {
let context = this,
args = arguments,
nowTime = Date.now();
// 如果两次工夫距离超过了指定工夫,则执行函数。if (nowTime - curTime >= delay) {curTime = Date.now();
return fn.apply(context, args);
}
};
}
应用标记
// 函数节流的实现;
const throttle = (fn, delay = 500) => {
let flag = true;
return (...args) => {if (!flag) return;
flag = false;
setTimeout(() => {fn.apply(this, args);
flag = true;
}, delay);
};
};
实用场景:
- 拖拽场景:固定工夫内只执行一次,避免超高频次触发地位变动
- 缩放场景:监控浏览器 resize
- 动画场景:防止短时间内屡次触发动画引起性能问题
在线运行 / 预览地址
深拷贝
// 应用 JSON 解决办法
const newObj = JSON.parse(JSON.stringify(Obj));
局限性:
- 函数对象会被转成 null、RegExp 会变成 {}
- 会摈弃对象的 constructor, 所有的构造函数会指向 Object
- 对象有循环援用, 会报错
// 递归实现深拷贝
function deepCopy(obj, dep = 0) {if (typeof obj !== "object" || dep < 1) {return typeof obj === "object" ? Object.assign({}, obj) : obj;
}
let clone = Array.isArray(obj) ? [] : {};
for (const key in obj) {if (Object.hasOwnProperty.call(obj, key)) {if (obj[key] && typeof obj[key] === "object") {clone[key] = deepCopy(obj[key], dep - 1);
} else {clone[key] = obj[key];
}
}
}
return clone;
}
实现简略通俗易懂个别能写出这个就 ok 了
在线运行 / 预览地址
Object.create
function create(obj) {function F() {}
F.prototype = obj;
return new F();}
ps: 补充一个面试知识点 Object.create(null) 能够创立一个不含原型链的污浊 Object 对象
instanceof
instanceof 运算符用于判断构造函数的 prototype 属性是否呈现在对象的原型链中的任何地位。
实现思路:
- 首先获取类型的原型
- 而后取得对象的原型
- 而后始终循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null
//instanceof 的实现
function myInstanceof(left, right) {function myInstanceof(left, right) {
// 获取对象原型
let __proto__ = Object.getPrototypeOf(left),
prototype = right.prototype; // 获取构造函数的 prototype
while (__proto__) {if (__proto__ === prototype) {return true;}
__proto__ = Object.getPrototypeOf(__proto__);
}
return false;
}
}
在线运行 / 预览地址
new 操作符
在调用 new 的过程中会产生四件事件 (面试常问):
- 在堆去开拓一块内存创立了一个新的空对象
- 设置原型,将新对象的原型设置为函数的 prototype 对象。
- 让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象增加属性)
- 判断函数的返回值类型,如果是值类型,返回创立的对象。如果是援用类型,就返回这个援用类型的对象。
function objectFactory(constructor, ...rest) {
let newObject = null;
let result = null;
// 判断参数是否是一个函数
if (typeof constructor !== "function") {console.error("type error");
return;
}
// 新建一个空对象,对象的原型为构造函数的 prototype 对象
newObject = Object.create(constructor.prototype);
// 将 this 指向新建对象,并执行函数
result = constructor.apply(newObject, ...rest);
// 判断返回对象
let flag =
result && (typeof result === "object" || typeof result === "function");
// 判断返回后果
return flag ? result : newObject;
}
在线运行 / 预览地址
Function.prototype.call 办法
call 函数的实现步骤:
- 判断调用对象是否为函数,即便咱们是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
- 判断传入上下文对象是否存在,如果不存在,则为 window。
- 解决传入的参数,截取第一个参数后的所有参数。
- 将函数作为上下文对象的一个属性。
- 应用上下文对象来调用这个办法,并保留返回后果。
- 删除方才新增的属性。
- 返回后果。
// call 函数实现
Function.prototype.myCall = function (context) {
// 判断调用对象
if (typeof this !== "function") {console.error("type error");
}
// 获取参数
let args = [...arguments].slice(1),
result = null;
// 判断 context 是否传入,如果未传入则设置为 window
context = context || window;
// 将调用函数设为对象的办法
context.fn = this;
// 调用函数
result = context.fn(...args);
// 将属性删除
delete context.fn;
return result;
};
Function.prototype.apply 办法
apply 函数的实现步骤:
- 判断调用对象是否为函数,即便咱们是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
- 判断传入上下文对象是否存在,如果不存在,则设置为 window。
- 将函数作为上下文对象的一个属性。
- 判断参数值是否传入
- 应用上下文对象来调用这个办法,并保留返回后果。
- 删除方才新增的属性
- 返回后果
// apply 函数实现
Function.prototype.myApply = function (context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {throw new TypeError("Error");
}
let result = null;
// 判断 context 是否存在,如果未传入则为 window
context = context || window;
// 将函数设为对象的办法
context.fn = this;
// 调用办法
if (arguments[1]) {result = context.fn(...arguments[1]);
} else {result = context.fn();
}
// 将属性删除
delete context.fn;
return result;
};
Function.prototype.bind 办法
bind 函数的实现步骤:
- 判断调用对象是否为函数,即便咱们是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
- 保留以后函数的援用,获取其余传入参数值。
- 创立一个函数返回
- 函数外部应用 apply 来绑定函数调用,须要判断函数作为构造函数的状况,这个时候须要传入以后函数的 this 给 apply 调用,其余状况都传入指定的上下文对象。
// bind 函数实现
Function.prototype.myBind = function (context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {throw new TypeError("Error");
}
// 获取参数
var args = [...arguments].slice(1),
fn = this;
return function Fn() {
// 依据调用形式,传入不同绑定值
return fn.apply(
this instanceof Fn ? this : context,
args.concat(...arguments)
);
};
};
函数柯里化
柯里化就是把承受「多个参数」的函数变换成承受一个「繁多参数」的函数,并且返回承受「余下参数」返回后果的一种利用。
思路:
- 判断传递的参数是否达到执行函数的 fn 个数
- 没有达到的话,持续返回新的函数,并且返回 curry 函数传递残余参数
// 实现 1
function curry(fn, args) {
// 获取函数须要的参数长度
let length = fn.length;
args = args || [];
return function () {let subArgs = args.slice(0);
// 拼接失去现有的所有参数
for (let i = 0; i < arguments.length; i++) {subArgs.push(arguments[i]);
}
// 判断参数的长度是否曾经满足函数所需参数的长度
if (subArgs.length >= length) {
// 如果满足,执行函数
return fn.apply(this, subArgs);
} else {
// 如果不满足,递归返回科里化的函数,期待参数的传入
return curry.call(this, fn, subArgs);
}
};
}
// 实现 2
function currying(exeFunc) {let args = [];
let currFunc = function (...rest) {args.push(...rest);
if (rest.length <= 0) {return exeFunc(args);
} else {return currFunc;}
};
return currFunc;
}
// es6 实现
function curry(fn, ...args) {return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}
在线运行 / 预览地址
AJAX 申请
AJAX 是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新以后网页的对应局部,而不必刷新整个网页。
创立 AJAX 申请的步骤:
- 创立一个 XMLHttpRequest 对象。
- 在这个对象上应用 open 办法创立一个 HTTP 申请,open 办法所须要的参数是申请的办法、申请的地址、是否异步和用户的认证信息。
- 在发动申请前,能够为这个对象增加一些信息和监听函数。比如说能够通过 setRequestHeader 办法来为申请增加头信息。还能够为这个对象增加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变动时会触发 onreadystatechange 事件,能够通过设置监听函数,来解决申请胜利后的后果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接管实现,这个时候能够通过判断申请的状态,如果状态是 2xx 或者 304 的话则代表返回失常。这个时候就能够通过 response 中的数据来对页面进行更新了。
- 当对象的属性和监听函数设置实现后,最初调用 sent 办法来向服务器发动申请,能够传入参数作为发送的数据体。
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 创立 Http 申请
xhr.open("GET", SERVER_URL, true);
// 设置状态监听函数
xhr.onreadystatechange = function () {if (this.readyState !== 4) return;
// 当申请胜利时
if (this.status === 200) {handle(this.response);
} else {console.error(this.statusText);
}
};
// 设置申请失败时的监听函数
xhr.onerror = function () {console.error(this.statusText);
};
// 设置申请头信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 发送 Http 申请
xhr.send(null);
在线运行 / 预览地址
A+ 标准的 promise
promise 是 Es6 新加的一个对象手写 promise 能帮忙更好的了解
var PromisePolyfill = (function () {
// 和 reject 不同的是 resolve 须要尝试开展 thenable 对象
function tryToResolve(value) {if (this === value) {
// 次要是避免上面这种状况
// let y = new Promise(res => setTimeout(res(y)))
throw TypeError("Chaining cycle detected for promise!");
}
// 依据标准 2.32 以及 2.33 对对象或者函数尝试开展
// 保障 S6 之前的 polyfill 也能和 ES6 的原生 promise 混用
if (
value !== null &&
(typeof value === "object" || typeof value === "function")
) {
try {
// 这里记录这次 then 的值同时要被 try 包裹
// 次要起因是 then 可能是一个 getter, 也也就是说
// 1. value.then 可能报错
// 2. value.then 可能产生副作用 (例如屡次执行可能后果不同)
var then = value.then;
// 另一方面, 因为无奈保障 then 的确会像预期的那样只调用一个 onFullfilled / onRejected
// 所以减少了一个 flag 来避免 resolveOrReject 被屡次调用
var thenAlreadyCalledOrThrow = false;
if (typeof then === "function") {
// 是 thenable 那么尝试开展
// 并且在该 thenable 状态扭转之前 this 对象的状态不变
then.bind(value)(
// onFullfilled
function (value2) {if (thenAlreadyCalledOrThrow) return;
thenAlreadyCalledOrThrow = true;
tryToResolve.bind(this, value2)();}.bind(this),
// onRejected
function (reason2) {if (thenAlreadyCalledOrThrow) return;
thenAlreadyCalledOrThrow = true;
resolveOrReject.bind(this, "rejected", reason2)();}.bind(this)
);
} else {
// 领有 then 然而 then 不是一个函数 所以也不是 thenable
resolveOrReject.bind(this, "resolved", value)();}
} catch (e) {if (thenAlreadyCalledOrThrow) return;
thenAlreadyCalledOrThrow = true;
resolveOrReject.bind(this, "rejected", e)();}
} else {
// 根本类型 间接返回
resolveOrReject.bind(this, "resolved", value)();}
}
function resolveOrReject(status, data) {if (this.status !== "pending") return;
this.status = status;
this.data = data;
if (status === "resolved") {for (var i = 0; i < this.resolveList.length; ++i) {this.resolveList[i]();}
} else {for (i = 0; i < this.rejectList.length; ++i) {this.rejectList[i]();}
}
}
function Promise(executor) {if (!(this instanceof Promise)) {throw Error("Promise can not be called without new !");
}
if (typeof executor !== "function") {
// 非标准 但与 Chrome 谷歌保持一致
throw TypeError("Promise resolver" + executor + "is not a function");
}
this.status = "pending";
this.resolveList = [];
this.rejectList = [];
try {executor(tryToResolve.bind(this), resolveOrReject.bind(this, "rejected"));
} catch (e) {resolveOrReject.bind(this, "rejected", e)();}
}
Promise.prototype.then = function (onFullfilled, onRejected) {
// 返回值穿透以及谬误穿透, 留神谬误穿透用的是 throw 而不是 return,否则的话
// 这个 then 返回的 promise 状态将变成 resolved 即接下来的 then 中的 onFullfilled
// 会被调用, 然而咱们想要调用的是 onRejected
if (typeof onFullfilled !== "function") {onFullfilled = function (data) {return data;};
}
if (typeof onRejected !== "function") {onRejected = function (reason) {throw reason;};
}
var executor = function (resolve, reject) {
setTimeout(function () {
try {
// 拿到对应的 handle 函数解决 this.data
// 并以此为根据解析这个新的 Promise
var value =
this.status === "resolved"
? onFullfilled(this.data)
: onRejected(this.data);
resolve(value);
} catch (e) {reject(e);
}
}.bind(this)
);
};
// then 承受两个函数返回一个新的 Promise
// then 本身的执行永远异步与 onFullfilled/onRejected 的执行
if (this.status !== "pending") {return new Promise(executor.bind(this));
} else {
// pending
return new Promise(function (resolve, reject) {this.resolveList.push(executor.bind(this, resolve, reject));
this.rejectList.push(executor.bind(this, resolve, reject));
}.bind(this)
);
}
};
// for prmise A+ test
Promise.deferred = Promise.defer = function () {var dfd = {};
dfd.promise = new Promise(function (resolve, reject) {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
};
// for prmise A+ test
if (typeof module !== "undefined") {module.exports = Promise;}
return Promise;
})();
PromisePolyfill.all = function (promises) {return new Promise((resolve, reject) => {const result = [];
let cnt = 0;
for (let i = 0; i < promises.length; ++i) {promises[i].then((value) => {
cnt++;
result[i] = value;
if (cnt === promises.length) resolve(result);
}, reject);
}
});
};
PromisePolyfill.race = function (promises) {return new Promise((resolve, reject) => {for (let i = 0; i < promises.length; ++i) {promises[i].then(resolve, reject);
}
});
};
在线运行 / 预览地址
千位分隔符
千位分隔符是解决日期罕用的形式
function parseToMoney(num) {num = parseFloat(num.toFixed(3));
let [integer, decimal] = String.prototype.split.call(num, ".");
integer = integer.replace(/\d(?=(\d{3})+$)/g, "$&,");
return integer + "." + (decimal ? decimal : "");
}
观察者模式
当对象间存在一对多关系时,则应用观察者模式(Observer Pattern)。比方,当一个对象被批改时,则会主动告诉依赖它的对象。vue 双向绑定就是应用了观察者模式。
class Subject {constructor() {this.ObserverList = [];
}
add(observer) {this.ObserverList.push(observer);
}
remove(observer) {this.ObserverList = this.ObserverList.filter((item) => item !== observer);
}
notify(...arg) {this.ObserverList.forEach((cb) => cb.update(...arg));
}
}
class Observer {constructor(name) {this.name = name;}
update() {console.log(this.name);
}
}
在线运行 / 预览地址
定义公布 / 订阅
公布 / 订阅模式:基于一个主题 / 事件通道,心愿接管告诉的对象(称为 subscriber)通过自定义事件订阅主题,被激活事件的对象(称为 publisher)通过公布主题事件的形式被告诉。vue2 中的全局事件总线就是应用这种设计模式。
/**
* 公布 / 订阅模式组件
* @author wilton
*/
// 定义公布 / 订阅类
class Pubsub {constructor() {this.topics = {};
this.subUid = -1;
}
// 公布事件
publish(topic, args) {if (!this.topics[topic]) return false;
let subscribers = this.topics[topic];
let len = subscribers ? subscribers.length : 0;
while (len--) {subscribers[len].func(topic, args);
}
return this;
}
// 订阅事件
subscribe(topic, func) {if (!this.topics[topic]) this.topics[topic] = [];
let token = (++this.subUid).toString();
this.topics[topic].push({
token: token,
func: func,
});
return token;
}
// 勾销订阅
unsubscribe(token) {for (let m in topics) {if (topics[m]) {for (let i = 0; i < topics[m].length; i++) {if (topics[m][i].token == token) {topics[m].splice(i, 1);
return token;
}
}
}
}
return this;
}
}
在线运行 / 预览地址
应用 setTimeout 实现 setInterval
setInterval 的作用是每隔一段指定工夫执行一个函数,然而这个执行不是真的到了工夫立刻执行,它真正的作用是每隔一段时间将事件退出事件队列中去,只有当执行栈为空的时候,能力去从事件队列中取出事件执行。所以可能会呈现这样的状况,就是以后执行栈执行的工夫很长,导致事件队列里边积攒多个定时器退出的事件,当执行栈完结的时候,这些事件会顺次执行,因而就不能到距离一段时间执行的成果。
针对 setInterval 的这个毛病,咱们能够应用 setTimeout 递归调用来模仿 setInterval,这样咱们就确保了只有一个事件完结了,咱们才会触发下一个定时器事件,这样解决了 setInterval 的问题。
function mySetInterval(fn, timeout) {setTimeout(() => {fn();
mySetInterval(fn, timeout);
}, timeout);
}
将 js 对象转化为树形构造
// 转换前:source = [{
id: 1,
pid: 0,
name: 'body'
}, {
id: 2,
pid: 1,
name: 'title'
}, {
id: 3,
pid: 2,
name: 'div'
}]
// 转换为:
tree = [{
id: 1,
pid: 0,
name: 'body',
children: [{
id: 2,
pid: 1,
name: 'title',
children: [{
id: 3,
pid: 1,
name: 'div'
}]
}
}]
function jsonToTree(data) {
// 初始化后果数组,并判断输出数据的格局
let result = []
if(!Array.isArray(data)) {return result}
// 应用 map,将以后对象的 id 与以后对象对应存储起来
let map = {};
data.forEach(item => {map[item.id] = item;
});
//
data.forEach(item => {let parent = map[item.pid];
if(parent) {(parent.children || (parent.children = [])).push(item);
} else {result.push(item);
}
});
return result;
}
在线运行 / 预览地址
实现 sleep 函数
// 应用 Promise 封装 setTimeou
function timeout(delay) {return new Promise((resolve) => {setTimeout(resolve, delay);
});
// 应用工夫戳阻塞线程
function sleep(time, fn) {let curDate = Date.now();
while (Date.now() - curDate < time);
fn();}
sleep(3000, () => {console.log(1);
});
}
Es6 的 flat(数组扁平化)
数组扁平化就是将 [1, [2, [3]]] 这种多层的数组拍平成一层 [1, 2, 3]。
// 数组扁平化
let arr = [[22, [1, 2, [5, 8], [9, 2]], [2, 7, 1, [9, 0, [1, 6]]]], [2]];
//1 reduce + concat
function flatten(arr, dep) {
return dep > 1
? arr.reduce((pre, cur) =>
pre.concat(Array.isArray(cur) ? flatten(cur, dep - 1) : cur),
[])
: Array.prototype.slice.call(arr);
}
//2 some + concat + 构造
function _flatten(arr, dep = 0) {if (dep < 1) {return arr.slice();
}
while (dep && arr.some((item) => Array.isArray(item))) {arr = [].concat(...arr);
dep--;
}
return arr;
}
//3 pust + apply
function __flatten(arr, dep = 0) {if (dep < 1) {return Array.prototype.slice.call(arr);
}
let result = [];
arr.forEach((element) =>
Array.isArray(element)
? result.push.apply(result, __flatten(element, dep - 1))
: result.push(element)
);
return result;
}
//4 number only
function ___flatten(arr) {
return arr
.toString()
.split(",")
.map((item) => +item);
}
//5 stack
function ____flatten(arr, dep = 0) {let stack = [...arr];
let result = [];
while (stack.length) {let data = stack.pop();
if (Array.isArray(data)) {stack.push(...data);
} else {result.push(data);
}
}
return result.reverse();}
在线运行 / 预览地址
Object.assign
浅拷贝
Object.myAssign = function (target, ...source) {if (target == null) {throw new TypeError("Cannot convert undefined or null to object");
}
let ret = Object(target);
source.forEach(function (obj) {if (obj != null) {for (let key in obj) {if (obj.hasOwnProperty(key)) {ret[key] = obj[key];
}
}
}
});
return ret;
};
实现每隔一秒打印 1,2,3,4
var 作用域问题会导致始终输入最初一个数字
// 应用闭包实现
for (var i = 0; i < 5; i++) {(function (i) {setTimeout(function () {console.log(i);
}, i * 1000);
})(i);
}
// 应用 let 块级作用域
for (let i = 0; i < 5; i++) {setTimeout(function () {console.log(i);
}, i * 1000);
}
js 解析 URL Params 为对象
let url =
"http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled";
parseParam(url);
/* 后果
{ user: 'anonymous',
id: [123, 456], // 反复呈现的 key 要组装成数组,能被转成数字的就转成数字类型
city: '北京', // 中文需解码
enabled: true, // 未指定值得 key 约定为 true
}
*/
function parseParam(url) {const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 前面的字符串取出来
const paramsArr = paramsStr.split("&"); // 将字符串以 & 宰割后存到数组中
let paramsObj = {};
// 将 params 存到对象中
paramsArr.forEach((param) => {if (/=/.test(param)) {
// 解决有 value 的参数
let [key, val] = param.split("="); // 宰割 key 和 value
val = decodeURIComponent(val); // 解码
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
if (paramsObj.hasOwnProperty(key)) {
// 如果对象有 key,则增加一个值
paramsObj[key] = [].concat(paramsObj[key], val);
} else {
// 如果对象没有这个 key,创立 key 并设置值
paramsObj[key] = val;
}
} else {
// 解决没有 value 的参数
paramsObj[param] = true;
}
});
return paramsObj;
}
实现一个 JSON.stringify
- Boolean | Number| String 类型会主动转换成对应的原始值。
- undefined、任意函数以及 symbol,会被疏忽(呈现在非数组对象的属性值中时),或者被转换成 null(呈现在数组中时)。
- 不可枚举的属性会被疏忽
- 如果一个对象的属性值通过某种间接的形式指回该对象自身,即循环援用,属性也会被疏忽。
-
Function 会变成 null RegExp 对象会变成 {}
function jsonStringify(obj) { let type = typeof obj; if (type !== "object") {if (/string|undefined|function/.test(type)) {obj = '"'+ obj +'"';} return String(obj); } else {let json = []; let arr = Array.isArray(obj); for (let k in obj) {let v = obj[k]; let type = typeof v; if (/string|undefined|function/.test(type)) {v = '"'+ v +'"';} else if (type === "object") {v = jsonStringify(v); } json.push((arr ? "":'"' + k + '":') + String(v)); } return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}"); } }
在线运行 / 预览地址
实现 JSON.parse
let json = '{"name":"cxk","age":25}';
let obj = eval("(" + json + ")");
应用 eval 实现简略但很容易被 xss 攻打
js 的继承
继承有很多形式这里只演绎最优的两种寄生组合式和 es6 的 class 继承
// 寄生组合式继承
function inheritPrototype(subType, superType) {
// 创建对象,创立父类原型的一个正本
let prototype = Object.create(superType.prototype);
// constructor 属性为子类构造函数
prototype.constructor = subType;
// 指定对象,将新创建的对象赋值给子类的原型
subType.prototype = prototype;
}
//es6 class 的 extends
class Rectangle {
// constructor
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {return this.calcArea();
}
// Method
calcArea() {return this.height * this.width;}
}
const rectangle = new Rectangle(40, 20);
console.log(rectangle.area);
// 输入 800
// 继承
class Square extends Rectangle {constructor(len) {
// 子类没有 this, 必须先调用 super
super(len, len);
// 如果子类中存在构造函数,则须要在应用“this”之前首先调用 super()。this.name = "SquareIng";
}
get area() {return this.height * this.width;}
}
const square = new Square(20);
console.log(square.area);
// 输入 400
//extends 继承的外围代码如下,其实和寄生组合式继承形式一样
function _inherits(subType, superType) {
// 创建对象,创立父类原型的一个正本
// 加强对象,补救因重写原型而失去的默认的 constructor 属性
// 指定对象,将新创建的对象赋值给子类的原型
subType.prototype = Object.create(superType && superType.prototype, {
constructor: {
value: subType,
enumerable: false,
writable: true,
configurable: true,
},
});
if (superType) {
Object.setPrototypeOf
? Object.setPrototypeOf(subType, superType)
: (subType.__proto__ = superType);
}
}
在线运行 / 预览地址
判断对象是否存在循环援用
循环援用对象原本没有什么问题,然而序列化的时候就会产生问题,比方调用 JSON.stringify() 对该类对象进行序列化,就会报错: Converting circular structure to JSON.
const isCycleObject = (obj, parent) => {const parentArr = parent || [obj];
for (let i in obj) {if (typeof obj[i] === "object") {
let flag = false;
parentArr.forEach((pObj) => {if (pObj === obj[i]) {flag = true;}
});
if (flag) return true;
flag = isCycleObject(obj[i], [...parentArr, obj[i]]);
if (flag) return true;
}
}
return false;
};
在线运行 / 预览地址
数组的去重
数组去重也是罕用的解决数组面试题
// 双重循环
function unique(array) {
// res 用来存储后果
var res = [];
for (var i = 0, arrayLen = array.length; i < arrayLen; i++) {for (var j = 0, resLen = res.length; j < resLen; j++) {if (array[i] === res[j]) {break;}
}
// 如果 array[i] 是惟一的,那么执行完循环,j 等于 resLen
if (j === resLen) {res.push(array[i]);
}
}
return res;
}
//es6 set
function unique(arr) {return [...new Set(arr)];
}
//filter + indexOf
function unique_(arr) {return arr.filter((item, index) => arr.indexOf(item) === index);
}
//reduce + includes
function unique__(arr) {
return arr.reduce((pre, cur) => (pre.includes(cur) ? pre : pre.concat(cur)),
[]);
}
//map
function unique___(arr) {let mp = new Map();
arr.forEach((element) => {if (!mp.has(element)) {mp.set(element, 1);
}
});
return [...mp.keys()];
}
// 排序后
function unique(array) {var res = [];
var sortedArray = array.slice().sort();
var seen;
for (var i = 0, len = sortedArray.length; i < len; i++) {
// 如果是第一个元素或者相邻的元素不雷同
if (!i || seen !== sortedArray[i]) {res.push(sortedArray[i]);
}
seen = sortedArray[i];
}
return res;
}
在线运行 / 预览地址
图片懒加载
图片懒加载就是鼠标滑动到哪里,图片加载到哪里。总的来说,个别页面关上,会同时加载页面所有的图片,如果页面的图片申请太多会造成很卡很慢的景象,为了防止这一景象,利用懒加载图片的办法,进步性能
let imgList = [...document.querySelectorAll("img")];
let length = imgList.length;
const imgLazyLoad = (function () {
let count = 0;
return function () {let deleteIndexList = [];
imgList.forEach((img, index) => {let rect = img.getBoundingClientRect();
if (rect.top < window.innerHeight) {
img.src = img.dataset.src;
deleteIndexList.push(index);
count++;
if (count === length) {document.removeEventListener("scroll", imgLazyLoad);
}
}
});
imgList = imgList.filter((img, index) => !deleteIndexList.includes(index));
};
})();
// 加上防抖解决
document.addEventListener("scroll", debounce(imgLazyLoad, 200));
洗牌算法
打乱数组开发场景中也是很罕用
function shuffle(arr) {for (leti = 0; i < arr.length; i++) {let randomIndex = i + Math.floor(Math.random() * (arr.length - i));
[arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];
}
return arr;
}
在线运行 / 预览地址