getType
js
function getType(data) {
return Object.prototype.toString.call(data).slice(8, -1); // [object Type]
}
console.log(getType("")); // String
console.log(getType([])); // Array
instanceof
A instanceof B
: 判断对象 A的原型链上是否存在构造函数 B的原型对象
js
function myInstanceof(L, R) {
if (typeof L !== "object") {
return false;
}
while (true) {
if (L === null) {
return false;
}
if (L.__proto__ === R.prototype) {
return true;
}
L = L.__proto__;
}
}
myInstanceof("", String);
myInstanceof([], Object);
call
fn.call(target, args1, args2...)
主要思路:
- myCall 方法需绑定到
Function.prototype
原型上 - 将 fn 作为 target 的临时属性去执行,执行后删除该属性,返回执行结果
js
Function.prototype.myCall(context = window, ...args) {
if (this === Function.prototype) {
return undefined // 避免 Function.prototype.myCall() 直接调用
}
let fn = Symbol()
context[fn] = this
let result = context[fn](...args)
delete context[fn]
return result
}
apply
fn.apply(target, [args1, args2...])
主要思路:
- 同 call 区别在于传参
js
Function.prototype.myApply(context = window, args) {
if (this === Function.prototype) {
return undefined // 避免 Function.prototype.myCall() 直接调用
}
let fn = Symbol()
context[fn] = this
let result = Array.isArray(args) ? context[fn](...args) : context[fn]()
delete context[fn]
return result
}
bind
let newFn = fn.bind(obj, arg1, arg2...)
主要思路:
- 判断函数是否正常调用
- 柯理化的实现,内外参数的合并
- 兼容构造函数,当 bind 返回的函数被当作构造函数搭配
new
关键字使用, 手动绑带的this
会被忽略
js
Function.prototype.myBind(context = window) {
const fn = this
if (typeof fn !== 'function'){
throw new TypeError('Function.prototype.bind called error')
}
const outArgs = Array.prototype.slice.call(arguments, 1)
function F(){}
F.prototype = this.prototype
const bound = function(){
const innerArgs = Array.prototype.slice.call(arguments)
const args = outArgs.concat(innerArgs)
return fn.apply(this instanceof F ? this : context, args)
}
bound.prototype = new F()
return bound
}
new
主要思路
- 创建空对象作为返回的实例
- 绑定空对象原型
- 执行构造函数,指定 this 为空对象,保存返回结果
- 判断返回结果类型是否为对象
js
function newFun(...args) {
const constructor = args.shift();
let obj = Object.create(constructor.prototype);
const result = constructor.apply(obj, args);
return result && typeof result === "object" ? result : obj;
}
数组相关
- map
js
Array.prototype.myMap = function (executerFn) {
const arr = this;
if (!Array.isArray(arr)) {
throw new TypeError("the caller must be array");
}
let result = [];
for (let i = 0; i < arr.length; ++i) {
result.push(executerFn(arr[i], i, arr));
}
return result;
};
reduce
flat
js
Array.prototype.myFlat = function (deep = 1) {
let arr = this;
if (!Array.isArray(arr)) {
throw new TypeError("the caller must be Array");
}
if (deep < 1) return arr;
return arr.reduce((result, cur) => {
return result.concat(Array.isArray(cur) ? cur.myFlat(deep - 1) : cur);
}, []);
};
let a = [1, 2, [3, [4, [5, 6]]]];
let b = a.myFlat(2); // [ 1, 2, 3, 4, [ 5, 6 ] ]
- 去重
filter + indexOf
js
function unique(arr) {
return arr.filter((item, index) => arr.indexOf(item) === index);
}
set
js
function unique(arr) {
return [...new Set(arr)];
}
debounce 防抖
debounce(fn, time)
: 触发事件 n 秒之内不再触发新事件,才会去执行 fn
应用场景:
- 搜索框,输入后等待 1000ms 后才去搜索
- 窗口变化,1000ms 后再去调整样式
主要思路:
- 返回一个闭包,
- 添加一个标识,判断 fn 是否需要立即执行
js
let timer;
function debounce(fn, time, flag = false) {
let timer;
return function (...args) {
if (timer) {
clearTimeout(timer);
}
if (flag && !timer) {
fn.apply(this, args); // 立即执行一次
}
timer = setTimeout(() => {
timer = null
!flag && fn.apply(this, args);
}, time);
};
}
throttle 节流
throttle(fn, time)
: 无论事件的执行频次,fn 总是在单位时间内执行
可以想象成:无论早高峰还是晚高峰,公交车都是固定班次出发的
主要思路:
- 返回一个闭包
js
function throttle(fn, time) {
let timer = null;
let pre = 0;
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
timer = null;
fn.apply(this, args);
}, time);
}
};
}
deepClone
常见的深拷贝
js
JSON.parse(JSON.stringify(obj));
缺陷:JSON
的限制
- 会自动忽略值为
undefined
,Symbol
,function
的属性 Date
日期会自动转化成字符串RegExp
正则自动转成空对象{}
NaN
,Infinity
会被当初null
处理
递归版
主要思路:
- 1、普通数据类型, null 等直接返回
- 2、
Date
,RegExp
等直接返回新实例 - 3、使用
WeakMap
解决对象自身嵌套引用的问题ps: 当使用
WeakMap
时,hash
与obj
之间就是弱引用关系,不影响垃圾回收机制的执行 - 4、性能优化,当数据量大时
for...in...
循环效率低,可替换成for
,while
循环等
js
function deepClone(obj, hash = new WeakMap()) {
// 基本类型,null 直接返回
if (typeof obj !== "object" || obj === null) return obj;
// 日期,正则
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 解决嵌套循环
if (hash.has(obj)) return hash.get(obj);
let target = new obj.constructor();
hash.set(obj, target);
// for...in... 效率低
// for(let key in obj){
// if(obj.hasOwnProperty(key)){
// target[key] = deepClone(obj[key])
// }
// }
const isArray = Array.isArray(obj);
const keys = isArray ? obj : Object.keys(obj);
for (let i = 0; i < keys.length; ++i) {
const key = isArray ? i : keys[i];
target[key] = deepClone(obj[key]);
}
return target;
}
let x = {
a: 1,
b: undefined,
c: new Date("1996/01/01"),
d: NaN,
e: function () {
console.log("hello");
},
f: Symbol("123"),
g: /(0-9)/,
h: { name: "1", value: "2" },
i: [1, 2, 3, 4],
};
let y = deepClone(x);
console.log(y);
Object.is
Object.is(value1, value2)
: 判断是否是同一个值
主要思路:
- 需要区分与
===
运算符在处理NaN
与+0,-0
等值区别:
js
Object.is(NaN, NaN); // true
Object.is(+0, -0); // false
代码实现:
js
function isSame(a, b) {
if (a === b) {
// 需要区分 +0,-0
// a !== 0, 直接返回 true
// a === 0, 就需要判断 +0,-0, 使用 1/+0 === Infinity 和 1/-0 === -Infinity来进行判断
return a !== 0 || 1 / a === 1 / b;
}
// 当 a,b 都是 NaN 的时候返回 true
return a !== a && b !== b;
}
isSame(NaN, NaN); // true
isSame(+0, -0); // false
浅拷贝 Object.assign
TIP
Object.assign(target, ...sources)
: 从一个或多个源对象sources
上复制可枚举属性的值到目标对象target
主要思路
- 1、判断原生
Object
是否支持该函数, 没有就使用Object.defineProperty
将该函数绑定到Object
上 - 2、校验参数,目标对象 target 不能为 null, undefined
- 3、当目标对象 target 为基本类型,需要转换成对象
- 4、使用
for...in...
遍历源对象上的可枚举属性到目标对象上
js
if (typeof Object.myAssign !== "function") {
// 1,默认 Object 上的属性是不可枚举的,直接往 Object 上添加属性是可枚举的,所以需要通过
// Object.defineProperty 进行设置
("use strict");
Object.defineProperty(Object, "myAssign", {
writeable: true,
configurable: true,
value: function (target) {
if (target == null) {
// 2
throw TypeError("Cannot convert undefined or null to object");
}
let to = Object(target); // 3
for (let i = 1; i < arguments.length; ++i) {
const source = arguments[i];
if (source != null) {
for (let key in source) {
// 没直接调用 source.hasOwnProperty ,是因为 source 对象可能是通过
// Object.create() 创造的,原型没关联到 Object.prototype 对象上,不存在该方法
if (Object.prototype.hasOwnProperty.call(source, key)) {
to[key] = source[key];
}
}
}
}
return to;
},
});
}
Object.create
EventEmit 发布订阅
主要思路
EventEmit
作为一个类,其实例上存在on
,off
,emit
等方法
js
class EventEmit{
constructor(){
this.cache = {}
},
on(name, fn){
if(!this.cache[name]){
this.cache[name] = []
}
this.cache[name].push(fn)
},
off(name, fn){
const tasks = this.cache[name]
if(tasks){
const idx = tasks.findIndex(it => it === fn)
if (idx >= 0) {
tasks.splice(idx, 1)
}
}
},
emit(name, once = false, ...args){
if (this.cache[name]) {
// 创建副本,如果回调函数内继续注册相同事件,会造成死循环
let tasks = this.cache[name].slice()
for (let fn of tasks) {
fn(...args)
}
if (once) {
delete this.cache[name]
}
}
},
}
解析 URL
JSONP
主要思路
- 封装
jsonp
函数,传入url
,data
,callback
等参数 - 拼接
url
, - 动态创建
script
标签并插入页面中 - 挂载回调函数
js
(function (window, document) {
const jsonp = (url, data, callback) => {
// 1. 拼接url,处理 data
let dataString = url.indexOf("?") === -1 ? "?" : "";
for (let key in data) {
dataString += `${key}=${data[key]}&`;
}
// 2.创建自定义 cb
const cbFuncName = `json_cb_${Math.random().toString().replace(".", "")}`;
dataString += `callback=${cbFuncName}`;
// 3.创建 script 标签
const scriptEl = document.createElement("script");
scriptEl.src = url + dataString;
// 4.挂载回调函数
window[cbFuncName] = function (data) {
callback(data);
document.body.removeChild(scriptEl);
};
document.body.appendChild(scriptEl);
};
window.$jsonp = jsonp;
})(window, document);