常见JS代码实现

  1. 用js打印一个乘法表
  2. 斐波那契数列函数(30以内)
  3. 手写Promise、手写防抖节流、手写vue响应式、手写算法
  4. 不用sort实现排序比如输入[3,2,6,9,1,4,8] 返回排序后的数组 - 冒泡,快排(空间时间复杂度)
  5. 用js实现一个permute函数,输入数字123, 打印出这三个数字的全排列(递归和回溯)
  6. 代码笔试题

手写对象继承,闭包,原型链,作用域,this指向,对象继承,深浅拷贝,进程线程,同步异步,防抖节流

单行写一个评级组件:"★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate);

实现防抖和节流

function debounce(fn) {
    let timeout = null; // 创建一个标记用来存放定时器的返回值
    return function() {
        clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
        timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
            fn.apply(this, arguments)
        }, 500)
    }
}
function sayHi() {
 console.log('防抖成功');
}
document.getElementById('inp').addEventListener('input', debounce(sayHi)) // 防抖

function throttle(fn) {
    let canRun = true // 通过闭包保存一个标记
    return function() {
        if (!canRun) return; // 在函数开头判断标记是否为 true,不为 true 则 return
        canRun = false // 立即设置为 false
        setTimeout(() => {
            fn.apply(this, arguments); // 将外部传入的函数的执行放在 setTimeout 中
            canRun = true; // 最后在 setTimeout 执行完毕后再把标记设置为 true(关键) 表示可以执行下一次循环了。当定时器没有执行的时候标记永远是 false,在开头被return 掉
        }, 500)
    }
}
function sayHi(e) {
 console.log(e.target.innerWidth, e.target.innerHeight)
}
window.addEventListener('resize', throttle(sayHi))

堆栈内存 + 闭包作用域

// example 1
var a = 0,
    b = 0;
function A(a){
    A = function(b) {
        alert(a + b++);
    }
    alert(a++);
}
A(1); // '1'
A(2); // '4'
console.log(a); // 0
console.log(b); // 0

// example 2
let a = {},
    b = {n: 1},
    c = {m: 2};
a[b] = 'Hello';
a[c] = 'World';
console.log(a[b]); // World,key会转化成字符串[Obejct object]

// example 3
let test = (function(i) {
    return function(){ alert(i *= 2); }
})(2);
test(5); // '4',alert弹出的会转化成字符串

// example 3
var a =0, b = 0;
function A(a) {
    A = function (b) {
        alert(a + b++);
    };
    alert(a++);
}
A(1); // '1',执行完 `stack` 不销毁,a = 2
A(2); // '4'

深浅拷贝

function deepClone(obj){
    // 过滤特殊情况
    if (obj == null) return null;
    if (obj instanceof RegExp) return new RegExp(obj);
    if (obj instanceof Date) return new Date(obj);
    if (typeof obj !== "object") return obj;
    if (typeof obj === "function") return new obj.construtor; // Funciton 克隆,待补充。。。。。。
    
    // 不直接粗昂见空对象目的:克隆的结果和之前保持相同的所属类
    let newObj = new obj.construtor;
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = deepClone(obj[key]);
        }
    }
}

面向对象(阿里)

function Foo() {
    getName = function (){
        console.log(1);
    }
    return this;
}
Foo.getName = function () {
    console.log(2);
}
Foo.prototype.getName = function (){
    console.log(3);
}
var getName = function () {
    console.log(4);
}
function getName() {
    console.log(5);
}

Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1,window.getName()
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); // 3

EventLoop(头条)

事件循环:同步、异步、宏任务、微任务

宏任务:定时器、事件绑定

