【自我拯救计划】JavaScript代码手写篇

166 阅读5分钟

这一篇打算重刷一遍常考手写代码题,方便以后翻出来记忆。

废话少说,直接刷~

实现一个new

实现new需要几步?:

  1. 创建一个对象
  2. 获取其构造函数,确保原型指向正确
  3. this指向
  4. 返回这个对象
  • 代码:
function my_new(){
    // 1. 创建一个对象
    let obj = {}
    // 2. 获取构造函数
    let F = [].shift.call(arguments)
    // 3. 将obj的原型指向构造函数的原型
    obj.__proto__ = F.prototype
    // 4. this指向该新创建对象,以及参数
    let result = F.apply(obj,arguments)
    // 5. 确保返回是个对象
    return typeof result === 'object' ? result : obj
}
  • 测试: image.png

实现一个call

call做了什么事?

  1. 改变this指向
  2. 执行了该函数
  • 代码:
Function.prototype.my_call = function (context) {
    // this参数如果传null或者不传,应该指向window
    context = context || window
    //  symbol保证context.fn的唯一性
    let fn = Symbol('fn')
    // 获取调用call的函数,用this获取
    context[fn] = this
    let args = []
    // 获取传入的参数
    for(let i = 1; i < arguments.length; i++){
        args.push('arguments['+ i +']')
    }
    // 执行函数
    let result = eval('context[fn](' + args + ')')
    // 删除该函数
    delete context[fn]
    return result
}
  • 测试:
let obj = {
    name: 'boom'
}
function test(value) {
    console.log('my call 执行啦 —— ' + value)
}
test.my_call(obj, '2022')

image.png

实现一个apply

apply跟call相似,区别就是传入参数不一样

  • 代码:
Function.prototype.my_apply = function (context) {
    context = context || window
    let fn = Symbol('fn')
    context[fn] = this
    let result = arguments[1] ? context[fn](...arguments[1]) : context[fn]();
    delete context[fn]
    return result
}
  • 测试
let obj = {
    name: 'boom'
}
function test(v1, v2, v3) {
    console.log('my apply 执行啦 —— ' + v1 + v2 + v3)
}
test.my_apply(obj, ['2022', '01', '01'])

image.png

实现一个bind

bind 做了什么?

  1. 返回一个函数
  2. this绑定为传入的第一个参数
  3. 注意bind返回的函数的使用方式:如果是作为构造函数new,this指向实例;如果是作为普通函数,this指向window
  • 代码
