js手写题收录

  1. Object.create 和 new 的相互实现,原型链和继承的原理
<!-- 通过Object.create(null)创建的对象是非常纯净的,原型链的属性和方法都不会携带。这就非常适合数组对象开发的时候,从对象中取值,提高循环效率 -->
<!-- 首先要明白原型链的原理,每一个构造函数都有一个 prototype 属性(一个原型对象)这个对象上所有的属性和方法都会被构造函数拥有 -->
<!-- 对象都有一个 __proto__ 属性指向构造函数的 prototype 原型对象 -->
<!-- 对象原型(__proto__)和构造函数(prototype)原型对象里面都有一个属性constructor属性,constructor我们称为构造函数,因为它指回构造函数本身 -->
function Phone(system) {
    this.system = system,
}
const xiaomi = new Phone(android);
console.log(xiaomi.__proto__ === Phone.prototype) // true
Object.create 可以创建空(构造)函数,关联它的原型(实现继承)
<!-- 其本质是不用 new 的特性,而是用显式原型继承的法子,这样就不用因使用 new 而产生副作用 -->
Object.create= function (proto){
    function Fun () {};
    Fun.prototype = proto;
    return new Fun();
}
Object.create({name: "steven"})

function _new(Constructor, ...args) {
    const obj = Object.create(null);
    obj.__proto__ = Constructor.prototype;
    <!-- 构造函数内部 this 被赋值为新对象后,执行构造函数内部代码 -->
    const result = Constructor.apply(obj, args);
    <!-- 如果构造函数返回非空对象,则返回该对象,否则返回创建的对象 -->
    return typeof result === "object" ? result : obj;
}
function User(firstname, lastname) {
    this.firstname = firstname;
    this.lastname = lastname;
}
const user = _new_(User, 'johnny', 'joestar')
// new带来的原型链关系是:实例.__proto__ === 构造函数.prototype
// Object.create 的则是:实例.__proto__ === 传入的对象

// 1. 原型链继承
function Person () {
    this.brain = 'smart'
}
Person.prototype.getBrain = function () {
    console.log("brain is: " + this.brain)
}
Person.prototype.age = 25;
function JoestarFamily (name) {
    this.name = name
    this.sayName = function () {
        console.log("say: " + this.name)
    }
}
JoestarFamily.prototype = new Person()
// 等同于 JoestarFamily.prototype.__proto__ === 实例.__proto__ === Person.prototype,但要注意 Person 没有实例化的话,内部的 brain 就不会生成
JoestarFamily.prototype.constructor = JoestarFamily; // 原型的 constructor 指回原来的构造函数
var johnny = new JoestarFamily('johnny')
// 等同于 johnny.__proto__ === JoestarFamily.prototype
// 也就是说 johnny.__proto__.__proto__ === Person.prototype
console.log(johnny.name)
console.log(johnny.age)
console.log(johnny.brain)
console.log(johnny.sayName())
console.log(johnny.getBrain())
// 继承思想:利用原型让一个引用类型继承另一个引用类型的属性和方法
// 优点:父类/父类原型新增属性和方法,子类实例可访问
// 缺点 1. 实现所有属性方法共享,但无法做到属性、方法独享(例如Sub1修改了父类的函数,其他所有的子类Sub2、Sub3...想调用旧的函数就无法实现了);2. 在创建子类型的实例时,不能向超类型的构造函数中传递参数

// 2. 构造函数继承
function SuperType (name) {
    this.name = name;
}
function SubType () {
    SuperType.call(this, "SubType-name");
    this.age = 20;
}
const _instance = new SubType();
console.log(_instance.name);
console.log(_instance.age);
// 继承思想:在子类型的构造函数的内部调用超类型构造函数,通过 apply,call 方法实现,原理是 this 的应用
// 优点:1. 可以在在类型构造函数中向超类型构造函数传递参数;2. 解决了原型链中子类实例共享父类引用属性的问题;3. 可以实现多继承(call 多个父类对象)
// 缺点:1. 实现所有属性方法独享,但无法做到属性、方法共享((例如,Sub1新增了一个函数,然后想让Sub2、Sub3...都可以用的话就无法实现了,只能Sub2、Sub3...各自在构造函数中新增);2. 占用内存,每个子类都有父类的属性和方法(一模一样),影响性能

// 3. 显式原型继承分别是Object.create、Object.setPrototypeOf,隐式原型继承则是 new、对象字面量

// 4. ES6 的类继承,是模拟类继承而出现的一语法糖 ,它的底层实现还是基于 prototype

