js 常见手写题
- 手写call
Function.prototype.myCall = function(thisArg, ...args) {
const fn = Symbol('fn') // 声明一个独有的Symbol属性, 防止fn覆盖已有属性
thisArg = thisArg || window // 若没有传入this, 默认绑定window对象
thisArg[fn] = this // this指向调用call的对象,即我们要改变this指向的函数
const result = thisArg[fn](...args) // 执行当前函数
delete thisArg[fn] // 删除我们声明的fn属性
return result // 返回函数执行结果
}
//测试
foo.myCall(obj)
- 手写 apply
Function.prototype.myApply = function(thisArg, args) {
const fn = Symbol('fn') // 声明一个独有的Symbol属性, 防止fn覆盖已有属性
thisArg = thisArg || window // 若没有传入this, 默认绑定window对象
thisArg[fn] = this // this指向调用call的对象,即我们要改变this指向的函数
const result = thisArg[fn](...args) // 执行当前函数
delete thisArg[fn] // 删除我们声明的fn属性
return result // 返回函数执行结果
}
//测试
foo.myApply(obj, [])
- 手写bind
Function.prototype.myBind = function (thisArg, ...args) {
var self = this
// new优先级
var fbound = function () {
self.apply(this instanceof self ? this : thisArg, args.concat(Array.prototype.slice.call(arguments)))
}
// 继承原型上的属性和方法
fbound.prototype = Object.create(self.prototype);
return fbound;
}
//测试
const obj = { name: '写代码像蔡徐抻' }
function foo() {
console.log(this.name)
console.log(arguments)
}
foo.myBind(obj, 'a', 'b', 'c')()
- 防抖
function debounce(func, wait) {
let timeout = null
return function() {
let context = this
let args = arguments
if (timeout) clearTimeout(timeout)
timeout = setTimeout(() => {
func.apply(context, args)
}, wait)
}
}
- 节流
function throttle(func, wait) {
let timeout = null
return function() {
let context = this
let args = arguments
if (!timeout) {
timeout = setTimeout(() => {
timeout = null
func.apply(context, args)
}, wait)
}
}
}
function throttle(func, wait) {
var prev = 0;
return function() {
let now = Date.now();
let context = this;
let args = arguments;
if (now - prev > wait) {
func.apply(context, args);
prev = now;
}
}
}
- 实现new
// new是关键字,这里我们用函数来模拟,new Foo(args) <=> myNew(Foo, args)
function myNew(foo, ...args) {
// 创建新对象,并继承构造方法的prototype属性, 这一步是为了把obj挂原型链上, 相当于obj.__proto__ = Foo.prototype
let obj = Object.create(foo.prototype)
// 执行构造方法, 并为其绑定新this, 这一步是为了让构造方法能进行this.name = name之类的操作, args是构造方法的入参, 因为这里用myNew模拟, 所以入参从myNew传入
let result = foo.apply(obj, args)
// 如果构造方法已经return了一个对象, 那么就返回该对象, 一般情况下,构造方法不会返回新实例,但使用者可以选择返回新实例来覆盖new创建的对象 否则返回myNew创建的新对象
return typeof result === 'object' && result !== null ? result : obj
}
function Foo(name) {
this.name = name
}
const newObj = myNew(Foo, 'zhangsan')
console.log(newObj) // Foo {name: "zhangsan"}
console.log(newObj instanceof Foo) // true
- 继承
function Parent(name) {
this.name = [name]
}
Parent.prototype.getName = function() {
return this.name
}
function Child() {
// 构造函数继承
Parent.call(this, 'zhangsan')
}
//原型链继承
// Child.prototype = new Parent()
Child.prototype = Object.create(Parent.prototype) //将`指向父类实例`改为`指向父类原型`
Child.prototype.constructor = Child
//测试
const child = new Child()
const parent = new Parent()
child.getName() // ['zhangsan']
parent.getName()
jsonp
/**
* 手写jsonp并返回Promise对象
* 参数url,data:json对象,callback函数
*/
function jsonp(url, data = {}, callback = 'callback') {
// 处理json对象,拼接url
data.callback = callback
let params = []
for (let key in data) {
params.push(key + '=' + data[key])
}
console.log(params.join('&'))
// 创建script元素
let script = document.createElement('script')
script.src = url + '?' + params.join('&')
document.body.appendChild(script)
// 返回promise
return new Promise((resolve, reject) => {
window[callback] = (data) => {
try {
resolve(data)
} catch (e) {
reject(e)
} finally {
// 移除script元素
script.parentNode.removeChild(script)
console.log(script)
}
}
})
}
jsonp('http://photo.sina.cn/aj/index', {
page: 1,
cate: 'recommend'
}, 'jsoncallback').then(data => {
console.log(data)
})
ajax promise
function getJson(url) {
let promise = new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return
if (this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
xhr.onerror = function() {
reject(new Error(this.statusText))
}
xhr.responseType = 'json'
xhr.setRequestHeader('Accept', 'application/json')
xhr.send(null)
})
return promise
}
深拷贝
if(obj.constructor === Date) return new Date(obj);
if(obj.constructor === RegExp) return new RegExp(obj)
let obj ={
a: 15,
b: 2,
c: {
d:1,
e:3,
fn: function(){
console.log(1111111111111)
},
date: new Date(),
reg: new RegExp(/^\d+$/g)
}
}
function deepClone(obj) {
let map = new WeakMap()
function dc(obj) {
let res, exitObj
if (obj.constructor === Date) {
return new Date(obj)
}else if (obj.constructor === RegExp) {
return new RegExp(obj)
}else if (Array.isArray(obj)) {
res = []
} else if (Object.prototype.toString.call(obj) === '[object Object]'){
res = {}
}else{
return obj
}
exitObj = map.get(obj)
if (exitObj) {
return exitObj
}
map.set(obj, res)
for (let key in obj) {
if (obj.hasOwnProperty(key)) { //symbol 换成Reflect.ownKeys
if (obj[key] && typeof obj[key] === 'object'){
res[key] = dc(obj[key])
}else{
res[key] = obj[key]
}
}
}
return res
}
return dc(obj)
}
let res = deepClone(obj)
res.a = 5555
console.log(obj)
console.log(res)
深度优先遍历 和 广度优先遍历
- 其实简单来说 深度优先就是自上而下的遍历搜索 广度优先则是逐层遍历, 如下图所示
深度优先
广度优先
对于算法来说 无非就是时间换空间 空间换时间
- 深度优先不需要记住所有的节点, 所以占用空间小, 而广度优先需要先记录所有的节点占用空间大
- 深度优先有回溯的操作(没有路走了需要回头)所以相对而言时间会长一点
深度优先采用的是堆栈的形式, 即先进后出
广度优先则采用的是队列的形式, 即先进先出
const data = [ { name: 'a', children: [ { name: 'b', children: [{ name: 'e' }] },
{ name: 'c', children: [{ name: 'f' }] },
{ name: 'd', children: [{ name: 'g' }] },
],
},
{
name: 'a2',
children: [
{ name: 'b2', children: [{ name: 'e2' }] },
{ name: 'c2', children: [{ name: 'f2' }] },
{ name: 'd2', children: [{ name: 'g2' }] },
],
}
]
// 深度遍历, 使用递归
function getName(data) {
const result = [];
data.forEach(item => {
const map = data => {
result.push(data.name);
data.children && data.children.forEach(child => map(child));
}
map(item);
})
return result.join(',');
}
// 广度遍历, 创建一个执行队列, 当队列为空的时候则结束
function getName2(data) {
let result = [];
let queue = data;
while (queue.length > 0) {
[...queue].forEach(child => {
queue.shift();
result.push(child.name);
child.children && (queue.push(...child.children));
});
}
return result.join(',');
}
console.log(getName(data))
console.log(getName2(data))
promise
class Promise{
constructor(executor){
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);
}
}
then(onFulfilled,onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
let promise2 = new Promise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'rejected') {
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)
});
};
});
return promise2;
}
catch(fn){
return this.then(null,fn);
}
}
function resolvePromise(promise2, x, resolve, reject){
if(x === promise2){
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called;
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if(called)return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, err => {
if(called)return;
called = true;
reject(err);
})
} else {
resolve(x);
}
} catch (e) {
if(called)return;
called = true;
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);
};
});
}
数组扁平化排序
let arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]
arr.toString().split(',').sort((a,b)=>a-b).map(Number)
while(arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr)
}
数组转对象
const arr = [
{
username: 'makai',
displayname: '馆长',
email: 'guanzhang@coffe1891.com'
},
{
username: 'xiaoer',
displayname: '小二',
email: 'xiaoer@coffe1891.com'
},
{
username: 'zhanggui',
displayname: '掌柜',
email: null
},
];
function callback(acc, person) {
//下面这句用到了扩展运算符...acc,表示把acc对象的属性“肢解”开,和新的属性一起
//以一个新的对象返回
return {...acc, [person.username]: person};
}
const obj = arr.reduce(callback, {});//这里的初始值为{}
console.log(obj);
浮点数三位一个逗号
function format(number){
if(number.indexOf('.') == -1) {
return number.replace(/\B(?=(\d{3})+$)/g, ',')
} else {
return number.replace(/\B(?=(\d{3})+.)/g, ',')
}
}
观察者模式
class Subject{
constructor(arg) {
this.observers = []
}
add(observer) {
this.observers.push(observer)
}
notify(...args) {
this.observers.forEach(item => item.update(...args))
}
}
class Observer {
update(...args) {
console.log(...args)
}
}
let sub = new Subject()
let observer1 = new Observer()
let observer2 = new Observer()
sub.add(observer1)
sub.add(observer2)
sub.notify('4564')
树状菜单
let arr = [
{ id: 1, name: '广东', pid: 0 },
{ id: 2, name: '广州', pid: 1 },
{ id: 3, name: '天河', pid: 2 },
{ id: 4, name: '白云', pid: 2 },
{ id: 5, name: '广西', pid: 0 },
{ id: 6, name: '玉林', pid: 5 },
{ id: 7, name: '北流', pid: 6 },
{ id: 8, name: '深圳', pid: 1 },
{ id: 9, name: '东莞', pid: 1 },
{ id: 10, name: '松山湖', pid: 9 },
]
function getMenu(data) {
let ans = []
let map = new WeakMap()
if (data && data.length) {
data.forEach(item => map[item.id] = item)
data.forEach(menu => {
let parent = map[menu.pid]
if (parent) {
parent.chidlren = parent.chidlren || []
parent.chidlren.push(menu)
} else {
// 是顶级
ans.push(menu)
}
})
}
return ans
}
console.log(getMenu(arr))
数组查找路径
const data = [ { name: 'a', children: [ { name: 'b', children: [{ name: 'e' }] },
{ name: 'c', children: [{ name: 'f' }] },
{ name: 'd', children: [{ name: 'g' }] },
],
},
{
name: 'a2',
children: [
{ name: 'b2', children: [{ name: 'e2' }] },
{ name: 'c2', children: [{ name: 'f2' }] },
{ name: 'd2', children: [{ name: 'g2' }] },
],
}
]
function fn(data, name, path) {
if (!path) path = []
for(let i =0;i<data.length;i++) {
let item = data[i]
let temp = [...path]
temp.push(item.name)
if (item.name === name) return temp
if (item.children) {
let res = fn(item.children, name, temp)
if (res) return res
}
}
}
持续更新~~~
转载自:https://juejin.cn/post/7038773226402480142