# 防抖函数
function bound(fn, time = 300) {
let cleartimer;
return function(...arguments) {
if (cleartimer) {
clearTimeout(cleartimer)
}
cleartime = setTimeout(() => {
fn.apply(this, arguments);
}, time);
}
}
# 节流函数
function throtte(fn, time=300) {
let can = true;
return function(...args) {
if (!can) return;
can = false;
setTimeout(() => {
can = true;
fn.apply(this, args);
}, time)
}
}
# new
function myNew() {
const obj = {};
const constructFn = [].shift.call(arguments);
obj.__proto__ = constructFn.prototype;
const parmas = arguments;
const res = constructFn.apply(obj, params);
return res instanceOf Object ? res : obj;
}
# const实现原理
var __const = function __const (data, value) {
window.data = value // 把要定义的data挂载到window下,并赋值value
// 利用Object.defineProperty的能力劫持当前对象,并修改其属性描述符
Object.defineProperty(window, data, {
enumerable: false,
configurable: false,
get: function () {
return value
},
set: function (data) {
if (data !== value) { // 当要对当前属性进行赋值时,则抛出错误!
throw new TypeError('Assignment to constant variable.')
} else {
return value
}
}
})
}
__const('a', 10)
console.log(a)
delete a
console.log(a)
// 因为const定义的属性在global下也是不存在的,所以用到了enumerable: false来模拟这一功能
for (let item in window) {
if (item === 'a') { // 因为不可枚举,所以不执行
console.log(window[item])
}
}
a = 20 // 报错
# bind函数实现
Function.prototype.selfBind = function(that, ...arg) {
const fn = this;
const newFn = function(...parmas) {
// bind返回后的函数还能用来new 此时会忽略传给bind的this
if (this instanceof newFn) {
fn.apply(this, arg.concat.parmas);
} else {
fn.apply(that, arg.concat.parmas);
}
}
newFn.prototype = Object.create(fn.prototype);
return newFn;
}
# call函数实现
Function.prototype.selfCall = function(that, ...args) {
const fn = this;
const key = Symbol('key');
that[key] = fn;
const res = that[key](...args);
delete that[key];
return res;
}
# apply函数实现
Function.prototype.selfApply = function(that, args) {
const fn = this;
const key = Symbol('key');
that[key] = fn;
let res;
if (args && args.length > 0) {
res = that[key](...args);
} else {
res = that[key]();
}
delete that[key];
return res;
}
# deepCopy
JSON.parse(JSON.stringify(obj))我们一般用来深拷贝的问题
- 如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式。而不是时间对象
- 如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象
- 如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失
- 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null
- JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor
function deepCopy(obj, cache = new WeakMap) {
if (!obj instanceof Object) return obj
// 防止循环引用
if (cache.get(obj)) return cache.get(obj)
// 支持函数
if (obj instanceof Function) {
return function () {
return obj.apply(this, arguments)
}
}
// 支持日期
if (obj instanceof Date) return new Date(obj)
// 支持正则对象
if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags)
// 还可以增加其他对象,比如:Map, Set等,根据情况判断增加即可,面试点到为止就可以了
// 数组是 key 为数字素银的特殊对象
const res = Array.isArray(obj) ? [] : {}
// 缓存 copy 的对象,用于处理循环引用的情况
cache.set(obj, res)
Object.keys(obj).forEach((key) => {
if (obj[key] instanceof Object) {
res[key] = deepCopy(obj[key], cache)
} else {
res[key] = obj[key]
}
});
return res
}
# 发布订阅模式
class EventEmitter {
constructor() {
this.cache = {}
}
on(name, fn) {
if (this.cache[name]) {
this.cache[name].push(fn)
} else {
this.cache[name] = [fn]
}
}
off(name, fn) {
const tasks = this.cache[name]
if (tasks) {
const index = tasks.findIndex((f) => f === fn || f.callback === fn)
if (index >= 0) {
tasks.splice(index, 1)
}
}
}
emit(name) {
if (this.cache[name]) {
// 创建副本,如果回调函数内继续注册相同事件,会造成死循环
const tasks = this.cache[name].slice()
for (let fn of tasks) {
fn();
}
}
}
emit(name, once = false) {
if (this.cache[name]) {
// 创建副本,如果回调函数内继续注册相同事件,会造成死循环
const tasks = this.cache[name].slice()
for (let fn of tasks) {
fn();
}
if (once) {
delete this.cache[name]
}
}
}
}
# 函数柯里化与反柯里化
function curry(func) {
return function curried(...args) {
// 关键知识点:function.length 用来获取函数的形参个数
// 补充:arguments.length 获取的是实参个数
if (args.length >= func.length) {
return func.apply(this, args)
}
return function (...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
// 测试
function sum (a, b, c) {
return a + b + c
}
const curriedSum = curry(sum)
console.log(curriedSum(1, 2, 3))
console.log(curriedSum(1)(2,3))
console.log(curriedSum(1)(2)(3))
反柯里化,使得this指针泛化
Function.prototype.unCurrying = function () {
var f = this;
return function () {
var a = arguments;
return f.apply(a[0], [].slice.call(a, 1));
};
};
var push = Array.prototype.push.unCurrying(),
obj = {};
push(obj, 'first', 'two');
console.log(obj); /*obj {0 : "first",1 : "two"}*/
(function(){
push(arguments,4);
console.log(arguments) //[1,2,3,4]
})(1,2,3)
# es5实现继承
首先我们得知道es6中class的super表示父类构造函数,super()相当于a.prototype.constructor.call(this)
class ac{
constructor(){
this.x = 1
}
cout(){
console.log(this.x);
}
}
class bc extends ac{
constructor(){
super();
this.c = 2
}
bd(){
super.cout();
}
}
var c = new bc();
console.log(c);
// 最终会生成以下对象
{
x: 1,
c: 2,
__proto__ : {
bd: ƒ bd()
constructor: class bc
__proto__ : {
cout: ƒ cout()
constructor: class ac
__proto__: {}
}
}
}
通过上面我们可以知道class的继承最终都是通过原型继承的d
function creat(params) {
let f = function() {};
f.prototype = params;
return new f();
}
function parent(xx) {
this.x = xx;
}
parent.prototype.myown = function() {};
function child(name, xx) {
parent.call(this, xx);
this.b = name;
}
child.prototype = creat(parent.prototype);
child.prototype.constructor = child;
child.prototype.sayAge = function () {
console.log(this.name)
}
let a = new child(1,2);
# instanceOf
function isInstanceOf(instance, klass) {
let proto = instance.__proto__
let prototype = klass.prototype
while (true) {
if (proto === null) return false
if (proto === prototype) return true
proto = proto.__proto__
}
}
# 异步的串行和并行实现
function asyncAdd(a, b, callback) {
setTimeout(function () {
callback(null, a + b);
}, 500);
}
// 解决方案
// 1. promisify
const promiseAdd = (a, b) => new Promise((resolve, reject) => {
asyncAdd(a, b, (err, res) => {
if (err) {
reject(err)
} else {
resolve(res)
}
})
})
// 2. 串行处理
async function serialSum(...args) {
return args.reduce((task, now) => task.then(res => promiseAdd(res, now)), Promise.resolve(0))
}
// 3. 并行处理
async function parallelSum(...args) {
if (args.length === 1) return args[0]
const tasks = []
for (let i = 0; i < args.length; i += 2) {
tasks.push(promiseAdd(args[i], args[i + 1] || 0))
}
const results = await Promise.all(tasks)
return parallelSum(...results)
}
// 测试
(async () => {
console.log('Running...');
const res1 = await serialSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
console.log(res1)
const res2 = await parallelSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
console.log(res2)
console.log('Done');
})()
# vue reactive
// Dep module
class Dep {
static stack = []
static target = null
deps = null
constructor() {
this.deps = new Set()
}
depend() {
if (Dep.target) {
this.deps.add(Dep.target)
}
}
notify() {
this.deps.forEach(w => w.update())
}
static pushTarget(t) {
if (this.target) {
this.stack.push(this.target)
}
this.target = t
}
static popTarget() {
this.target = this.stack.pop()
}
}
// reactive
function reactive(o) {
if (o && typeof o === 'object') {
Object.keys(o).forEach(k => {
defineReactive(o, k, o[k])
})
}
return o
}
function defineReactive(obj, k, val) {
let dep = new Dep()
Object.defineProperty(obj, k, {
get() {
dep.depend()
return val
},
set(newVal) {
val = newVal
dep.notify()
}
})
if (val && typeof val === 'object') {
reactive(val)
}
}
// watcher
class Watcher {
constructor(effect) {
this.effect = effect
this.update()
}
update() {
Dep.pushTarget(this)
this.value = this.effect()
Dep.popTarget()
return this.value
}
}
// 测试代码
const data = reactive({
msg: 'aaa'
})
new Watcher(() => {
console.log('===> effect', data.msg);
})
setTimeout(() => {
data.msg = 'hello'
}, 1000)
# 数组扁平化
// 方案 1
function recursionFlat(ary = []) {
const res = []
ary.forEach(item => {
if (Array.isArray(item)) {
res.push(...recursionFlat(item))
} else {
res.push(item)
}
})
return res
}
// 方案 2
function reduceFlat(ary = []) {
return ary.reduce((res, item) => res.concat(Array.isArray(item) ? reduceFlat(item) : item), [])
}
# 对象扁平化
function objectFlat(obj = {}) {
const res = {}
function flat(item, preKey = '') {
Object.entries(item).forEach(([key, val]) => {
const newKey = preKey ? `${preKey}.${key}` : key
if (val && typeof val === 'object') {
flat(val, newKey)
} else {
res[newKey] = val
}
})
}
flat(obj)
return res
}
# 图片懒加载
// <img src="default.png" data-src="https://xxxx/real.png">
function isVisible(el) {
const position = el.getBoundingClientRect()
const windowHeight = document.documentElement.clientHeight
// 顶部边缘可见
const topVisible = position.top > 0 && position.top < windowHeight;
// 底部边缘可见
const bottomVisible = position.bottom < windowHeight && position.bottom > 0;
return topVisible || bottomVisible;
}
function imageLazyLoad() {
const images = document.querySelectorAll('img')
for (let img of images) {
const realSrc = img.dataset.src
if (!realSrc) continue
if (isVisible(img)) {
img.src = realSrc
img.dataset.src = ''
}
}
}
// 测试
window.addEventListener('load', imageLazyLoad)
window.addEventListener('scroll', imageLazyLoad)
// or
window.addEventListener('scroll', throttle(imageLazyLoad, 1000))
# 大文件分段上传
var bytesPerPiece = 1024 * 1024; // 每个文件切片大小定为1MB .
var totalPieces;
//发送请求
function upload() {
var blob = document.getElementById("file").files[0];
var start = 0;
var end;
var index = 0;
var filesize = blob.size;
var filename = blob.name;
//计算文件切片总数
totalPieces = Math.ceil(filesize / bytesPerPiece);
while(start < filesize) {
end = start + bytesPerPiece;
if(end > filesize) {
end = filesize;
}
var chunk = blob.slice(start,end);//切割文件
var sliceIndex= blob.name + index;
var formData = new FormData();
formData.append("file", chunk, filename);
$.ajax({
url: 'http://localhost:9999/test.php',
type: 'POST',
cache: false,
data: formData,
processData: false,
contentType: false,
}).done(function(res){
// empty
}).fail(function(res) {
// empty
});
start = end;
index++;
}
}
# 模拟实现koa中间件
// 注意其中的compose函数,这个函数是实现中间件洋葱模型的关键
// 场景模拟
// 异步 promise 模拟
const delay = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 2000);
});
}
// 中间间模拟
const fn1 = async (ctx, next) => {
console.log(1);
await next();
console.log(2);
}
const fn2 = async (ctx, next) => {
console.log(3);
await delay();
await next();
console.log(4);
}
const fn3 = async (ctx, next) => {
console.log(5);
}
const middlewares = [fn1, fn2, fn3];
// compose 实现洋葱模型
const compose = (middlewares, ctx) => {
const dispatch = (i) => {
let fn = middlewares[i];
if(!fn){ return Promise.resolve() }
return Promise.resolve(fn(ctx, () => {
return dispatch(i+1);
}));
}
return dispatch(0);
}
compose(middlewares, 1);