// 笔试例子
// 1. Student 继承 Person
// 2. Person 包含一个实例变量 name, 包含一个实例方法 printName
// 3. Student 包含一个实例变量 score, 包含一个实例方法 printScore
// 4. Person 和 Student 之间共享一个方法 printCommon
function Person (name) {
    this.name = name;
    this.printName = function () {
        console.log(this.name);
    }
}
Person.prototype.commonMethods = function () {
    console.log("共享方法")
}
function Student (name, scope) {
    this.score = score;
    this.printScore = function () {
        console.log(this.score);
    }
    Person.call(this, name)
}
// 这里是为了避免调用两次构造函数,生成两份实例(造成不必要地内存开销)
// 个方法就是引用了 Object.create 的核心代码,其本质是不用 new 的特性,而是用显式原型继承的法子,这样就不用因使用 new 而产生副作用
// new 是会有副作用的,它不仅会建立原型链关系,而且会执行构造函数中的代码,将其赋予内存中生成的一个对象,并返回它成为实例,而像显式原型继承则只做关系(原型链)的链接
const F = function () { }
F.prototype = Person.prototype;
Student.prototype = new F();
var johnny = new Person('johnny')
var elaine = new Student('elaine', 99)
console.log(johnny.commonMethods === elaine.commonMethods)
  1. 请写一个通用的 JavaScript 函数,来找出某个对象身上的某个属性继承自哪个对象
<!-- 利用 hasOwnProperty 检测属性是否是对象自有属性 -->
function findPrototypeByProperty(obj, propertyName) {
    while(obj) {
        if(obj.hasOwnProperty(propertyName)) return obj;
        else obj = obj.__proto__;
    }
}
var b = 123;
console.log(Object.getPrototypeOf(b) === Number.prototype); // true
console.log(Number.prototype === b.__proto__); // true
const obj0 = { a: 1 }
const obj1 = Object.create(obj0)
obj1.b = 2
const obj2 = Object.create(obj1)
obj2.c = 3
console.log(findPrototypeByProperty(obj2, "c") === obj2) // true
console.log(findPrototypeByProperty(obj1, "b") === obj1) // true
console.log(findPrototypeByProperty(obj0, "a") === obj0) // true
  1. 实现 instanceof
<!-- instanceof 是判断左侧对象是否是右侧构造函数的实例化对象 -->
const _instanceof = (target, origin) => {
    while(target) {
        if(target.__proto__ === origin.prototype) return true;
        target === target.__proto__
    }
    return false
}
  1. 实现对象代理
<!-- vue2 是用 Object.defineProperty 实现数据相应 -->
const obj = { count: 0 };
let count = 0;
Object.defineProperty(obj, "count", {
    get: function () {
        return count++; 
    }
})
console.log(obj.count);
console.log(obj.count);
<!-- vue3采用的是 proxy 实现数据响应 -->
<!-- proxy 是对整个对象的代理,对象上新增属性 proxy 可以监听到,defineProperty不行,数组新增修改也是一样;若对象内部属性要全部递归代理,Proxy可以只在调用的时候递归,而 Object.definePropery 需要一次完成所有递归,性能比 Proxy 差 -->
// proxy 实现链式调用
const pipe = (val) => {
  const funStack = [];
  const proxy = new Proxy({}, {
    get: (obj, fnName) => {
      if (fnName === "getRes") {
        return funStack.reduce((prevVal, fn) => fn(prevVal), val);
      }
      funStack.push(window[fnName]);
      return proxy;
    }
  })
  return proxy
}
const window = {
  double: n => n * 2,
  reverseInt: n => n.toString().split("").reverse().join("") | 0
}
console.log(pipe(8).double.reverseInt.getRes) // 61
  1. 防抖+节流
<!-- 防抖主要应用在搜索联想,窗口变化的情况 -->
function debounce (func, delay) {
    let timer = null;
    return function (...args) {
        if (timer) clearTimout(timer);
        timer = setTimeout(() => func.call(this, ...args), delay);
    }
}
<!-- 节流主要应用在监听滚动事件上 -->
function throttle (func, delay) {
    let timer = null;
    return function (...args) {
        if (timer) return;
        timer = setTimout(() => {
            func.apply(this, args);
            timer = null;
        }, delay)
    }
}
<!-- 利用定时器是首次不执行的写法,如果需要首次执行则可以利用时间戳 -->
function _throttle (func, delay) {
    let prev = 0;
    return function (...args) {
        const curr = new Date().valueof();
        if (curr - prev > delay) {
            func.apply(this, args);
            prev = curr;
        }
    }
}
<!-- 时间戳 + 定时器 的节流写法 -->
function throttle (func, wait) {
    let prev = 0;
    let timer = null;
    return function (...args) {
        const curr = new Date().valueof();
        // 第一次点击和间隔内的点击都靠时间戳判定触发
        if (curr - prev > wait) {
            clearTimeout(timer);
            timer = null;
            prev = curr;
            func.apply(this, args);
        }
        // 最后一次点击
        else {
            timer = setTimeout(() => {
                func.apply(this, args)
            })
        }
    }
}
  1. 实现发布者,订阅者
