# 手写防抖函数
function debounce(fn, delay) {
let timer = null;
return function() {
const context = this;
const args = arguments;
if (timer) clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(context, args);
}, delay);
};
}
# 手写节流函数
function throttle(fn, interval) {
let last = 0;
return function() {
const now = Date.now();
const context = this;
const args = arguments;
if (now - last >= interval) {
fn.apply(context, args);
}
};
}
# 手写加强版节流
防抖有时触发的太频繁,可能会迟迟没有响应,我们需要将防抖和节流组合下避免这种问题:
function throttle(fn, delay) {
let last = 0, timer = null;
return function() {
const context = this;
const args = arguments;
const now = Date.now();
if (now - last < delay) {
clearTimeout(timer);
timer = setTimeout(function() {
last = now;
fn.apply(context, args);
}, delay);
}else {
last = now;
fn.apply(context, args);
}
}
}
# 手写懒加载
let num = 0;
const imgs = document.getElementsByTagName('img');
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
function lazyload() {
for (let i = num, len = imgs.length; i < len; i++) {
const distance = viewHeight - imgs[i].getBoundingClientRect().top
if (distance >=0 ) {
imgs[i].src = imgs[i].getAttribute('data-src');
num = i + 1;
}
}
}
function throttle(fn, interval) {
let last = 0;
return function() {
const context = this;
const args = arguments;
const now = Date.now();
if (now - last >= interval) {
fn.apply(context, args);
}
}
}
window.addEventListener('scroll', throttle(lazyload, 300), false);
# 手写jsonp
# 手写ajax
export default function ajax({ url = '', method = 'GET', data = {}, success = null, fail = null }) {
const xhr = new XMLHttpRequest();
const isGet = /^(GET|DELETE|HEAD|OPTIONS)$/i.test(method);
if (isGet) {
url = url + '?' + toQuery(data);
data = null;
} else {
data = JSON.stringify(data);
}
xhr.open(method, url);
xhr.onreadystatechange = function() {
if (/^2\d{2}$/.test(xhr.status) && xhr.readyState === 4) {
typeof success === 'function' ? success(xhr.responseText) : '';
}else {
typeof fail === 'function' ? fail(xhr.responseText) : ''
}
};
xhr.send(data);
}
function toQuery(obj) {
let str = '';
for (let key in obj) {
str += '&' + key + '=' + obj[key];
}
return str.slice(1);
}
# 你认为ajax的意义是什么?
局部刷新
# ajax、$.ajax、axios、fetch对比?
ajax是基于XMLHttpRequest的
$.ajax是对ajax的封装,基于回调函数
axios是基于promise对ajax的封装
Fetch是es6中新增的通信方案,它不是ajax,它本身实现数据通信就是基于promise管理的。
# 手写call函数
es5
~function(proto) {
proto.myCall = function(context) {
var args = [].splice.call(arguments, 1);
context = Object(context) || window;
context.fn = this;
var res = eval('context.fn(' + args + ')');
delete context.fn;
return res;
};
}(Function.prototype);
es6
(function(proto) {
proto.myCall = function(context = Object(context) || window, ...args) {
context.$fn = this
const res = context.$fn(...args)
delete context.$fn
return res
}
})(Function.prototype)
# 手写apply函数
es5
~function(proto) {
proto.myApply = function(context) {
var args = [].splice.call(arguments, 1);
context = Object(context) || window;
context.fn = this;
var res = eval('context.fn(' + args + ')');
delete context.fn;
return res;
};
}(Function.prototype);
es6
(function(proto) {
proto.myApply = function(context = Object(context) || window, ...args) {
context.$fn = this
const res = context.$fn(...args)
delete context.$fn()
return res
}
})(Function.prototype)
# 手写bind函数
方法一:
(function(proto){
function bind(context) {
if (!context) context = window
var args = [].slice.call(arguments, 1)
var _this = this
return function() {
var arg = [].slice.call(arguments, 0)
_this.apply(context, args.conca t(arg))
}
}
proto.myBind = bind
})(Function.prototype)
方法二:
(function(proto){
function bind(context = window, ...args) {
return (...innerArgs) => {
this.call(context, ...args, ...innerArgs)
}
}
proto.myBind = bind
})(Function.prototype)
# 手写new
function new1(func) {
const o = Object.create(func.prototype);
const obj = func.call(this);
return typeof obj === 'function' ? obj : o;
}
# 手写instanceof
# 手写拷贝
数组浅拷贝:
let arr = [1, 2, 3];
let arr1 = [...arr];
let arr2 = arr.concat([]);
let arr3 = arr.slice();
对象浅拷贝:
let obj = {
0: 'math',
score: {
english: 90
},
reg: /1/,
time: new Date,
friends: ['tom'],
say: function () {},
tag: Symbol('tag'),
[Symbol.toStringTag]: 'object'
}
let obj1 = {
...obj
}
let obj2 = Object.assign({}, obj)
// 上面两种包含了原始对象的Symbol属性的处理
// 不支持对 Symbol 属性的处理
let objCopy = {}
for (let key in obj) {
objCopy[key] = obj[key]
}
极简版的浅克隆(处理的情况比较少):
function shallowClone(obj) {
if (typeof obj !== 'object' || typeof obj === null) return obj;
return Array.isArray(obj) ? [...obj] : {...obj};
}
相对完善的浅克隆:
['Boolean', 'Number', 'String', 'Function', 'Array', 'Date', 'RegExp', 'Object', 'Error', 'Symbol', 'BigInt']
.forEach(name => {
class2type[`[object ${name}]`] = name.toLowerCase();
})
const toType = function (obj) {
if (obj == null) return obj + '';
return typeof obj === 'object' || typeof obj === 'function' ? class2type[toString.call(obj)] || 'object' :
typeof obj;
}
function shallowClone(obj) {
const type = toType(obj), Ctor = obj.constructor;
// Symbol/BigInt
if (/^(symbol|bigint)$/i.test(type)) return Object(obj);
// 正则和日期
if (/^(regexp|date)$/i.test(type)) return new Ctor(obj);
// 错误对象的处理
if (/^error$/i.test(type)) return new Ctor(obj.message);
// 函数
if (/^function$/i.test(type)) return function() {
return obj.call(obj, ...arguments)
};
// 数组对象
if (/^(object|array)$/i.test(type)) {
let keys = [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)]
const res = new Ctor();
keys.forEach(key => {
res[key] = obj[key]
})
return res;
}
return obj;
}
简版的深克隆:
function isObject (obj) {
return (typeof obj === 'object') && obj !== null
}
function deepClone (obj) {
if (!isObject(obj)) {
return obj
}
const newObj = Array.isArray(obj) ? [] : {}
Reflect.ownKeys(obj).forEach(key => {
const value = obj[key]
newObj[key] = isObject(value) ? deepClone(value) : value
})
return newObj
}
相对完善些的深克隆:
function deepClone(obj, cache = new Set()) {
const type = toType(obj),
Ctor = obj.constructor;
if (!/^(object|array)$/i.test(type)) return shallowClone(obj);
// 避免套娃
if(cache.has(obj)) return obj;
cache.add(obj)
let res = new Ctor();
Reflect.ownKeys(obj).forEach(key => {
res[key] = deepClone(obj[key], cache)
})
return res;
}
# 实现柯里化函数
# 手写一个观察者模式
# 手动实现EventEmitter(发布订阅模式)
# 手动实现 Object.assign
# js格式化数字(每三位加逗号)
# 手写数组去重的方法?
# 基于JS实现Ajax请求并发请求的控制
在平时开发中,我们都是通过promise对ajax请求进行封装。这里我们先说一下,通过promise来实现串行和并行操作。
- 串行:请求是异步的,需要等到上一个请求成功才能执行下一个请求。
- 并行:同时发送多个请求,注意HTTP请求可以同时进行。但是因为js是单线程的,所以js的处理操作是一步一步来的。
# 串行
const delay = function(interval) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(interval)
}, interval)
})
}
delay(1000).then(res=> {
console.log(res)
return delay(2000)
}).then(res=> {
console.log(res)
return delay(3000)
}).then(res=> {
console.log(res)
return delay(4000)
}).then(res => {
console.log(res)
})
串行会从上到下依次执行。
# 并行
等到所有请求都成功后我们再做某些事情,我们可以使用promise.all来进行并行请求
const delay = function(interval) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(interval)
}, interval)
})
}
const fetchRequest = () => delay(1000)
const tasks = [
() => delay(1000),
() => delay(2000),
() => delay(3000),
() => delay(4000)
]
Promise.all(tasks.map(task => task())).then(res => {
console.log(res) // 四秒后输出 [1000, 2000, 3000, 4000]
}).catch(err => {
console.log(err)
})
如果tasks里面的请求由有很多,在瞬间需要发出这么多的请求可能会带来一些问题。这个时候我们需要使用promise.all做并发限制。
方法一: 方法二:
function delay(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(ms)
}, ms);
})
}
const tasks = [
() => delay(4000),
() => delay(3000),
() => delay(2000),
() => delay(1000)
]
function createRequest(tasks, pool, cb) {
if (pool && typeof pool === 'function') {
pool = 5
cb = pool
} else {
pool = pool || 5
}
if (typeof cb !== 'function') cb = function() {}
class TaskQueue {
running = 0
queue = []
results = []
pushTask(task) {
this.queue.push(task)
this.next()
}
next() {
while(this.running < pool && this.queue.length) {
this.running++
const task = this.queue.shift()
task().then(result => {
this.results.push(result)
}).finally(() => {
this.running--
this.next()
})
}
if (!this.running) cb(this.results)
}
}
let TQ = new TaskQueue
tasks.forEach(task => TQ.pushTask(task))
}
createRequest(tasks, 2, (res) => {
console.log(res) // [3000, 4000, 1000, 2000]
})
从运行结果可以看出,使用上述方法实现的方式是乱序的。。
# 手写merge合并
const options = {
url: '',
method: 'get',
headers: {
'Content-type': 'application/json'
},
data: null,
arr: [1, 2],
config: {
xhr: {
async: true,
cache: true
}
}
}
const params = {
url: 'www.baidu.com',
headers: {
'token': '123'
},
data: {
name: 'f'
},
arr: [3, 4],
config: {
xhr: {
cache: true
}
}
}
function isObject(obj) {
return typeof obj === 'object' && obj !== null
}
function merge(options, params) {
for (let key in params) {
const isA = isObject(options[key]),
isB = isObject(params[key]);
if (isA && !isB) {
throw new TypeError(`${key} in params must be object`)
}
if (isA && isB) {
options[key] = merge(options[key], params[key])
}else {
options[key] = params[key]
}
}
return options
}
# 手写AOP切片编程
APO(面向切片编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来后再通过动态植入的方式参入业务逻辑模块中。
例子:
Function.prototype.before = function (cb) {
if (typeof cb !== 'function') throw new TypeError('cb must be function')
const self = this;
return function () {
cb.call(this, ...arguments);
return self.call(this, ...arguments);
}
}
Function.prototype.after = function (cb) {
if (typeof cb !== 'function') throw new TypeError('cb must be function')
const self = this;
return function () {
const res = self.call(this, ...arguments);
cb.call(this, ...arguments);
return res;
}
}
const func = () => {
console.log('func')
}
func.before(() => {
console.log('before')
}).after(() => {
console.log('after')
})();
# 手写getUrlParam
方法一:
function getUrlParam(name) {
let res = '';
const str = location.search
if (!str) return res;
const arr = str.split('?')[1].split('&')
arr.forEach(item => {
const arr2 = item.split('=')
if (arr2[0] === name) {
res = arr2[1]
}
})
return res
}
方法二:
function getUrlParam(name){
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');
var result = window.location.search.substr(1).match(reg);
return result ? decodeURIComponent(result[2]) : null;
}
← 问答题