Function.prototype.my_bind = function (context) {
    if (typeof this !== "function") {
      throw new Error("not a function");
    }

    let self = this;
    let args = Array.prototype.slice.call(arguments, 1);

    let fNOP = function () {};

    let fBound = function () {
        // 合并两次的参数
        var bindArgs = Array.prototype.slice.call(arguments);
        // 如果是返回的函数是通过new方式,this指向实例,此时的this为fBound函数,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
        // 如果返回的函数普通方式调用,this为window,将绑定函数的 this 指向 context
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;

}
  • 测试
var value = 2;

var foo = {
    value: 1
};

function bar(name, age) {
    this.habit = 'shopping';
    console.log(this.value);
    console.log(name);
    console.log(age);
}

bar.prototype.friend = 'haha';

var bindFoo = bar.my_bind(foo, 'boom');

var obj = new bindFoo('18');

let o = bindFoo('11')

image.png

image.png

判断数组类型

let arr = [1, 2, 3];

// 方法一:instanceof方法
console.log(arr instanceof Array);

// 方法二:isArray方法
console.log(Array.isArray(arr));

// 方法三:Object.prototype方法
console.log(Object.prototype.toString.call(arr) === '[object Array]');

// 方法二:constructor方法
console.log(arr.constructor === Array);

// 方法五:Array.__proto__方法
console.log(arr.__proto__ === Array.prototype);

// 方法六:Object.getPrototypeOf方法
console.log(Object.getPrototypeOf(arr) === Array.prototype);

// 方法七:Array.prototype.isPrototypeOf方法
console.log(Array.prototype.isPrototypeOf(arr));

sort快速打乱数组

  • 代码
let arr = [1,2,3,4,5,6,7,8,9,10]

arr.sort(()=>{ return Math.random() - 0.5})
// 利用sort,返回结果为大于等于0时被交换位置,小于0时交换位置
  • 测试 image.png

但是由于v8 在处理 sort 方法时,当目标数组长度小于 10 时,使用插入排序;反之,使用快速排序和插入排序的混合排序。存在乱序不彻底的问题

第二种乱序更彻底

  • 代码
function shuffle(a){
    for(let i = a.length; i; i--){
        let j = Math.floor(Math.random() * i);
        [a[i-1], a[j]] = [a[j], a[i-1]];
    }
    return a;
}
  • 测试

image.png

防抖

【重点清零】使用setTimeout来辅助实现,延迟运行需要执行的代码。如果方法多次触发,就把上次记录的延迟执行代码清掉,重新开始计时。若计时器件事件没有被重新触发,等延迟时间计时完毕,则执行目标代码。

防抖需要注意什么?

  1. 先清除定时器
  2. 注意修改this指向,因为直接return函数里面的this指向的时window,而不是目标DOM
  3. 注意获取event参数
function debounce(func, wait) {
    var timer;
    return function () {
        // 获取this
        var context = this;
        // 获取event
        var args = arguments;
        // 清零
        clearTimeout(timer)
        timeout = setTimeout(function(){
            func.apply(context, args)
        }, wait);
    }
}

节流

节流原理:如果持续触发事件,每隔一段时间,只执行一次事件。有两种主流的实现方式,一种是使用【时间戳】,一种是【设置定时器】

  • 时间戳
function throtte(func, wait) {
	var context, args
	var before = 0
	return function() {
		var now = +new Date() // 隐式转换,相当于ToNumber。将字符串直接转化为number类型
		context = this
		args = arguments
		if (now - before > wait) {
			func.apply(context, args)
			before = now
		}

	}
}
  • 定时器
function throtte(func, wait) {
    var timer
    var context, args
    return function() {
        context = this
        args = arguments
        if (!timer) {
            timer = setTimeout(function(){
                    timer = null
                    func.apply(this, args)
            }, wait)
        }
    }
}

container.onmousemove = throtte(getUserAction, 3000)

实现一个instanceof

instanceof的原理就是根据原型链

  • 代码
function instanceOf (A, B){
    let p = A
    while(p){
        if (p === B.prototype){
            return true
        }
        p = p.__proto__
    }
    return false
}
  • 测试

image.png

JSONP的实现

JSONP的原理:script标签不受同源策略限制,用来进行跨域请求,优点兼容性比较好,缺点是只能get请求

  • 代码
const jsonp = (url, param, callback) => {
    function dealUrl () {
        let data = ''
        for(let key in param){
            console.log(param)
            if (param.hasOwnProperty(key)){
                data += `${key}=${param[key]}&`
            }
        }
        data += `callback=${callback}`
        return `${url}?${data}`
    }
    return new Promise((resolve, reject)=>{
        const script = document.createElement('script')
        script.src = dealUrl()
        document.body.appendChild(script)
        window[callback] = data => {
            resolve(data)
            document.body.removeChild(script)
        }
    })

}
  • 测试

image.png

实现一个简单的发布订阅模式

  • 代码
class Subject{
    constructor(name){
        this.name = name
        this.observer = []
        this.weather = 'sunny'
    }
    on(observer){
        this.observer.push(observer)
    }
    trigger(data){
        this.weather = data
        this.observer.forEach((item)=>{
            item.update(data)
        })
    }
    off(observer){
        let index = this.observer.findIndex(item => item === observer)
        if (index !== -1){
            this.observer.splice(index, 1)
        }
    }
}

class Observer{
    constructor(name){
        this.name = name
    }
    update(msg){
        console.log(`观察者${this.name}: ${msg}`)
    }
}
  • 测试
let s = new Subject('msg')

let o1 = new Observer('haha')
let o2 = new Observer('boom')

s.on(o1)
s.on(o2)

s.trigger('rainny')
s.off(o1)
s.trigger('hot')
console.log(s)

image.png

实现一个Promise.all

  • 代码
Promise.my_all = (promises) => {
    return new Promise ((resolve, reject) => {
        let result = []
        let count = 0
        if(promises.length === 0){
            return resolve(result)
        }
        promises.forEach((item, index) => {
            Promise.resolve(item).then((res) => {
                result[index] = res
                count++
                if (count === promises.length){
                    resolve(result)
                }
            }).catch(reject)
        });
    })
}

  • 测试
const p1 = Promise.resolve(1)
const p2 = new Promise((resolve) => {
  setTimeout(() => resolve(2), 1000)
})
const p3 = new Promise((resolve) => {
  setTimeout(() => resolve(3), 3000)
})
const p4 = Promise.reject('err4')

const p11 = Promise.my_all([ p1, p2, p3 ])
    .then(data => {console.log(data)})
    .catch(err => {console.log(err)})

const p12 = Promise.my_all([ p1, p2, p3, p4 ])
    .then(data => {console.log(data)})
    .catch(err => {console.log(err)})   

image.png

实现一个Promise.race

  • 代码
Promise.my_race = (promises) => {
    return new Promise((resolve, reject) => {
        promises.forEach(item => {
            Promise.resolve(item).then(resolve).catch(reject)
        });
    })
}
  • 测试
const p1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 1)
})

const p2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 2)
})

Promise.my_race([p1, p2]).then((value) => {
  console.log(value) // 2
})

Promise.my_race([p1, p2, 3]).then((value) => {
  console.log(value) // 3
})

image.png

实现一个promise

class MyPromise{
    constructor(fn){
        this.resolvedcallback = []
        this.rejectedCallback = []
        this.state = 'pending'
        this.value = ''
        fn(this.resolve.bind(this), this.reject,bind(this))
    }
    resolve(value){
        if(this.state === 'pending'){
            this.state = 'resolved'
            this.value = value
            this.resolvedcallback.map(item => item(value))
        }
    }
    reject(value){
        if(this.state === 'panding'){
            this.state = 'rejected'
            this.value = value
            this.rejectedCallback.map(item => item(value))
        }
    }
    then(onfulfill, onreject){
        if(this.state === 'pending'){
            this.resolvedcallback.map(item => item(value))
            this.resolvedcallback.map(item => item(value))
        }
        if(this.state === 'resolved'){
            onfulfill(this.value)
        }
        if(this.state === 'rejected'){
            onreject(this.value)
        }
    }
}