微任务:promise、async、await、ajax

  • 浏览器是多线程的
  • JavaScript 是单线程 => 浏览器只给了其一个线程来渲染
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start');
setTimeout(function() {
    console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');

/*
 'script start'
    'async1 start'
    'async2'
    'promise1'
    'script end'
    'async1 end'
    'promise2'
    // 等待浏览器反应时间后
    'setTimeout'
*/

使下面输出1

var a = ?;
if(a == 1 && a == 2 && a == 3){
 console.log(1);
}

var a = {
 toString = function*(){
  yield 1;
  yield 2;
  yield 3;
 }
}

JS模拟实现双向数据绑定

<div>
   姓名:<span id="spanName"></span> <br />
   <input type="text" id="inputName" />
</div>
let obj = {name: ''};
// definProperty
let newObj = JSON.parse(JSON.stringify(obj));
Object.defineProperty(obj, 'name', {
    get() {
        return newObj.name;
    },
    set(val) {
        if(val === newObj.name) return;
        newObj.name = val;
        observer();
    }
})
// proxy
obj = new Proxy(obj, {
    get(target, prop) {
        return target[prop];
    },
    set(target, prop, value) {
        target[prop] = value;
    }
})
// 共用
function observer() {
    spanName.innerHTML = obj.name;
    inputName.value = obj.name
}
observer();

// 当输入时,改变展示文本
inputName.oninput = function() {
    obj.name = this.value;
}

输入m.n参数,获取一个m长度的都是n的数组(不能用循环)

// 递归实现
function getArr(m, n){
    var arr = [];
    function addArr(){
        if (arr.length == m) return arr; // 递归结束条件 
        arr.push(n);
        return addArr(); // 这里必须要写return,否则输出为undefined
    }
    return addArr();
}
console.log(getArr(3, 0));

随机生成长度为n,且值在[min-max]范围内

function generateRandomArr(n, min, max) {
    var arr = [];
    for (var i = 0; i < n; i++) {
        var random = Math.floor(Math.random() * (max - min + 1) + min);
        arr.push(random);
    }
    return arr;
}

实现MVVM

必要函数:

  1. 实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
  2. 实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
  3. 实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图 4、mvvm入口函数,整合以上三者
// 创建一个Mvvm构造函数
// 这里用es6方法将options赋一个初始值,防止没传,等同于options || {}
function Mvvm(options = {}) {   
    // vm.$options Vue上是将所有属性挂载到上面
    // 所以我们也同样实现,将所有属性挂载到了$options
    this.$options = options;
    // this._data 这里也和Vue一样
    let data = this._data = this.$options.data;
    
    // 数据劫持
    observe(data);
}

输入m.n参数,获取一个m长度的都是n的数组,不能用循环

递归实现深拷贝

function deepClone(source){
  const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
  for(let keys in source){ // 遍历目标
    if(source.hasOwnProperty(keys)){
      if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下
        targetObj[keys] = source[keys].constructor === Array ? [] : {};
        targetObj[keys] = deepClone(source[keys]);
      }else{ // 如果不是,就直接赋值
        targetObj[keys] = source[keys];
      }
    } 
  }
  return targetObj;
}

js中删除两个数组中id相同的对象

const arr1 = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }];
const arr2 = [{ id: 1 }, { id: 2 }, { id: 3 }];

const idList = arr2.map(v => v.id)
const newList = arr1.filter(item => {
  return !idList.includes(item.id)
})
console.log(newList)

js 数组对象的某个属性排序