class PubSub {
    constructor() {
        this.subscribles = [];
    }
    subscrible (subscribleName, callback) {
        this.subscribles.push(callback);
    }
    unSubscrible (subscribleName, callback) {
        if (this.subscribles[subscribleName]) {
            const index = this.subscribles.indexOf(callback);
            this, this.subscribles.splice(index, 1);
        }
    }
    publish (subscribleName, ...args) {
        this, this.subscribles.forEach((callback) => callback(...args))
    }
}
const [sub1, sub2] = [(...arg) => console.log("sub1", ...arg), (...arg) => console.log("sub2", ...arg)];
const pubSub = new PubSub();
pubSub.subscrible("SUB", sub1)
pubSub.subscrible("SUB", sub2)
pubSub.publish("SUB", 1, 2, 3) // sub1 1 2 3 \n sub2 1 2 3
  1. 实现 map 方法
Array.prototype._map = function (callback, thisVal) {
    const arr = this;
    const res = [];
    for (const [i, item] of arr.entries()) {
        res[i] = callback.call(thisVal, item, i, arr);
    }
    return res;
}
<!-- [0,2,4] -->
console.log([0, 1, 2].map((num, i) => num + i))
console.log([0, 1, 2]._map((num, i) => num + i))
  1. 实现 reduce
Array.prototype._reduce = function (callback, accmulate) {
    const arr = this;
    const len = arr.length;
    let i = accmulate ? 0 : 1;
    for (; i < len; i++) {
        accmulate = callback(accmulate || arr[0], arr[i], i, arr);
    }
    return accmulate;
}
// 11/0/1*2/1/2*3/2/3*
console.log([1, 2, 3]._reduce((prev, curr, i, arr) => prev + curr + "/" + String(i) + "/" + String(arr[i]) + "*", 10));
console.log([1, 2, 3].reduce((prev, curr, i, arr) => prev + curr + "/" + String(i) + "/" + String(arr[i]) + "*", 10));
  1. 实现数组拍平
const multiArr = [1, 2, [3, 4, [5], [[[6]]], [[7], 8], 9], 10];
const flat = (arr) => {
    let res = [];
    const len = arr.length;
    for (let i = 0; i < len; i++) {
        if (Array.isArray(arr[i])) res = res.concat(flat(arr[i]));
        else res.push(arr[i]);
    }
    return res
}
// [1,2,3,4,5,6,7,8,9,10]
console.log(multiArr.flat(Infinity));
console.log(flat(multiArr))
  1. 实现函数柯理化
    // 参数个数
    if (args.length >= fn.length) return fn(...args);
    return function (..._args) {
        // 利用闭包保存 fn 和之前参数 args,递归生成新函数
        return carry(fn, ...args, ..._args);
    }
}
const add = (a, b, c) => a + b + c;
console.log(carry(add, 1, 2, 3))
console.log(carry(add)(2, 3, 4))
console.log(carry(add)(1, 2))
console.log(carry(add)(1)(2)(3))
  1. 实现 promise.all 和 promise.race
function promiseAll (promiseQuene = []) {
    return new Promise((resolve, reject) => {
        const len = promiseQuene.length;
        const result = [];
        for (let i = 0; i < len; i++) {
            // 防止队列里不是promise,从而缺失then方法后报错
            Promise.resolve(promiseQuene[i]).then(res => {
                result.push(res);
                if (result.length === len) resolve(result);
            }).catch(err => reject(err));
        }
    })
}

function promiseRace (promiseQuene = []) {
    return new Promise((resolve, reject) => {
        for (let i = 0; i < promiseQuene.length; i++) {
            Promise.resolve(promiseQuene[i]).then(res => resolve(res)).catch(err => reject(err))
        }
    })
}

