防抖 debounce
官方说明:当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。
通俗解释:当持续触发事件时,如果在设定的时间范围内频繁触发某个事件,则每次都会清空之前的计时,重新从0开始计时,直到在设定的时间范围内,没有事件触发,才会执行事件处理函数。以最后一次触发为准。
function debounce (fn, time) {
var timer;
return function () {
var _this = this;
var args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout (function() {
fn.call(_this, ...args);
}, time);
}
}
// 测试例子
function handleTouchMove(str) {
console.log(str);
}
// 没有加防抖
document.addEventListener('touchmove', function(){ handleTouchMove(111) }, false);
// 加防抖
document.addEventListener('touchmove', debounce(function(){ handleTouchMove(222) }, 1000), false);
触发touchmove事件,运行结果如下:
节流 throttle
官方定义:规定在一个单位时间内,只能触发一次函数,如果这个单位时间内触发多次函数,只有一次生效。 解释:多次触发,以第一次触发为准,当持续触发事件时,保证在设置的时间范围内只调用一次事件处理函数。
function throttle (fn, time) {
var timer = null;
return function() {
var _this = this;
var args = arguments;
if (!timer) {
timer = setTimeout(function() {
fn.call(_this, ...args);
timer = null;
}, time);
}
}
}
// 测试例子
function handleTouchMove(str) {
console.log(str);
}
// 没有加节流
document.addEventListener('touchmove', function() { handleTouchMove(`111---${new Date().getTime()}`) }, false);
// 加节流
document.addEventListener('touchmove', throttle(function(){ handleTouchMove(`222---${new Date().getTime()}`) }, 1000), false);
触发touchmove事件,运行结果如下:
深拷贝, 不考虑symbol和循环引用的情况
// 方法一
function deepClone1 (obj) {
if (typeof obj !== 'object' || obj == null) {
return obj;
}
if (obj instanceof Date) {
return new Date(obj);
}
if (obj instanceof RegExp) {
return new RegExp(obj);
}
const res = Array.isArray(obj) ? [] : {};
Object.keys(obj).forEach(key => {
if(typeof obj[key] === 'object') {
res[key] = deepClone1(obj[key]);
} else {
res[key] = obj[key];
}
});
return res;
}
// 方法二
function deepClone2 (obj) {
if(typeof obj !== 'object' || obj == null) {
return obj
}
if(obj instanceof Date) {
return new Date(obj);
}
if(obj instanceof RegExp) {
return new RegExp(obj);
}
const res = Object.create(Object.getPrototypeOf(obj));
const keys = Object.getOwnPropertyNames(obj);
keys.forEach(key => {
const value = Object.getOwnPropertyDescriptor(obj, key);
Object.defineProperty(res, key, value);
})
return res;
}
// 测试例子
let obj = {
age: 18,
favoriteAnimal: ['长颈鹿', '天鹅'],
obj: {
name: 'kele',
age: 18
},
};
let cloneObj1 = deepClone1(obj);
console.log("cloneObj1:", cloneObj1);
let cloneObj2 = deepClone2(obj);
console.log("cloneObj2:", cloneObj2);
深拷贝, 考虑symbol和循环引用的情况
function deepClone3 (obj, map = new Map()) {
if (typeof obj !== 'object' || obj == null) {
return obj;
}
if (map.has(obj)) {
return map.get(obj);
}
const res = Array.isArray(obj) ? [] : {};
map.set(obj, res);
Reflect.ownKeys(obj).forEach(k => {
if (typeof obj[k] === 'object' && obj != null) {
res[k] = deepClone3(obj[k], map);
} else {
res[k] = obj[k];
}
})
return res;
}
// 测试例子
let objx ={};
objx.repeat = objx;
let objy = {
[Symbol('name')]: 'litokele',
gender: Symbol('male'),
age: 18,
favoriteAnime: ['xxx1', 'xxx2'],
obj: {
[Symbol('test')]: 'test',
name: 'kele',
age: 18
},
repeat: objx
}
let cloneObj3 = deepClone3(objy);
console.log(cloneObj3);
数组去重
// 方法一
function uniq1 (arr) {
return [...new Set(arr)];
}
// 方法二
function uniq2 (arr) {
return Array.from(new Set(arr));
}
// 方法三
function uniq3 (arr) {
return arr.reduce((prev, cur) => {
if (!prev.includes(cur)) {
prev.push(cur);
}
return prev;
}, [])
}
// 方法四
function uniq4 (arr) {
const res = [];
const map = {};
arr.forEach(item => {
if(!map[item]) {
res.push(item);
map[item] = true;
}
})
return res;
}
// 测试例子
const arr = [1,2,3,1,5,3,9,6,5];
uniq1(arr); // => [1, 2, 3, 5, 9, 6]
uniq2(arr); // => [1, 2, 3, 5, 9, 6]
uniq3(arr); // => [1, 2, 3, 5, 9, 6]
uniq4(arr); // => [1, 2, 3, 5, 9, 6]
数组排序
// sort
function sortArr1(arr) {
return arr.sort((a, b) => a - b)
}
// 冒泡排序
function sortArr2(arr) {
if ( !Array.isArray( arr ) || arr.length < 2 ) {
return arr;
}
const len = arr.length;
for (let i = 0; i < len - 1; i++) {
for (let j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
let temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr
}
// 快速排序
function sortArr3 ( arr ) {
if (arr.length <= 1) {
//如果数组长度⼩于等于1⽆需判断直接返回即可
return arr;
}
var pivotIndex = Math.floor(arr.length / 2); //取基准点
var pivot = arr.splice(pivotIndex, 1)[0]; //取基准点的值,splice(index,1)函数可以返回数组中被删除的那个数
var left = []; //存放⽐基准点⼩的数组
var right = []; //存放⽐基准点⼤的数组
for (var i = 0; i < arr.length; i++) {
//遍历数组,进⾏判断分配
if (arr[i] < pivot) {
left.push(arr[i]); //⽐基准点⼩的放在左边数组
} else {
right.push(arr[i]); //⽐基准点⼤的放在右边数组
}
}
//递归执⾏以上操作,对左右两个数组进⾏操作,直到数组长度为<=1;
return sortArr3(left).concat(pivot, sortArr3(right));
};
// 选择排序
function sortArr4 ( arr ) {
const len = arr.length
for ( let i = 0; i < len; i++ ) {
for ( let j = i + 1; j < len; j++ ){
if ( arr[i] > arr[j] ) {
let temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
}
}
return arr
}
// 测试例子
const arr = [5, 6, 100, 30, 50, 10, 1, 5, 3, 9, 10]
sortArr1(arr)
sortArr2(arr)
sortArr3(arr)
数组乱序
function confuseArr (arr) {
const res = [];
while(arr.length) {
res.push(arr.splice(Math.floor(Math.random() * (arr.length - 1)), 1)[0]);
}
return res;
}
// 测试例子
confuseArr([1,2,3,4,5,6,7]);
call函数实现
Function.prototype._call = function (context) {
if (typeof this !== 'function') {
throw new TypeError('not funciton')
}
const args = [...arguments].slice(1);
context = context || window;
context.fn = this;
const res = context.fn(...args);
delete context.fn
return res;
}
// 测试例子
var value = "this is a window value";
var foo = {
value: "this is a foo value"
};
function bar(...num) {
console.log(`${this.value} is ${num}`);
}
bar(); // => this is a window value is
bar._call(foo, 111); // => this is a foo value is 111
bar._call(null, 111, 222); // => this is a window value is 111,222
apply函数实现
Function.prototype._apply = function(context) {
if (typeof this !== 'function') {
throw new TypeError('not funciton')
}
context = context || window;
context.fn = this;
let res;
if(arguments[1]) {
res = context.fn(...arguments[1]);
} else {
res = context.fn();
}
delete context.fn;
return res;
}
// 测试例子
var value = "this is a window value";
var foo = {
value: "this is a foo value"
};
function bar(...num) {
console.log(`${this.value} is ${num}`);
}
bar(); // => this is a window value is
bar._apply(foo, [111]); // => this is a foo value is 111
bar._apply(null, [111, 222]); // => this is a window value is 111,222
bind函数实现
Function.prototype._bind = function() {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
const _this = this;
const context = [...arguments].shift();
const args = [...arguments].slice(1);
return function F() {
if (this instanceof F) {
return new _this(...args, ...arguments);
} else {
const arg = args.concat(...arguments)
return _this.call(context, ...arg);
}
}
}
// 测试例子
var value = "this is a window value";
var foo = {
value: "this is a foo value"
};
function bar(num) {
console.log(`${this.value} is ${num}`);
}
bar(222); // => this is a window value is 222
var bindFoo = bar._bind(foo, 111);
bindFoo(); // => this is a foo value is 111
实现一个自执行器 (实现async/await)
function _asyncToGenerator(fn) {
return function() {
var self = this,
args = arguments;
// 将返回值promise化
return new Promise(function(resolve, reject) {
// 获取迭代器实例
var gen = fn.apply(self, args);
// 执行下一步
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value);
}
// 抛出异常
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err);
}
// 第一次触发
_next(undefined);
});
};
}
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
// 迭代器完成,将返回值(return)保存起来
resolve(value);
} else {
// -- 这行代码就是精髓 --
// 将所有值promise化
// 比如 yield 1
// const a = Promise.resolve(1) a 是一个 promise
// const b = Promise.resolve(a) b 是一个 promise
// 可以做到统一 promise 输出
// 当 promise 执行完之后再执行下一步
// 递归调用 next 函数,直到 done == true
Promise.resolve(value).then(_next, _throw);
}
}
const asyncFunc = _asyncToGenerator(function* () {
console.log(1);
yield new Promise(resolve => {
setTimeout(() => {
resolve();
console.log('sleep 1s');
}, 1000);
});
console.log(2);
const a = yield Promise.resolve('a');
console.log(3);
const b = yield Promise.resolve('b');
const c = yield Promise.resolve('c');
return [a, b, c];
})
asyncFunc().then(res => {
console.log(res)
});
// 运行结果
// 1
// sleep 1s
// 2
// 3
// ["a", "b", "c"]
promise实现(包含all()、race()、finally()等方法实现)
参考:BAT前端经典面试问题:史上最最最详细的手写Promise教程
class Promise {
constructor(executor) {
// Promise存在三个状态(state)pending、fulfilled、rejected
// pending(等待态)为初始态,并可以转化为fulfilled(成功态)和rejected(失败态)
// 成功时,不可转为其他状态,且必须有一个不可改变的值(value)
// 失败时,不可转为其他状态,且必须有一个不可改变的原因(reason)
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach(fn=>fn());
}
};
let reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn=>fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
// Promise有一个叫做then的方法,里面有两个参数:onFulfilled,onRejected,成功有成功的值,失败有失败的原因
// onFulfilled,onRejected如果他们是函数,则必须分别在fulfilled,rejected后被调用,value或reason依次作为他们的第一个参数
then(onFulfilled,onRejected) {
// onFulfilled如果不是函数,就忽略onFulfilled,直接返回value
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// onRejected如果不是函数,就忽略onRejected,直接扔出错误
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
// 为了解决链式调用, 默认在第一个then里返回一个新的promise -> promise2, 将这个promise2返回的值传递到下一个then中
// 声明返回的promise2
const promise2 = new Promise((resolve, reject) => {
// 当我们在第一个then中return了一个参数(参数未知,需判断)。这个return出来的新的promise就是onFulfilled()或onRejected()的值
// onFulfilled()或onRejected()的值,即第一个then返回的值, 第一个then返回的值,叫做x,判断x的函数叫做resolvePromise
if (this.state === 'fulfilled') {
// onFulfilled或onRejected不能同步被调用,必须异步调用。我们就用setTimeout解决异步问题
setTimeout(() => {
try {
let x = onFulfilled(this.value);
// resolvePromise函数,处理自己return的promise和默认的promise2的关系
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'rejected') {
// onFulfilled或onRejected不能同步被调用,必须异步调用。我们就用setTimeout解决异步问题
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
};
});
// 返回promise,完成链式
return promise2;
}
catch(fn){
return this.then(null,fn);
}
}
// 首先,要看x是不是promise。
// 如果是promise,则取它的结果,作为新的promise2成功的结果
// 如果是普通值,直接作为promise2成功的结果
// 所以要比较x和promise2
// resolvePromise的参数有promise2(默认返回的promise)、x(我们自己return的对象)、resolve、reject
// resolve和reject是promise2的
// x 是普通值 直接resolve(x) x 是对象或者函数(包括promise),let then = x.then
function resolvePromise(promise2, x, resolve, reject) {
// 循环引用报错
if (x === promise2) {
// reject报错
return reject(new TypeError('Chaining cycle detected for promise'));
}
// 防止多次调用
let called;
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
// A+规定,声明then = x的then方法
let then = x.then;
if (typeof then === 'function') {
// 就让then执行 第一个参数是this 后面是成功的回调 和 失败的回调
then.call(x, y => {
if(called)return;
called = true;
// resolve的结果依旧是promise 那就继续解析
resolvePromise(promise2, y, resolve, reject);
}, err => {
if(called)return;
called = true;
reject(err);
})
} else {
resolve(x);
}
} catch (e) {
// 也属于失败
if(called)return;
called = true;
// 取then出错了那就不要在继续执行了
reject(e);
}
} else {
resolve(x);
}
}
// resolve方法
Promise.resolve = function(val){
return new Promise((resolve,reject)=>{
resolve(val)
});
}
// reject方法
Promise.reject = function(val){
return new Promise((resolve,reject)=>{
reject(val)
});
}
// race方法
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(resolve,reject)
};
})
}
// all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
Promise.all = function(promises){
let arr = [];
let i = 0;
function processData(index,data){
arr[index] = data;
i++;
if(i == promises.length){
resolve(arr);
};
};
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(data=>{
processData(i,data);
},reject);
};
});
}
// finally方法
Promise.prototype.finally = function(cb) {
let P = this.constructor;
return this.then(value => {
P.resolve(cb()).then(() => value);
}, reason => {
P.resolve(cb()).then(() => {throw reason})
})
}
// retry方法
Promise.retry = function(fn, times, delay) {
return new Promise((resolve, reject) => {
var error;
var tryFun = function() {
if (times === 0) {
reject(error);
}
fn().then(res => {
resolve(res);
}).catch(e => {
times--;
error = e;
setTimeout(() => {
tryFun();
}, delay);
})
};
tryFun();
})
}
写一个函数,可以控制最大并发数
class requestQueue {
constructor(max) {
this.taskQueue = [];
this.max = max || 10;
setTimeout(() => {
this.next();
}, 0)
}
addTask(task) {
this.taskQueue.push(task);
}
next() {
const len = this.taskQueue.length;
if(!len) {
return;
}
const min = Math.min(len, this.max);
for (let i = 0 ; i < min; i++) {
this.max --;
var task = this.taskQueue.shift();
task().then(res => {
console.log(res);
}).catch((err) => {
console.log(err);
}).finally(() => {
this.max ++;
this.next();
})
}
}
}
// 测试例子
const request = new requestQueue();
for(let i = 0; i < 20; i++) {
request.addTask(function() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(i);
}, 2000)
})
})
}
jsonp 的实现
// 动态的加载js文件
function addScript(src) {
const script = document.createElement('script');
script.src = src;
script.type = "text/javascript";
document.body.appendChild(script);
}
addScript("http://xxx.xxx.com/xxx.js?callback=handleRes");
// 设置一个全局的callback函数来接收回调结果
function handleRes(res) {
console.log(res);
}
// 接口返回的数据格式
handleRes({a: 1, b: 2});
简单实现 EventEmitter
发布者和订阅者是松耦合的,互不关心对方,他们关注的对象是事件本身。
发布者通过事件调度中心提供publish方法进行事件发布操作,他们并不关心有没有人订阅。
订阅者通过事件调度中心提供subscribe方法进行事件订阅操作,他们并不关心有没有人发布事件,但是只要是自己订阅的事件发生了,他们就作出响应。
class EventEmitter {
constructor () {
this.events = {};
}
// 订阅事件
on (type, cb) {
if (!this.events[type]) {
this.events[type] = [];
}
this.events[type].push(cb);
}
// 发布事件
emit(type, ...args) {
if (this.events[type]) {
this.events[type].forEach(cb => {
cb(...args);
});
}
}
// 只执行一次
once(type, cb) {
const _this = this;
function one() {
cb.call(_this, arguments);
_this.off(type, one);
}
this.on(type, one);
}
// 移除事件
off(type, cb) {
if (this.events[type]) {
this.events[type] = this.events[type].filter(item => item != cb );
}
}
}
// 测试例子
var myEmitter = new EventEmitter();
myEmitter.on('study', function(data) {
console.log(`学习${data}`);
});
myEmitter.on('eat', function(data) {
console.log(`吃${data}`);
});
myEmitter.once('relax', function() {
console.log('relax');
})
myEmitter.emit('study', 'javascript'); // => 学习javascript
myEmitter.emit('eat', '苹果'); // => 吃苹果
myEmitter.emit('relax'); // => relax
myEmitter.emit('relax'); // => undefined
简单实现观察者模式
观察者的Observer和目标对象Subject耦合度相对较高,没有事件调度中心作为中间者,目标对象Subject和观察者Observer都要实现约定的成员方法。
观察者需要实现update方法,在目标对象通知更新时被调用。
目标对象需要维护观察者列表,在自身状态改变时,通过notify方法遍历观察者列表,通知所有观察者,目标对象的主动性很强,自己收集和维护观察者,并在状态变化时主动通知观察者更新
class Observer {
constructor(cb) {
if(!cb || typeof cb !== 'function') {
throw new Error('Observer构造器必须传入函数类型');
return;
}
this.cb = cb;
}
// 被目标对象通知时执行
update() {
this.cb();
}
}
class Subject {
// 维护观察者列表
constructor(observer) {
this.observerList = [];
}
// 添加一个观察者
addObserver(observer) {
this.observerList.push(observer);
}
// 通知所有观察者
notify() {
this.observerList.forEach(observer => {
observer.update();
})
}
}
// 测试例子
const cbFun = function() {
console.log('update');
}
const observer = new Observer(cbFun);
const subject = new Subject();
subject.addObserver(observer);
subject.notify(); // update
实现 instanceof
function _instanceof (obj, cunstructor) {
while (true) {
if (obj.__proto__ == null) {
return false;
}
if (obj.__proto__ === cunstructor.prototype) {
return true;
}
obj = obj.__proto__;
}
}
// 测试例子
_instanceof({a: 1, b:2}, Array) // => false
_instanceof({a: 1, b:2}, Object) // => true
实现 new 操作符
// 方法一
function _new1(fn, ...args) {
const obj = new Object();
obj.__proto__ = fn.prototype;
const res = fn.call(obj, ...args);
if ((typeof res === 'object' || typeof res === 'function') && res != null) {
return res;
}
return obj;
}
// 方法二
function _new2(fn, ...args) {
const obj = Object.create(fn.prototype);
const res = fn.call(obj, ...args);
if ((typeof res === 'object' || typeof res === 'function') && res != null) {
return res;
}
return obj;
}
// 测试例子
function A(a, b) {
this.a = a;
this.b = b;
}
const a = _new1(A, 1, 2);
console.log(a); // => {a: 1, b: 2}
const b = _new2(A, 2, 3);
console.log(b); // => {a: 2, b: 3}
实现Object.create
function createObject(o) {
function F() {};
F.prototype = o;
return new F();
}
实现数组的flat方法
function _flat(arr, depth) {
if(!Array.isArray(arr) || depth <= 0) {
return arr;
}
return arr.reduce((prev, cur) => {
if (Array.isArray(cur)) {
return prev.concat(_flat(cur, depth - 1))
} else {
return prev.concat(cur);
}
}, []);
}
// 测试例子
_flat([1,2,3,[4,5,[6,7]]], 1); // => [1, 2, 3, 4, 5, [6, 7]]
_flat([1,2,3,[4,5,[6,7]]], 2); // => [1, 2, 3, 4, 5, 6, 7]
实现数组的filter方法
Array.prototype._filter = function(fn) {
if (typeof fn !== "function") {
throw Error('参数必须是一个函数');
}
const res = [];
for (let i = 0, len = this.length; i < len; i++) {
fn(this[i]) && res.push(this[i]);
}
return res;
}
// 测试例子
const arr = [1,2,3,4,5];
arr._filter(item => item > 3);
实现数组的map方法
Array.prototype._map = function(fn) {
if (typeof fn !== "function") {
throw Error('参数必须是一个函数');
}
const res = [];
for (let i = 0, len = this.length; i < len; i++) {
res.push(fn(this[i]));
}
return res;
}
// 测试例子
const arr = [1, 2, 3, 4, 5];
arr._map(function(item) {
return item * 2;
});
// [2, 4, 6, 8, 10]
实现柯理化函数currying
function currying(fn, len) {
len = len || fn.length;
return function curry() {
const args = [...arguments];
var _this = this;
if (args.length < len) {
return function () {
return curry.apply(_this, args.concat([...arguments]));
};
} else {
return fn.apply(_this, args);
}
};
}
// 测试例子
function add(x, y, z) {
return x + y + z;
}
const curry = currying(add);
curry(2)(3)(4);
URL参数字典实现
function getURLParams (href = window.location.href) {
const result = {};
let param = null;
const reg = /[?&](.*?)=([^&#]*)/g;
param = reg.exec(href);
while (param) {
try {
result[param[1]] = decodeURIComponent(param[2]);
} catch (e) {
try {
result[param[1]] = unescape(param[2]);
} catch (escapeErr) {
result[param[1]] = param[2];
}
}
param = reg.exec(href);
}
return result;
}
getURLParams('https://www.xx.cn/api?keyword=&level1=&local_batch_id=&elective=&local_province_id=33');
// { keyword: "", level1: "", local_batch_id: "", elective: "", local_province_id: "33" }
getURLParams('https://www.xx.cn/api?keyword=&level1=&local_batch_id=&elective=800&local_province_id=33');
// { keyword: "", level1: "", local_batch_id: "", elective: "800", local_province_id: "33" }
getURLParams('https://www.xx.cn/api?keyword=&level1=&local_batch_id=&elective=800,700&local_province_id=33');
// { keyword: "", level1: "", local_batch_id: "", elective: "800,700", local_province_id: "33" }
使用setTimeout模拟setInterval
function _setInterval(fn, time) {
function _inner() {
fn();
setTimeout(_inner, time);
}
_inner();
}
// 测试例子
_setInterval(function() {
console.log(1);
}, 1000);
实现ajax
function _ajax (url, method = "GET", data = null) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.readyState === 4 && xhr.status === 200) {
success(JSON.parse(xhr.responseTxt));
} else {
fail(xhr.responseTxt)
}
}
xhr.open(method, url, true);
if(method.toLowerCase() == 'post') {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencode');
}
xhr.send(data);
}
function success(data) {
console.log(data);
}
function fail(err) {
console.log(err);
}
使用promise对象实现ajax
function _ajax (url, method = "GET", data = null) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onreadystatechange = function () {
if (xhr.readystate === 4) {
if (xhr.status === 200 || xhr.status === 304) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(xhr.response);
}
}
};
xhr.send(data);
});
}
实现sleep函数
// 方法一 - promise
function sleep(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time)
})
}
// 测试例子
sleep(1000).then(() => {
console.log(1);
})
// 方法二 - Generator
function * sleep(time) {
yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time);
})
}
// 测试例子
sleep(1000).next().value.then(() => {
console.log(2);
});
// 方法三 - async/await
function sleep(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time)
})
}
async function sleepDoSomething(time) {
let res = await sleep(time);
console.log(3);
return res;
}
// 测试例子
sleepDoSomething(1000);
手动实现vue双向数据绑定
const Vue = (function() {
let uid = 0;
// 用于储存订阅者并发布消息
class Dep {
constructor() {
// 设置id,用于区分新Watcher和只改变属性值后新产生的Watcher
this.id = uid++;
// 储存订阅者的数组
this.subs = [];
}
// 触发target上的Watcher中的addDep方法,参数为dep的实例本身
depend() {
Dep.target.addDep(this);
}
// 添加订阅者
addSub(sub) {
this.subs.push(sub);
}
notify() {
// 通知所有的订阅者(Watcher),触发订阅者的相应逻辑处理
this.subs.forEach(sub => {
sub.update();
})
}
}
// 为Dep类设置一个静态属性,默认为null,工作时指向当前的Watcher
Dep.target = null;
// 监听者,监听对象属性值的变化
class Observer {
constructor(value) {
this.value = value;
this.walk(value);
}
// 遍历属性值并监听
walk(value) {
Object.keys(value).forEach(key => this.convert(key, value[key]));
}
// 执行监听的具体方法
convert(key, val) {
defineReactive(this.value, key, val);
}
}
function defineReactive(obj, key, val) {
const dep = new Dep();
// 给当前属性的值添加监听
let chlidOb = observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {
// 如果Dep类存在target属性,将其添加到dep实例的subs数组中
// target指向一个Watcher实例,每个Watcher都是一个订阅者
// Watcher实例在实例化过程中,会读取data中的某个属性,从而触发当前get方法
if (Dep.target) {
dep.depend();
}
return val;
},
set: newVal => {
if (val === newVal) return;
val = newVal;
// 对新值进行监听
chlidOb = observe(newVal);
// 通知所有订阅者,数值被改变了
dep.notify();
}
})
}
function observe(value) {
// 当值不存在,或者不是复杂数据类型时,不再需要继续深入监听
if (!value || typeof value !== 'object') {
return;
}
return new Observer(value);
}
class Watcher {
constructor(vm, expOrFn, cb) {
this.depIds = {}; // hash储存订阅者的id,避免重复的订阅者
this.vm = vm; // 被订阅的数据一定来自于当前Vue实例
this.cb = cb; // 当数据更新时想要做的事情
this.expOrFn = expOrFn; // 被订阅的数据
this.val = this.get(); // 维护更新之前的数据
}
// 对外暴露的接口,用于在订阅的数据被更新时,由订阅者管理员(Dep)调用
update() {
this.run();
}
addDep(dep) {
// 如果在depIds的hash中没有当前的id,可以判断是新Watcher,因此可以添加到dep的数组中储存
// 此判断是避免同id的Watcher被多次储存
if (!this.depIds.hasOwnProperty(dep.id)) {
dep.addSub(this);
this.depIds[dep.id] = dep;
}
}
run() {
const val = this.get();
console.log(val);
if (val !== this.val) {
this.val = val;
this.cb.call(this.vm, val);
}
}
get() {
// 当前订阅者(Watcher)读取被订阅数据的最新更新后的值时,通知订阅者管理员收集当前订阅者
Dep.target = this;
const val = this.vm._data[this.expOrFn];
// 置空,用于下一个Watcher使用
Dep.target = null;
console.log(Dep.target, 2);
return val;
}
}
class Vue {
constructor(options = {}) {
// 简化了$options的处理
this.$options = options;
// 简化了对data的处理
let data = (this._data = this.$options.data);
// 将所有data最外层属性代理到Vue实例上
Object.keys(data).forEach(key => this._proxy(key));
// 监听数据
observe(data);
}
// 对外暴露调用订阅者的接口,内部主要在指令中使用订阅者
$watch(expOrFn, cb) {
new Watcher(this, expOrFn, cb);
}
_proxy(key) {
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
get: () => this._data[key],
set: val => {
this._data[key] = val;
},
});
}
}
return Vue;
})();
// 测试例子
let demo = new Vue({
data: {
text: '111',
},
});
const domp = document.createElement('p');
domp.setAttribute('id', 'p');
const domInput = document.createElement('input');
domInput.type = 'text';
domInput.value = demo.data;
domInput.setAttribute('id', 'input');
document.body.appendChild(domp);
document.body.appendChild(domInput);
const p = document.getElementById('p');
const input = document.getElementById('input');
input.addEventListener('keyup', function(e) {
demo.text = e.target.value;
});
demo.$watch('text', str => p.innerHTML = str);
使用 JavaScript Proxy 实现简单的数据绑定
const domp = document.createElement('p');
domp.setAttribute('id', 'p');
const domInput = document.createElement('input');
domInput.setAttribute('type', 'text');
domInput.setAttribute('id', 'input');
document.body.appendChild(domp);
document.body.appendChild(domInput);
const p = document.getElementById('p');
const input = document.getElementById('input');
const inputObj = {
inputTxt: 5
};
const proxy = new Proxy(inputObj, {
get: function(target, key, receiver) {
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
if (key === 'inputTxt') {
input.value = value;
p.innerHTML = value;
}
return Reflect.set(target, key, value, receiver);
}
});
// 显示初始化的值
input.value = proxy.inputTxt;
p.innerHTML = proxy.inputTxt;
// 根据输入框的值变化
input.addEventListener('input', function(e) {
proxy.inputTxt = e.target.value;
})
将一个同步callback包装成promise形式
function promisify(original) {
return function (...args) {
return new Promise((resolve, reject) => {
args.push(function callback(err, ...values) {
if (err) {
return reject(err);
}
return resolve(...values)
});
original.call(this, ...args);
});
};
}
将下划线命名转换为驼峰命名
// 例如:将 "wo_ai_xue_qian_duan" 转换为 "woAiXueQianDuan"
const str = "wo_ai_xue_qian_duan";
// 方法一
str.replace(/_./g, function(i) {
console.log(i);
return i.slice(1).toUpperCase();
});
// 输出:"woAiXueQianDuan"
// 方法二
function formatStr(str) {
const strArr = str.split('_');
let res;
strArr.forEach((item, index) => {
if (index === 0) {
res = item
} else {
res += item[0].toUpperCase() + item.slice(1);
}
});
return res;
}
formatStr(str);
// 方法三
function formatStr2(str) {
return str.split('_').reduce((prev, cur) => {
return prev += cur[0].toUpperCase() + cur.slice(1);
});
}
formatStr2(str);
把 entry 转换成如下对象 var entry = { 'a.b.c.dd': 'abcdd', 'a.d.xx': 'adxx', 'a.e': 'ae' }; 要求转换成如下对象 var output = { a: { b: { c: { dd: 'abcdd' } }, d: { xx: 'adxx' }, e: 'ae' } }
function transformToJson(obj) {
const res = {};
const keys = Object.keys(obj);
for(let k of keys) {
const keyArr = k.split('.');
keyArr.reduce((prev, cur, index, arr) => {
if(index === arr.length - 1) {
prev[cur] = obj[k];
return;
}
prev[cur] = prev[cur] || {};
return prev[cur];
}, res);
} return res;
}
// 测试例子
const entry = { 'a.b.c.dd': 'abcdd', 'a.d.xx': 'adxx', 'a.e': 'ae' };
transformToJson(entry);
// { a: { b: { c: { dd: "abcdd" } }, d: { xx: "adxx" }, e: "ae" } }
上一题的反向操作,把 entry 转换成如下对象 var entry = { a: { b: { c: { dd: 'abcdd' } }, d: { xx: 'adxx' }, e: 'ae' } } 要求转换成如下对象 var output = { 'a.b.c.dd': 'abcdd', 'a.d.xx': 'adxx', 'a.e': 'ae' };
const transformFun = function (obj, tmpKey = '', res = {}, nums = 0) {
for (let k in obj) {
const item = obj[k];
if (typeof item === 'object' && item !== null && Object.keys(item).length > 0) {
if (nums !== 0) {
transformFun(item, tmpKey + '.' + k, res, nums + 1);
} else {
transformFun(item, k, res, nums + 1);
}
} else {
if (nums !== 0) {
res[tmpKey + '.' + k] = item;
} else {
res[k] = item;
}
}
}
return res;
};
var entry = { a: { b: { c: { dd: 'abcdd' } }, d: { xx: 'adxx' }, e: 'ae' } }
transformFun(entry)
/* {
"a.b.c.dd": "abcdd",
"a.d.xx": "adxx",
"a.e": "ae",
"f": "xxxxx",
"g": {}
}
*/
如有问题,请指正,谢谢!