list.sort((a, b) => { return a.position - b.position 

模拟实现JS的call和apply方法

// 浏览器环境 非严格模式
function getGlobalObject(){
    return this;
}
function generateFunctionCode(argsArrayLength){
    var code = 'return arguments[0][arguments[1]](';
    for(var i = 0; i < argsArrayLength; i++){
        if(i > 0){
            code += ',';
        }
        code += 'arguments[2][' + i + ']';
    }
    code += ')';
    return code;
}
Function.prototype.applyFn = function apply(thisArg, argsArray){
    if(typeof this !== 'function'){
        throw new TypeError(this + ' is not a function');
    }
    if(typeof argsArray === 'undefined' || argsArray === null){
        argsArray = [];
    }
    if(argsArray !== new Object(argsArray)){
        throw new TypeError('CreateListFromArrayLike called on non-object');
    }
    if(typeof thisArg === 'undefined' || thisArg === null){
        thisArg = getGlobalObject();
    }
    thisArg = new Object(thisArg);
    var __fn = '__' + new Date().getTime();
    var originalVal = thisArg[__fn];
    var hasOriginalVal = thisArg.hasOwnProperty(__fn);
    thisArg[__fn] = this;
    var code = generateFunctionCode(argsArray.length);
    var result = (new Function(code))(thisArg, __fn, argsArray);
    delete thisArg[__fn];
    if(hasOriginalVal){
        thisArg[__fn] = originalVal;
    }
    return result;
};
Function.prototype.callFn = function call(thisArg){
    var argsArray = [];
    var argumentsLength = arguments.length;
    for(var i = 0; i < argumentsLength - 1; i++){
        argsArray[i] = arguments[i + 1];
    }
    return this.applyFn(thisArg, argsArray);
}

模拟实现ES5的apply、call、bind方法

//简单模拟Symbol属性
function jawilSymbol(obj) {
    var unique_proper = "00" + Math.random();
    if (obj.hasOwnProperty(unique_proper)) {
        arguments.callee(obj)//如果obj已经有了这个属性,递归调用,直到没有这个属性
    } else {
        return unique_proper;
    }
}
//原生JavaScript封装apply方法,第五版
Function.prototype.applyFive = function(context) {
    var context = context || window
    var args = arguments[1] //获取传入的数组参数
    var fn = jawilSymbol(context);
    context[fn] = this //假想context对象预先不存在名为fn的属性
    if (args == void 0) { //没有传入参数直接执行
        return context[fn]()
    }
    var fnStr = 'context[fn]('
    for (var i = 0; i < args.length; i++) {
        //得到"context.fn(arg1,arg2,arg3...)"这个字符串在,最后用eval执行
        fnStr += i == args.length - 1 ? args[i] : args[i] + ','
    }
    fnStr += ')'
    var returnValue = eval(fnStr) //还是eval强大
    delete context[fn] //执行完毕之后删除这个属性
    return returnValue
}
//简单模拟call函数
Function.prototype.callOne = function(context) {
    return this.applyFive(([].shift.applyFive(arguments)), arguments)
    //巧妙地运用上面已经实现的applyFive函数
}

//简单模拟bind函数
Function.prototype.bind = Function.prototype.bind || function (context) {
    var me = this;
    var args = Array.prototype.slice.callOne(arguments, 1);
    var F = function () {};
    F.prototype = this.prototype;
    var bound = function () {
        var innerArgs = Array.prototype.slice.callOne(arguments);
        var finalArgs = args.concat(innerArgs);
        return me.applyFive(this instanceof F ? this : context || this, finalArgs);
    }
    bound.prototype = new F();
    return bound;
}

根据promiseA+实现一个自己的promise

function resolvePromise(promise2,x,resolve,reject){
    //判断x是不是promise
    //规范中规定:我们允许别人乱写,这个代码可以实现我们的promise和别人的promise 进行交互
    if(promise2 === x){//不能自己等待自己完成
        return reject(new TypeError('循环引用'));
    };
    // x是除了null以外的对象或者函数
    if(x !=null && (typeof x === 'object' || typeof x === 'function')){
        let called;//防止成功后调用失败
        try{//防止取then是出现异常  object.defineProperty
            let then = x.then;//取x的then方法 {then:{}}
            if(typeof then === 'function'){//如果then是函数就认为他是promise
                //call第一个参数是this,后面的是成功的回调和失败的回调
                then.call(x,y => {//如果Y是promise就继续递归promise
                    if(called) return;
                    called = true;
                    resolvePromise(promise2,y,resolve,reject)
                },r => { //只要失败了就失败了
                    if(called) return;
                    called = true;
                    reject(r);  
                });
            }else{//then是一个普通对象,就直接成功即可
                resolve(x);
            }
        }catch (e){
            if(called) return;
            called = true;
            reject(e)
        }
    }else{//x = 123 x就是一个普通值 作为下个then成功的参数
        resolve(x)
    }

}

class Promise {
    constructor (executor){
        //默认状态是等待状态
        this.status = 'panding';
        this.value = undefined;
        this.reason = undefined;
        //存放成功的回调
        this.onResolvedCallbacks = [];
        //存放失败的回调
        this.onRejectedCallbacks = [];
        let resolve = (data) => {//this指的是实例
            if(this.status === 'pending'){
                this.value = data;
                this.status = "resolved";
                this.onResolvedCallbacks.forEach(fn => fn());
            }
 
        }
        let reject = (reason) => {
            if(this.status === 'pending'){
                this.reason = reason;
                this.status = 'rejected';
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        }
        try{//执行时可能会发生异常
            executor(resolve,reject);
        }catch (e){
            reject(e);//promise失败了
        }
       
    }
    then(onFuiFilled,onRejected){ 
        //防止值得穿透 
        onFuiFilled = typeof onFuiFilled === 'function' ? onFuiFilled : y => y;
        onRejected = typeof onRejected === 'function' ? onRejected :err => {throw err;}        
        let promise2;//作为下一次then方法的promise
       if(this.status === 'resolved'){
           promise2 = new Promise((resolve,reject) => {
               setTimeout(() => {
                  try{
                        //成功的逻辑 失败的逻辑
                        let x = onFuiFilled(this.value);
                        //看x是不是promise 如果是promise取他的结果 作为promise2成功的的结果
                        //如果返回一个普通值,作为promise2成功的结果
                        //resolvePromise可以解析x和promise2之间的关系
                        //在resolvePromise中传入四个参数,第一个是返回的promise,第二个是返回的结果,第三个和第四个分别是resolve()和reject()的方法。
                        resolvePromise(promise2,x,resolve,reject)
                  }catch(e){
                        reject(e);
                  } 
               },0)
           }); 
       } 
       if(this.status === 'rejected'){
            promise2 = new Promise((resolve,reject) => {
                setTimeout(() => {
                    try{
                        let x = onRejected(this.reason);
                        //在resolvePromise中传入四个参数,第一个是返回的promise,第二个是返回的结果,第三个和第四个分别是resolve()和reject()的方法。
                        resolvePromise(promise2,x,resolve,reject)
                    }catch(e){
                        reject(e);
                    }
                },0)

            });
       }
       //当前既没有完成也没有失败
       if(this.status === 'pending'){
           promise2 = new Promise((resolve,reject) => {
               //把成功的函数一个个存放到成功回调函数数组中
                this.onResolvedCallbacks.push( () =>{
                    setTimeout(() => {
                        try{
                            let x = onFuiFilled(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;//调用then后返回一个新的promise
    }
    catch (onRejected) {
        // catch 方法就是then方法没有成功的简写
        return this.then(null, onRejected);
    }
}
Promise.all = function (promises) {
    //promises是一个promise的数组
    return new Promise(function (resolve, reject) {
        let arr = []; //arr是最终返回值的结果
        let i = 0; // 表示成功了多少次
        function processData(index, data) {
            arr[index] = data;
            if (++i === promises.length) {
                resolve(arr);
            }
        }
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(function (data) {
                processData(i, data)
            }, reject)
        }
    })
}
// 只要有一个promise成功了 就算成功。如果第一个失败了就失败了
Promise.race = function (promises) {
    return new Promise((resolve, reject) => {
        for (var i = 0; i < promises.length; i++) {
            promises[i].then(resolve,reject)
        }
    })
}
// 生成一个成功的promise
Promise.resolve = function(value){
    return new Promise((resolve,reject) => resolve(value);
}
// 生成一个失败的promise
Promise.reject = function(reason){
    return new Promise((resolve,reject) => reject(reason));
}
Promise.defer = Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise( (resolve, reject) =>  {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd
}
module.exports = Promise;
上次更新:
Contributors: jingmin.jiang, kyxiao