const [p1, p2] = [
    new Promise((resolve) => setTimeout(() => resolve("p1-1000"), 1000)),
    new Promise((resolve) => setTimeout(() => resolve("p2-4000"), 4000))
]

// [ 'p1-1000', 'p2-4000' ]
promiseAll([p1, p2]).then(result => console.log(result))
  1. 实现深拷贝、浅拷贝
<!-- 浅拷贝是按位拷贝对象,他会创建一个新对象,这个对象有原始对象属性值的一份精准拷贝 -->
<!-- 如果属性值是基本类型,拷贝的是值,如果属性值是引用类型,拷贝的就是内存地址 -->
<!-- 浅拷贝有:Object.assign, slice, concat -->
function shallowClone (obj) {
    const newObj = Object.create(null);
    for (const item in obj) newObj[item] = obj[item];
}
<!-- JSON.stringfy 实现深拷贝的缺点有:循环引用对象执行方法会抛错,以 Symbol 类型为属性值的属性都会被忽略掉,缺少针对其他的内置构造函数的兼容,如 Function、RegExp、Date、Set、Map -->
function deeepClone (obj) {
    const newObj = Object.create(null);
    for (const item in obj) {
        const type = Object.prototype.toString.call(obj[item]).slice(8, -1);
        if (type === "Array") newObj[item] = obj[item].map((_item) => deeepClone(_item))
        if (type === "Object") newObj[item] = deeepClone(obj[item])
        else newObj[item] = obj[item];
    }
    return newObj;
}
  1. 实现 promise
<!-- promise 是一个对象,保存着未来将要结束的事件,代表一个异步操作,有三种状态 -->
class _Promise {
    constructor(callback) {
        // promise 回调只接受一个参数,多个可采用数组或对象形式
        this.msg = null;
        this.status = "pending";
        callback(this.resolve.bind(this), this.reject.bind(this))
    }
    resolve (val) {
        this.status = "fulfilled";
        this.msg = val;
    }
    reject (err) {
        this.status = "rejected";
        this.msg = err
    }
    then (fulfilled, rejected) {
        this.status === "fulfilled" && fulfilled(this.msg)
        this.status === "rejected" && rejected(this.msg)
    }
}
const newPromise = new _Promise((resolve, reject) => {
    try {
        // resolve("promise done")
        throw new Error("fake error")
    } catch (err) {
        reject(err)
    }
});
newPromise.then((res) => console.log(res), (err) => console.log(err))
  1. promise 时序打印
setTimeout(() => {
    console.log("setTimeout1");
    new Promise((resolve) => {
        console.log("promise1_in_setTimeout1");
        resolve();
    }).then(() => {
        setTimeout(() => console.log("setTimeout2_in_then_of_promise1"), 1000);
        console.log("then_in_promise1_of_setTimeout1")
    })
}, 1000);

async function async1() {
    // 函数内-宏任务
    console.log('async1_start');
    // 注意这里生成一个promise
    await async2();
    // promise内是宏任务,then内是微任务
    console.log('async1_end');
}
async function async2() {
    console.log('async2');
}
async1();
console.log("script_start");
new Promise((resolve) => {
    console.log("promise2");
    resolve();
    console.log("promise2_end");
}).then(() => {
    console.log("then_in_promise2")
})
setTimeout(() => console.log("setTimeout2"), 1000);
console.log("script_end");
// JS是单线程执行的语言,在异步模式下,创建异步任务主要分为宏任务与微任务两种
// 不进入主线程,而是进入任务队列,当主线程中的任务执行完毕,
// 就从任务队列中取出任务放进主线程中来进行执行。由于主线程不断重复的获得任务、
// 执行任务、再获取再执行,所以者种机制被叫做事件循环
// 宏任务一般由宿主(node,浏览器)发起如:script,setTimeout,微任务是由js本身发起如:promise,nectTick,先同步任务再微任务后宏任务
// async1_start => async2 => script_start => promise2 => promise2_end => script_end
// ...开始第一轮循环机制里的 微任务 async1_end => then_in_promise2
// ...开始第一轮循环机制里 宏任务 setTimeout1
// ...开始第二轮循环机制 即第一轮循环机制里的 第一个宏任务里的微任务 promise1_in_setTimeout1 => then_in_promise1_of_setTimeout1
// ...开始第二轮循环机制 即第一轮循环机制里的 第二个宏任务 setTimeout2 
// ...开始第三轮循环机制 即第二轮循环机制里的 第一个微任务里的 第一个宏任务 setTimeout2_in_then_of_promise1 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值