共计 8331 个字符,预计需要花费 21 分钟才能阅读完成。
实现 apply 办法
思路: 利用
this
的上下文个性。apply
其实就是改一下参数的问题
Function.prototype.myApply = function(context = window, args) {
// this-->func context--> obj args--> 传递过去的参数
// 在 context 上加一个惟一值不影响 context 上的属性
let key = Symbol('key')
context[key] = this; // context 为调用的上下文,this 此处为函数,将这个函数作为 context 的办法
// let args = [...arguments].slice(1) // 第一个参数为 obj 所以删除, 伪数组转为数组
let result = context[key](...args); // 这里和 call 传参不一样
// 革除定义的 this 不删除会导致 context 属性越来越多
delete context[key];
// 返回后果
return result;
}
// 应用
function f(a,b){console.log(a,b)
console.log(this.name)
}
let obj={name:'张三'}
f.myApply(obj,[1,2]) //arguments[1]
实现防抖函数(debounce)
防抖函数原理:把触发十分频繁的事件合并成一次去执行 在指定工夫内只执行一次回调函数,如果在指定的工夫内又触发了该事件,则回调函数的执行工夫会基于此刻从新开始计算
防抖动和节流实质是不一样的。防抖动是将屡次执行变为 最初一次执行
,节流是将屡次执行变成 每隔一段时间执行
eg. 像百度搜寻,就应该用防抖,当我连续不断输出时,不会发送申请;当我一段时间内不输出了,才会发送一次申请;如果小于这段时间持续输出的话,工夫会从新计算,也不会发送申请。
手写简化版:
// func 是用户传入须要防抖的函数
// wait 是等待时间
const debounce = (func, wait = 50) => {
// 缓存一个定时器 id
let timer = 0
// 这里返回的函数是每次用户理论调用的防抖函数
// 如果曾经设定过定时器了就清空上一次的定时器
// 开始一个新的定时器,提早执行用户传入的办法
return function(...args) {if (timer) clearTimeout(timer)
timer = setTimeout(() => {func.apply(this, args)
}, wait)
}
}
实用场景:
- 文本输出的验证,间断输出文字后发送 AJAX 申请进行验证,验证一次就好
- 按钮提交场景:避免屡次提交按钮,只执行最初提交的一次
- 服务端验证场景:表单验证须要服务端配合,只执行一段间断的输出事件的最初一次,还有搜寻联想词性能相似
实现每隔一秒打印 1,2,3,4
// 应用闭包实现
for (var i = 0; i < 5; i++) {(function(i) {setTimeout(function() {console.log(i);
}, i * 1000);
})(i);
}
// 应用 let 块级作用域
for (let i = 0; i < 5; i++) {setTimeout(function() {console.log(i);
}, i * 1000);
}
查找字符串中呈现最多的字符和个数
例: abbcccddddd -> 字符最多的是 d,呈现了 5 次
let str = "abcabcabcbbccccc";
let num = 0;
let char = '';
// 使其依照肯定的秩序排列
str = str.split('').sort().join('');
// "aaabbbbbcccccccc"
// 定义正则表达式
let re = /(\w)\1+/g;
str.replace(re,($0,$1) => {if(num < $0.length){
num = $0.length;
char = $1;
}
});
console.log(` 字符最多的是 ${char},呈现了 ${num}次 `);
参考:前端手写面试题具体解答
应用 reduce 求和
arr = [1,2,3,4,5,6,7,8,9,10],求和
let arr = [1,2,3,4,5,6,7,8,9,10]
arr.reduce((prev, cur) => {return prev + cur}, 0)
arr = [1,2,3,[[4,5],6],7,8,9],求和
let arr = [1,2,3,4,5,6,7,8,9,10]
arr.flat(Infinity).reduce((prev, cur) => {return prev + cur}, 0)
arr = [{a:1, b:3}, {a:2, b:3, c:4}, {a:3}],求和
let arr = [{a:9, b:3, c:4}, {a:1, b:3}, {a:3}]
arr.reduce((prev, cur) => {return prev + cur["a"];
}, 0)
实现字符串翻转
在字符串的原型链上增加一个办法,实现字符串翻转:
String.prototype._reverse = function(a){return a.split("").reverse().join("");
}
var obj = new String();
var res = obj._reverse ('hello');
console.log(res); // olleh
须要留神的是,必须通过实例化对象之后再去调用定义的办法,不然找不到该办法。
instanceof
instanceof
运算符用于检测构造函数的 prototype
属性是否呈现在某个实例对象的原型链上。
const myInstanceof = (left, right) => {
// 根本数据类型都返回 false
if (typeof left !== 'object' || left === null) return false;
let proto = Object.getPrototypeOf(left);
while (true) {if (proto === null) return false;
if (proto === right.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
Array.prototype.forEach()
Array.prototype.forEach = function(callback, thisArg) {if (this == null) {throw new TypeError('this is null or not defined');
}
if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
}
const O = Object(this);
const len = O.length >>> 0;
let k = 0;
while (k < len) {if (k in O) {callback.call(thisArg, O[k], k, O);
}
k++;
}
}
实现一个 call
call 做了什么:
- 将函数设为对象的属性
- 执行 & 删除这个函数
- 指定 this 到函数并传入给定参数执行函数
- 如果不传入参数,默认指向为 window
// 模仿 call bar.mycall(null);
// 实现一个 call 办法:Function.prototype.myCall = function(context) {
// 此处没有思考 context 非 object 状况
context.fn = this;
let args = [];
for (let i = 1, len = arguments.length; i < len; i++) {args.push(arguments[i]);
}
context.fn(...args);
let result = context.fn(...args);
delete context.fn;
return result;
};
实现类数组转化为数组
类数组转换为数组的办法有这样几种:
- 通过 call 调用数组的 slice 办法来实现转换
Array.prototype.slice.call(arrayLike);
- 通过 call 调用数组的 splice 办法来实现转换
Array.prototype.splice.call(arrayLike, 0);
- 通过 apply 调用数组的 concat 办法来实现转换
Array.prototype.concat.apply([], arrayLike);
- 通过 Array.from 办法来实现转换
Array.from(arrayLike);
实现数组去重
给定某无序数组,要求去除数组中的反复数字并且返回新的无反复数组。
ES6 办法(应用数据结构汇合):
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]
ES5 办法:应用 map 存储不反复的数字
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
uniqueArray(array); // [1, 2, 3, 5, 9, 8]
function uniqueArray(array) {let map = {};
let res = [];
for(var i = 0; i < array.length; i++) {if(!map.hasOwnProperty([array[i]])) {map[array[i]] = 1;
res.push(array[i]);
}
}
return res;
}
手写 Promise
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise(fn) {
// 保留初始化状态
var self = this;
// 初始化状态
this.state = PENDING;
// 用于保留 resolve 或者 rejected 传入的值
this.value = null;
// 用于保留 resolve 的回调函数
this.resolvedCallbacks = [];
// 用于保留 reject 的回调函数
this.rejectedCallbacks = [];
// 状态转变为 resolved 办法
function resolve(value) {
// 判断传入元素是否为 Promise 值,如果是,则状态扭转必须期待前一个状态扭转后再进行扭转
if (value instanceof MyPromise) {return value.then(resolve, reject);
}
// 保障代码的执行程序为本轮事件循环的开端
setTimeout(() => {
// 只有状态为 pending 时能力转变,if (self.state === PENDING) {
// 批改状态
self.state = RESOLVED;
// 设置传入的值
self.value = value;
// 执行回调函数
self.resolvedCallbacks.forEach(callback => {callback(value);
});
}
}, 0);
}
// 状态转变为 rejected 办法
function reject(value) {
// 保障代码的执行程序为本轮事件循环的开端
setTimeout(() => {
// 只有状态为 pending 时能力转变
if (self.state === PENDING) {
// 批改状态
self.state = REJECTED;
// 设置传入的值
self.value = value;
// 执行回调函数
self.rejectedCallbacks.forEach(callback => {callback(value);
});
}
}, 0);
}
// 将两个办法传入函数执行
try {fn(resolve, reject);
} catch (e) {
// 遇到谬误时,捕捉谬误,执行 reject 函数
reject(e);
}
}
MyPromise.prototype.then = function(onResolved, onRejected) {
// 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
onResolved =
typeof onResolved === "function"
? onResolved
: function(value) {return value;};
onRejected =
typeof onRejected === "function"
? onRejected
: function(error) {throw error;};
// 如果是期待状态,则将函数退出对应列表中
if (this.state === PENDING) {this.resolvedCallbacks.push(onResolved);
this.rejectedCallbacks.push(onRejected);
}
// 如果状态曾经凝固,则间接执行对应状态的函数
if (this.state === RESOLVED) {onResolved(this.value);
}
if (this.state === REJECTED) {onRejected(this.value);
}
};
实现防抖函数(debounce)
防抖函数原理:在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则从新计时。
那么与节流函数的区别间接看这个动画实现即可。
手写简化版:
// 防抖函数
const debounce = (fn, delay) => {
let timer = null;
return (...args) => {clearTimeout(timer);
timer = setTimeout(() => {fn.apply(this, args);
}, delay);
};
};
实用场景:
- 按钮提交场景:避免屡次提交按钮,只执行最初提交的一次
- 服务端验证场景:表单验证须要服务端配合,只执行一段间断的输出事件的最初一次,还有搜寻联想词性能相似
生存环境请用 lodash.debounce
Array.prototype.map()
Array.prototype.map = function(callback, thisArg) {if (this == undefined) {throw new TypeError('this is null or not defined');
}
if (typeof callback !== 'function') {throw new TypeError(callback + 'is not a function');
}
const res = [];
// 同理
const O = Object(this);
const len = O.length >>> 0;
for (let i = 0; i < len; i++) {if (i in O) {
// 调用回调函数并传入新数组
res[i] = callback.call(thisArg, O[i], i, this);
}
}
return res;
}
手写 bind 函数
bind 函数的实现步骤:
- 判断调用对象是否为函数,即便咱们是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
- 保留以后函数的援用,获取其余传入参数值。
- 创立一个函数返回
- 函数外部应用 apply 来绑定函数调用,须要判断函数作为构造函数的状况,这个时候须要传入以后函数的 this 给 apply 调用,其余状况都传入指定的上下文对象。
// bind 函数实现
Function.prototype.myBind = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {throw new TypeError("Error");
}
// 获取参数
var args = [...arguments].slice(1),
fn = this;
return function Fn() {
// 依据调用形式,传入不同绑定值
return fn.apply(
this instanceof Fn ? this : context,
args.concat(...arguments)
);
};
};
手写类型判断函数
function getType(value) {
// 判断数据是 null 的状况
if (value === null) {return value + "";}
// 判断数据是援用类型的状况
if (typeof value === "object") {let valueClass = Object.prototype.toString.call(value),
type = valueClass.split("")[1].split("");
type.pop();
return type.join("").toLowerCase();} else {
// 判断数据是根本数据类型的状况和函数的状况
return typeof value;
}
}
验证是否是邮箱
function isEmail(email) {var regx = /^([a-zA-Z0-9_\-])[email protected]([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/;
return regx.test(email);
}
实现一个函数判断数据类型
function getType(obj) {if (obj === null) return String(obj);
return typeof obj === 'object'
? Object.prototype.toString.call(obj).replace('[object', '').replace(']','').toLowerCase()
: typeof obj;
}
// 调用
getType(null); // -> null
getType(undefined); // -> undefined
getType({}); // -> object
getType([]); // -> array
getType(123); // -> number
getType(true); // -> boolean
getType('123'); // -> string
getType(/123/); // -> regexp
getType(new Date()); // -> date
实现 (5).add(3).minus(2) 性能
例:5 + 3 – 2,后果为 6
Number.prototype.add = function(n) {return this.valueOf() + n;
};
Number.prototype.minus = function(n) {return this.valueOf() - n;
};
实现 add(1)(2) =3
// 题意的答案
const add = (num1) => (num2)=> num2 + num1;
// 整了一个加强版 能够有限链式调用 add(1)(2)(3)(4)(5)....
function add(x) {
// 存储和
let sum = x;
// 函数调用会相加,而后每次都会返回这个函数自身
let tmp = function (y) {
sum = sum + y;
return tmp;
};
// 对象的 toString 必须是一个办法 在办法中返回了这个和
tmp.toString = () => sum
return tmp;
}
alert(add(1)(2)(3)(4)(5))
有限链式调用实现的关键在于 对象的 toString 办法 : 每个对象都有一个
toString()
办法,当该对象被示意为一个文本值时,或者一个对象以预期的字符串形式援用时主动调用。
也就是我在调用很屡次后,他们的后果会存在 add
函数中的 sum
变量上,当我 alert
的时候 add
会主动调用 toString
办法 打印出 sum,
也就是最终的后果
实现一个队列
基于链表构造实现队列
const LinkedList = require('./ 实现一个链表构造')
// 用链表默认应用数组来模仿队列,性能更佳
class Queue {constructor() {this.ll = new LinkedList()
}
// 向队列中增加
offer(elem) {this.ll.add(elem)
}
// 查看第一个
peek() {return this.ll.get(0)
}
// 队列只能从头部删除
remove() {return this.ll.remove(0)
}
}
var queue = new Queue()
queue.offer(1)
queue.offer(2)
queue.offer(3)
var removeVal = queue.remove(3)
console.log(queue.ll,'queue.ll')
console.log(removeVal,'queue.remove')
console.log(queue.peek(),'queue.peek')