blog
blog copied to clipboard
JS基础手写代码
JS基础手写代码
考查 this
call、apply
Function.prototype._call = function(ctx = window, ...args) {
const fnKey = 'tmp_' + Date.now();
ctx[fnKey] = this;
const result = ctx[fnKey](...args);
delete ctx[fnKey];
return result;
};
// 第二个参数是数组
Function.prototype._apply = function(ctx = window, args = []) {
const fnKey = 'tmp_' + Date.now();
ctx[fnKey] = this;
const result = ctx[fnKey](...args);
delete ctx[fnKey];
return result;
};
let obj = { x: 1 };
function fn() {
console.log(this.x, arguments);
}
fn._call(obj, 1, 2, 3);
fn._apply(obj, [1, 2, 3]);
bind
Function.prototype._bind = function() {
const slice = [].slice;
const _fn = this;
const ctx = arguments[0];
const args = slice.call(arguments, 1);
if (typeof _fn !== 'function') {
throw new TypeError('Function.prototype.bind - ' +
'what is trying to be bound is not callable');
}
return function() {
const funcArgs = args.concat(slice.call(arguments));
return _fn.apply(ctx, funcArgs);
};
};
const obj = { x: 1 };
function A() {
console.log(this.x);
}
const fn = A._bind(obj);
fn();
new
function newObject(fn) {
const obj = {};
const res = fn.apply(obj, [].slice.call(arguments, 1));
return res instanceof Object ? res : obj;
}
function Con(a) {
this.a = a;
}
const obj = newObject(Con, 1);
链式调用
Number.prototype.add = function(num) {
num = +num;
if(num !== num) return Number(this);
return Number(this + num);
};
let n = 1;
n.add(1).add(2).add(3);
考查原型链
instanceof
function instance_of(lhs, rhs) {
while (lhs) {
lhs = lhs.__proto__;
if (lhs === rhs.prototype) return true;
}
return false;
}
组合寄生继承
function Super(){}
function Sub() {
Super.call(this); // 继承自有属性
}
// 继承原型链上的属性和方法
Sub.prototype = Object.create(Super.prototype); // 继承原型链
Sub.prototype.constructor = Sub;
Object.create
if(!Object.create) {
Object.create = function(proto) {
function F(){}
F.prototype = proto;
return new F;
}
}
纯对象
- redux 版
function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false;
var proto = obj;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(obj) === proto;
}
function A() {}
isPlainObject( new A ); // false
isPlainObject( new Object ); // true
isPlainObject( {} ); // true
判断类型
- jQuery 3.4.1版
const type = 'Boolean Number String Function Array Date RegExp Object Error Symbol'
.split(' ')
.reduce((pre, name) => {
pre[`[object ${name}]`] = name.toLowerCase();
return pre;
}, {});
function toType( obj ) {
if ( obj == null ) {
return obj + '';
}
return typeof obj === 'object' || typeof obj === 'function' ?
type[ Object.prototype.toString.call( obj ) ] || 'object' :
typeof obj;
}
toType(/xxx/) // regexp
考查闭包
柯里化
function curry(fn, ...args) {
if(args.length >= fn.length) {
return fn.apply(null, args);
}
return (...args2) => curry(fn, ...args, ...args2);
}
const add = curry(function(a, b, c) {
return a + b + c;
});
add(1, 2, 3);
add(1)(2)(3);
add(1, 2)(3);
add(1)(2, 3);
考查性能意识
防抖
function debounce(fn, delay) {
let timer = null;
return function() {
// 中间态一律清除掉
timer && clearTimeout(timer);
// 只需要最终的状态,执行
timer = setTimeout(() => fn.apply(this, arguments), delay);
};
}
节流
function throttle(fn, delay) {
let timer, lastTime;
return function() {
const now = Date.now();
const space = now - lastTime; // 间隔时间
if( lastTime && space < delay ) { // 为了响应用户最后一次操作
// 非首次,还未到时间,清除掉计时器,重新计时。
timer && clearTimeout(timer);
// 重新设置定时器
timer = setTimeout(() => {
lastTime = Date.now(); // 不要忘了记录时间
fn.apply(this, arguments);
}, delay - space);
return;
}
// 首次或到时间了
lastTime = now;
fn.apply(this, arguments);
timer && clearTimeout(timer);
};
}
事件代理
function delegate(ele, selector, type, fn) {
function callback(e) {
e = e || window.event;
const target = e.target || e.srcElement;
let selectors = ele.querySelectorAll(selector);
selectors = [].slice.call(selectors);
if( selectors.includes(target) ) {
fn.call(target, e);
}
}
ele.addEventListener(type, callback, false);
}
delegate(document.querySelector('body'), 'button', 'click', function () {
console.log('bingo');
});
限制并发数
function sendRequest(urls, max, callback) {
const last = performance.now();
const len = urls.length;
let limit = max; // 控制并发数
let cnt = 0; // 累积执行任务数
let res = []; // 有序存储执行结果
const tasks = urls.map((url, index) => () => fetch(url)
.then(data => {
res[index] = data;
})
.catch(reason => {
res[index] = reason;
})
.finally(() => {
if( ++cnt === len ) return callback(res);
++limit;
doTasks();
}));
doTasks();
function doTasks() {
while( limit && tasks.length ) {
--limit;
const task = tasks.shift();
task();
console.log(`执行间隔:${performance.now() - last}`);
}
}
}
// 模拟 fetch
function fetch(url) {
return new Promise(function (resolve, reject) {
let good, bad;
const time = 3000;
good = setTimeout(function () {
clearTimeout(bad);
const data = `resolve: ${url}`;
resolve(data);
console.log(data);
}, Math.random() * time);
bad = setTimeout(function () {
clearTimeout(good);
const reason = `reject: ${url}`;
reject(reason);
console.log(reason);
}, Math.random() * time);
});
}
// 测试
sendRequest([1,2,3,4,5,6,7,8,9,10], 5, (res) => console.log('all done:' + res));
考查跨域
JSONP
function fn({ip}) {
console.log(ip); //
}
function jsonp(cb, domain) {
const script = document.createElement('script');
script.src = `https://api.asilu.com/ip/?callback=${cb}&ip=${domain}`;
document.querySelector('head').appendChild(script);
}
// 获取百度IP
jsonp('fn', 'www.baidu.com');
考查 ES6
数组去重
- Map 版
function deleteDuplicate(arr) {
const map = new Map();
arr.forEach( value => map.set(value, value) );
return Array.from( map.values() ); // return [ ...map.values() ];
}
const arr = [NaN, 1, [1], [1], 1, '1', 4, 1, 2, 4, 5, 5, NaN, NaN, null, null, undefined, undefined];
deleteDuplicate( arr );
// [NaN, 1, Array(1), Array(1), "1", 4, 2, 5, null, undefined]
// Map 的遍历顺序就是插入顺序
- Set 版
function deleteDuplicate(arr) {
const set = new Set( arr );
return Array.from( set ); // return [ ...set ];
}
deleteDuplicate( arr );
//[NaN, 1, Array(1), Array(1), "1", 4, 2, 5, null, undefined]
Promise.all
Promise._all = function (promises) {
return new Promise(function(resolve, reject) {
if (!Array.isArray(promises)) {
return reject(new TypeError('arguments must be an array'));
}
const len = promises.length;
let cnt = 0;
let res = [];
for(let i = 0; i < len; i++) {
Promise
.resolve(promises[i])
.then(function(value) {
cnt++;
res[i] = value;
if (cnt === len) {
return resolve(res);
}
}, function(reason) {
return reject(reason);
});
}
});
};
Promise._all([1,2,3,4]);
// Promise {<resolved>: Array(4)}
Promise._all([1,2,3,4]).then(res => console.log(res));
// [1, 2, 3, 4]
Promise._all([1,2,3,Promise.reject('error')]);
// Promise {<rejected>: "error"}
Promise._all([1,2,3,Promise.reject('error')]).catch(reason => console.log(reason));
// error
考查设计模式
订阅发布模式
mitt:极简事件监听
function mitt(all/*: EventHandlerMap*/) {
all = all || Object.create(null);
return {
on(type/*: string*/, handler/*: EventHandler*/) {
(all[type] || (all[type] = [])).push(handler);
},
off(type/*: string*/, handler/*: EventHandler*/) {
if (all[type]) {
all[type].splice(all[type].indexOf(handler) >>> 0, 1);
}
},
emit(type/*: string*/, evt/*: any*/) { // * 表示订阅所有事件消息
(all[type] || []).slice().map((handler) => { handler(evt); });
(all['*'] || []).slice().map((handler) => { handler(type, evt); });
}
};
}
const m = mitt();
m.on('hello', (name) => console.log('hello ' + name));
m.emit('hello', 'world');
考查算法
深拷贝
/**
* Get the first item that pass the test
* by second argument function
*
* @param {Array} list
* @param {Function} f
* @return {*}
*/
export function find (list, f) {
return list.filter(f)[0]
}
/**
* Deep copy the given object considering circular structure.
* This function caches all nested objects and its copies.
* If it detects circular structure, use cached copy to avoid infinite loop.
*
* @param {*} obj
* @param {Array<Object>} cache
* @return {*}
*/
export function deepCopy (obj, cache = []) {
// just return if obj is immutable value
if (obj === null || typeof obj !== 'object') {
return obj
}
// if obj is hit, it is in circular structure
const hit = find(cache, c => c.original === obj)
if (hit) {
return hit.copy
}
const copy = Array.isArray(obj) ? [] : {}
// put the copy into cache at first
// because we want to refer it in recursive deepCopy
cache.push({
original: obj,
copy
})
Object.keys(obj).forEach(key => {
copy[key] = deepCopy(obj[key], cache)
})
return copy
}
DFS 全排列
输入 `abc`
输出
abc
acb
bac
bca
cab
cba
const str = 'abc';
const len = str.length;
const flag = [];
const res = [];
DFS(0);
// cur 表示第 cur 位取得字符
// 每一位有 len 种取法
function DFS(cur) {
if(cur === len)
return console.log(res.join(''));
for(let i = 0; i < len; i++) {
if(!flag[i]) {
res[cur] = str[i];
flag[i] = true;
DFS(cur + 1);
flag[i] = false;
}
}
}
快排
function swap(arr, i, j) {
if(i === j) return;
let tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
function quicksort(arr, s, e) {
if( s >= e ) return;
const r = s + Math.floor( (e - s) / 2 );
// 选取中间位r(也可以随机)放在首位
swap(arr, s, r);
// 找选取的数应该在数组中的位置
// 小于则表示位置挪后一位
let m = s, j = s + 1;
for(; m < e && j < e; j++) {
if(arr[j] < arr[s]) {
swap(arr, ++m, j);
}
}
// 找到 r 的序位
swap(arr, s, m);
// 递归排序左左右两边
quicksort(arr, s, m);
quicksort(arr, m + 1, e);
}
let arr = [2, 7, 3, 4, 1, 8, 6];
quicksort(arr, 0, arr.length);
考查正则、replace 技巧
数钱格式化
'99999999'.replace(/\d{1,3}(?=(\d{3})+$)/g, '$&,');
// antd 的例子
'99999999'.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
// 非正则版
function money(str) {
const len = str.length;
let start = len % 3 || 3;
let arr = [str.slice(0, start)];
while(start < len) {
arr.push( str.slice(start, start + 3) );
start += 3;
}
return arr.join(',')
}
首尾去空格
' 12 34 5 6 '.replace(/^\s+|\s+$/g, '');
相邻字符去重
'aaabbbcdfgghhjjkkk'.replace(/([A-Za-z]{1})(\1)+/g, '$1');
单词首字母大写
' hi man good luck '.replace(/\w+/g, function(word) {
return word.substr(0,1).toUpperCase() + word.substr(1);
});