手写常见面试题
防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
有两种情况:
// 非立即执行
const debounce1 = (fn, delay) => {
let timer = null;
return (...args) => {
if(timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay)
}
}
// 立即执行
const debounce2 = (fn, delay) => {
let timer = null;
let emitNow = true;
return (...args) => {
if(timer) clearTimeout(timer);
if(emitNow) {
fn.apply(this, args);
emitNow = false;
} else {
timer = setTimeout(() => {
fn.apply(this, args);
emitNow = true;
}, delay)
}
}
}
// 通过参数控制是否立即执行
const debounce3 = (fn, delay, isImmediate) => {
let timer = null;
let emitNow = true;
return (...args) => {
if(timer) clearTimeout(timer);
if(isImmediate) {
if(emitNow) {
fn.apply(this, args);
emitNow = false;
} else {
timer = setTimeout(() => {
fn.apply(this, args);
emitNow = true;
}, delay)
}
} else {
timer = setTimeout(() => {
fn.apply(this, args);
}, delay)
}
}
}
防抖函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
有两种情况:
// 非立即执行
const throttle1 = (fn, delay) => {
let isEmit = false;
return (...args) => {
if(isEmit) return;
isEmit = true;
setTimeout(() => {
fn.apply(this, args);
isEmit = false;
}, delay);
}
}
// 立即执行
const throttle2 = (fn, delay) => {
let isEmit = false;
return (...args) => {
if(isEmit) return;
isEmit = true;
fn.apply(this,args);
setTimeout(() => {
isEmit = false;
},delay);
}
}
// 通过参数控制是否立即执行
const throttle3 = (fn, delay, isImmediate) => {
let isEmit = false;
return (...args) => {
if(isEmit) return;
isEmit = true;
if(isImmediate) {
fn.apply(this, args);
setTimeout(() => {
isEmit = false;
},delay);
} else {
setTimeout(() => {
fn.apply(this, args);
isEmit = false;
}, delay);
}
}
}
function deepCopy(obj) {
if(typeof obj !== 'object') {
return obj;
};
let cloneObj = obj.constructor=== Array ? [] : {};
for(let property in obj) {
cloneObj[property] = typeof obj[property] === 'object' ? deepCopy(obj[property]) : obj[property];
}
return cloneObj;
}
instanceOf
根据原型链的知识,我们能很快能知道根据对象的__proto__
属性就能找到其构造函数。
const instanceOf = function(object, target) {
// 取目标的原型对象
const instance = target.prototype;
// 取待检验的对象的隐式原型
object = object.__proto__;
while(true) {
if(!object) return false;
if(object === instance) return true;
object = object.__proto__;
}
}
new
操作符new
的作用:
this
执行创建的新对象prototype
对象上(新对象的__proto__
属性指向函数的prototype
);function createObject() {
// 创建一个新对象
const obj = {};
// 获取构造函数,采用call方法使得arguments能够使用shift方法将第一个参数(构造函数)拿出来
const Constructor = [].shift.call(arguments);
// 将对象__proto__属性链接到构造函数的prototype属性中
obj.__proto__ = Constructor.prototype;
// 将构造函数中的this指向对象并传递参数
const result = Constructor.apply(obj, arguments);
// 确保返回值是一个对象
return typeof ret === "object" ? result : obj;
}
call
方法我们都很清楚call
这个方法就是用于修改this
指向,但是有些同学可能不太懂其原理,我们来手写一个call
方法帮助深入了解其原理。
Function.prototype.mycall = function(context) {
// 默认上下文为window
context = context || window;
// 添加一个属性用于保存当前调用call的函数
context.fn = this;
// 将arguments转变成数组并移除第一个参数(上下文)
const args = [...arguments].slice(1);
// 这样调用函数时该函数内部的this就指向调用者(context);
const result = context.fn(...args);
delete context.fn;
return result;
}
apply
方法apply
原理与call
很相似,唯一不同就是传参问题,apply
方法的第二个参数是所有参数组合成的数组,而call
方法除了第一个参数是context
外,其他都是传入的参数。
Function.prototype.myapply = function(context, arr) {
// 默认上下文为window
context = context || window;
// 添加一个属性用于保存当前调用call的函数
context.fn = this;
// 将arguments转变成数组并移除第一个参数(上下文)
let result;
if(!arr) {
result = context.fn();
} else {
result = context.fn(arr);
}
delete context.fn;
return result;
}
bind
方法相对于call
和apply
而言,bind
方法的返回值是一个改变了this
的函数(即非立即调用)。当返回的函数被当作构造函数使用时,this
失效,但是传入的参数依旧有效。
bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。
Function.prototype.mybind = function(context) {
if(typeof this !== 'function') {
throw new Error('Uncaught TypeError: not a function')
}
const args = [...arguments].slice(1);
// 用于记录当前传入的函数的prototype;
let Transit = function() {};
const _ = this;
const FunctionToBind = function() {
const bindArgs = [...arguments];
return _.apply(this instanceof Transit ? this : context, args.concat(bindArgs));
}
// 记录当前传入的函数的prototype;
Transit.prototype = this.prototype;
FunctionToBind.prototype = new Transit();
return FunctionToBind;
}
Object.create
方法Object.create()
方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
。
语法:
Object.create(proto[, propertiesObject])
proto
: 必须。表示新建对象的原型对象,即该参数会被赋值到目标对象(即新对象,或说是最后返回的对象)的原型上。该参数可以是null
, 对象, 函数的prototype
属性 (创建空的对象时需传null
, 否则会抛出TypeError
异常)
propertiesObject
: 可选。 添加到新创建对象的可枚举属性(即其自身的属性,而不是原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()
的第二个参数,创建非空对象的属性描述符默认是为false
的,而构造函数或字面量方法创建的对象属性的描述符默认为true
。
new
关键词是通过构造函数来创建对象, 添加的属性是在自身实例下。
Object.create()
创建对象的另一种方式,可以理解为继承一个对象, 添加的属性是在原型下。
// new Object() 方式创建
var a = { rep : 'apple' }
var b = new Object(a)
console.log(b) // {rep: "apple"}
console.log(b.__proto__) // {}
console.log(b.rep) // {rep: "apple"}
// Object.create() 方式创建
var a = { rep: 'apple' }
var b = Object.create(a)
console.log(b) // {}
console.log(b.__proto__) // {rep: "apple"}
console.log(b.rep) // {rep: "apple"}
上面讲了这么多Object.create
的知识,下面我们实现一下该方法:
Object.prototype.mycreate = function(proto, propertiesObject) {
function F() {};
F.prototype = proto;
const obj = new F();
if(propertiesObject) {
Object.defineProperties(obj, propertiesObject);
}
return obj
}
实现原理就是通过创建一个空构造函数并把其prototype
指向传入的对象,最后返回该构造函数的实例。
promise
const statusMap = {
PENDING: "pending",
FULFILLED: "fulfilled",
REJECTED: "rejected"
}
class MyPromise{
constructor(handler) {
if(Object.prototype.toString.call(handler) !== '[object Function]') {
throw new Error('the first parameter should be a function');
}
this.status = statusMap.PENDING;
this.result = null;
// 用于执行then方法
this.fulfilledQueues = [];
this.rejectedQueues = [];
try{
// 执行两个方法
handler(this._resolve.bind(this), this._reject.bind(this));
} catch(err) {
this._reject(err);
}
}
_resolve(val) {
if(this.status !== statusMap.PENDING) return;
const run = () => {
this.status = statusMap.FULFILLED;
this.result = val;
let cb;
while(cb = this.fulfilledQueues.shift()) {
cb(val);
}
}
setTimeout(() => run(), 0);
}
_reject(err) {
if(this.status !== statusMap.PENDING) return;
const run = () => {
this.status = statusMap.REJECTED;
this.result = err;
let cb;
while(cb = this.rejectedQueues.shift()) {
cb(err);
}
}
setTimeout(() => run(), 0);
}
}
MyPromise.prototype.then = function(onFulfilled, onRejeceted) {
const { status, result } = this;
return new MyPromise((onFulfilledNext, onRejecetedNext) => {
let fulfilled = value => {
try {
if(Object.prototype.toString.call(onFulfilled) !== '[object Function]') {
onFulfilledNext(value);
} else {
let res = onFulfilled(value);
// 返回结果还是MyPromise的实例
if(res instanceof MyPromise) {
res.then(onFulfilledNext, onRejecetedNext);
} else {
onFulfilledNext(res);
}
}
} catch(e) {
onRejecetedNext(e);
}
}
let rejected = error => {
try {
if(Object.prototype.toString.call(onRejeceted) !== '[Object Function]') {
onRejecetedNext(error);
} else {
let res = onRejeceted(error);
// 返回结果还是MyPromise的实例
if(res instanceof MyPromise) {
res.then(onFulfilledNext, onRejecetedNext);
} else {
onFulfilledNext(res);
}
}
} catch(e) {
onRejecetedNext(e);
}
}
switch(status) {
case statusMap.PENDING:
this.fulfilledQueues.push(fulfilled);
this.rejectedQueues.push(rejected)
break;
case statusMap.FULFILLED:
this.fulfilledQueues.push(fulfilled);
break;
case statusMap.REJECTED:
this.rejectedQueues.push(rejected);
break;
}
})
}
Array的方法flat很多浏览器还未能实现,而且浏览器支持的flat方法不能处理嵌套的数组。写一个flat方法,实现扁平化嵌套数组。
// 最简单的方案
Array.prototype.flat = function (arr) {
return arr
.toString()
.split(',')
.map((item) => +item);
};
Array.prototype.flat = function (arr) {
return arr.reduce((prev, item) => {
return prev.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
};
对于去除1次以上的重复item,可以使用Set
。
function delRepeat(arr) {
return Array.from(new Set(arr));
}
但是去除2次以上就不能用set
了。
// 已知数组
var arr = [1,1,1,1,1,1,1,3,3,3,3,3,5,5];
// 方法一
function delRepeat(arr) {
arr = arr.sort();
for (let i = 0; i < arr.length; i++) {
if (arr[i] == arr[i + 2]) {
arr.splice(i, 1);
i--;
}
}
return arr;
}
// 方法二
function delRepeat(arr) {
var newArr = [];
var obj = {};
arr.map((item) => {
if (obj[item]) {
obj[item] += 1;
} else {
obj[item] = 1;
}
obj[item] <= 2 ? newArr.push(item) : '';
});
return newArr;
}