前端面经-JS36个手写问题&设计模式&遇到的算法题

1、实现函数原型方法

call

使用一个指定的 this 值和一个或多个参数来调用一个函数。

实现要点:

  • this 可能传入 null;
  • 传入不固定个数的参数;
  • 函数可能有返回值;
Function.prototype.call2 = function (context) {
    var context = context || window;
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }

    var result = eval('context.fn(' + args +')');

    delete context.fn
    return result;
}

apply

apply 和 call 一样,唯一的区别就是 call 是传入不固定个数的参数,而 apply 是传入一个数组。

实现要点:

  • this 可能传入 null;
  • 传入一个数组;
  • 函数可能有返回值;
Function.prototype.apply2 = function (context, arr) {
    var context = context || window;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    } else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }

    delete context.fn
    return result;
}

bind

bind 方法会创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

实现要点:

  • bind() 除了 this 外,还可传入多个参数;
  • bing 创建的新函数可能传入多个参数;
  • 新函数可能被当做构造函数调用;
  • 函数可能有返回值;
Function.prototype.bind2 = function (context) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

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

2、实现数组原型方法

forEach

Array.prototype.forEach2 = 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)  // this 就是当前的数组
    const len = O.length >>> 0  // 后面有解释
    let k = 0
    while (k < len) {
        if (k in O) {
            callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
}

O.length >>> 0 是什么操作?就是无符号右移 0 位,那有什么意义嘛?就是为了保证转换后的值为正整数。其实底层做了 2 层转换,第一是非 number 转成 number 类型,第二是将 number 转成 Uint32 类型。

map

Array.prototype.map2 = 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, res = []
    while (k < len) {
        if (k in O) {
           res[k] = callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
    return res
}

filter

Array.prototype.filter2 = 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,
        res = []
    while (k < len) {
        if (k in O) {
            if (callback.call(thisArg, O[k], k, O)) {
                res.push(O[k])
            }
        }
        k++;
    }
    return res
}

some

+ Array.prototype.some2 = 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);
+           if (callback.call(thisArg, O[k], k, O)) {
+               return true
+           }
        }
        k++;
    }
+   return false
}

reduce

Array.prototype.reduce2 = function(callback, initialValue) {
    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, acc
    
    if (arguments.length > 1) {
        acc = initialValue
    } else {
        // 没传入初始值的时候,取数组中第一个非 empty 的值为初始值
        while (k < len && !(k in O)) {
            k++
        }
        if (k > len) {
            throw new TypeError( 'Reduce of empty array with no initial value' );
        }
        acc = O[k++]
    }
    while (k < len) {
        if (k in O) {
            acc = callback(acc, O[k], k, O)
        }
        k++
    }
    return acc
}

3、设计模式

设计模式

工厂模式

工厂模式是用来创建对象的一种最常用的设计模式,不暴露创建对象的具体逻辑,而是将逻辑封装在一个函数中,那么这个函数就可以被视为一个工厂,工厂模式根据抽象程度的不同可以分为:简单工厂,工厂方法和抽象工厂,接下来,将对简单工厂和工厂方法在JavaScript中的运用举个简单的例子:

简单工厂

简单工厂模式又叫静态工厂模式,由一个工厂对象决定创建某一种产品对象类的实例,主要用来创建同一类对象
比如说,在实际的项目中,我们常常需要根据用户的权限来渲染不同的页面,高级权限的用户所拥有的页面有些是无法被低级权限的用户所查看,所以我们可以在不同权限等级用户的构造函数中,保存该用户能够看到的页面。
总结: 在上面的例子中,UserFactory就是一个简单工厂,在该函数中有3个构造函数分别对应不同的权限的用户,当我们调用工厂函数时,只需要传递superAdmin, admin, user这三个可选参数中的一个获取对应的实例对象

优点: 简单工厂的优点在于,你只需要一个正确的参数,就可以获取到你所需要的对象,而无需知道其创建的具体细节;
缺点: 在函数内包含了所有对象的创建逻辑(构造函数)和判断逻辑的代码,每增加新的构造函数还需要修改判断逻辑代码,我们的对象不是上面的3个而是30个或更多时,这个函数会成为一个庞大的超级函数,便得难以维护,简单工厂只能作用于创建的对象数量较少,对象的创建逻辑不复杂时使用。

工厂方法
工厂方法模式的本意是将实际创建对象的工作推迟到子类中,这样核心类就变成了抽象类,但是在JavaScript中很难像传统面向对象那样去实现创建抽象类,所以在JavaScript中我们只需要参考它的核心思想即可,我们可以将工厂方法看作是一个实例化对象的工厂类
比如说上面的例子,我们用工厂方法可以这样写,工厂方法我们只把它看作是一个实例化对象的工厂,它只做实例化对象这一件事情,我们采用安全模式创建对象
总结: 在简单工厂中,如果我们新增加一个用户类型,需要修改两个地方的代码,一个是增加新的用户构造函数,一个是在逻辑判断中增加对新的用户的判断,而在抽象工厂方法中,我们只需要在UserFactory.prototype中添加就可以啦。​

单例模式

定义:是保证一个类只有一个实例,并且提供一个访问它的全局访问点。
需求:一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的window对象、登录浮窗等。
实现:用一个变量标识当前是否已经为某个类创建过对象,如果是,则在下一次获取这个类的实例时,直接返回之前创建的对象。
优点
可以用来划分命名空间,减少全局变量的数量
可以被实例化,且实例化一次,再次实例化生成的也是第一个实例

下面举个例子,在js中,我们可以使用闭包来创建实现这种模式:

var single = (function(){
  var unique;
  function Construct(){}

  function getInstance(){
    if(unique===undefined){
      unique = new Construct();
    }
    return unique;
  }
  return {
    getInstance : getInstance
  }
})();

总结: 在上面的代码中,我们可以使用single.getInstance来获取到例,并且每次调用均获取到同一个单例。

代理模式

代理模式主要是为其他对象提供一种代理以控制对这个对象的访问,主要解决在直接访问对象时带来的问题。
比如说:要访问的对象在远程的机器上,在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
代理模式最基本的形式是对访问进行控制,代理对象和另一个对象(本体)实现的是同样的接口。
实际上工作还是本体在做,它才是负责执行所分派的任务的那个对象或类,代理对象所做的不外乎节制对本体的访问,代理对象并不会在另一对象的基础上添加方法或修改其方法,也不会简化那个对象的接口,它实现的接口与本体完全相同,所有对它进行的方法调用都会被传递给本体。

总结: 在上面的代码中,Proxy可以控制对真正被代理对象的一个访问。在代理模式中,比较常见的就是虚拟代理,虚拟代理用于控制对那种创建开销很大的本体的访问,它会把本体的实例化推迟到有方法被调用的时候。
比如说,现在我们假设PublicLibrary的实例化很慢,不能在网页加载的时候立即完成,我们可以为其创建一个虚拟代理,让它把PublicLibrary的实例化推迟到必要的时候,比如说我们在前端中经常用到的图片懒加载,就可以用虚拟代理;

观察者模式

如果大家学过一些像vue,react这些框架,相信大家对观察者模式一定很熟悉,现在很多mvvm框架都用到了观察者模式这个思想,观察者模式又叫做发布—订阅模式。
它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知和更新,观察者模式提供了一个订阅模型,其中对象订阅事件并在发生时得到通知,这种模式是事件驱动的编程基石,它有利益于良好的面向对象的设计
定义: 对象间的一种一对多的依赖关系。
需求: 当一个对象的状态发生变化时,所有依赖于他的对象都将得到通知。
优点: 时间上的解耦,对象之间的解耦。
实现

指定好谁充当发布者;
给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者;
发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数。

下面举个例子,比如我们给页面中的一个dom节点绑定一个事件,其实就可以看做是一种观察者模式:

总结: 在上面的例子中,需要监听用户点击 document.body 的动作,但是我们是没办法预知用户将在什么时候点击的,因此我们订阅了 document.body 的 click 事件,当 body 节点被点击时,body 节点便会向订阅者发布 “Hello World” 消息。

策略模式

策略模式指的是定义一些列的算法,把他们一个个封装起来,目的就是将算法的使用与算法的实现分离开来,避免多重判断条件,更具有扩展性。
下面也是举个例子,现在超市有活动,vip为5折,老客户3折,普通顾客没折,计算最后需要支付的金额,如果不使用策略模式,我们的代码可能和下面一样:

总结: 在上面的代码中,通过策略模式,使得客户的折扣与算法解藕,又使得修改跟扩展能独立的进行,不影到客户端或其他算法的使用。
当我们的代码中有很多个判断分支,每一个条件分支都会引起该“类”的特定行为以不同的方式作出改变,这个时候就可以使用策略模式,可以改进我们代码的质量,也更好的可以进行单元测试。

模块模式

模块模式可以指定类想暴露的属性和方法,并且不会污染全局。采用闭包的形式,实现如下:

var Person = (function() {
    var name = 'xxx'
    function sayName() {
        console.log(name)
    }
    return{
        name: name,
        sayName: sayName
    }
})()

构造函数模式和混合模式

构造函数和混合模式就是js中继承的两种实现方式,前者通过构造函数的形式定义类,通过new新增实例。而后者是将构造函数的引用属性和方法放到其原型上,子类是父类原型的一个实例。

4、尾递归优化

其实就是一个把递归变成循环的过程嘛。将尾递归优化成迭代循环:

  1. 将参数提取出来,成为迭代变量。原来的参数则用来初始化迭代变量。
  2. 创建一个迭代函数,迭代函数只用来只用来更新迭代变量。
  3. 将原来函数的里面所代码(不包括我们上面的迭代函数和迭代变量初始化)包在一个 while (true) 迭代循环里面。Tip:加一个 label 用于标识循环。
  4. 递归终止的 return 不变,尾递归的 return 替换成迭代函数,并且 continue 掉上面的迭代循环。Tip:上面的 label 在这里用。

尾递归代码:

function fact(n, r) { // <= 这里把 n, r 作为迭代变量提出来
    if (n <= 0) {
        return 1 * r; // <= 递归终止
    } else {
        return fact(n - 1, r * n); // <= 用迭代函数替代 fact。
    }
}

转换后得到的代码:

function fact(_n, _r) { // <= _n, _r 用作初始化变量
    var n = _n;
    var r = _r; // <= 将原来的 n, r 变量提出来编程迭代变量
    function _fact(_n, _r) { // <= 迭代函数非常简单,就是更新迭代变量而已
        n = _n;
        r = _r;
    }
    _fact_loop: while (true) { // <= 生成一个迭代循环
        if (n <= 0) {
            return r;
        } else {
            _fact(n - 1, r * n); continue _fact_loop; // <= 执行迭代函数,并且进入下一次迭代
        }
    }
}

5、数组去重

ES5 实现:

function unique(arr) {
    var res = arr.filter(function(item, index, array) {
        return array.indexOf(item) === index
    })
    return res
}

ES6 实现:

var unique = arr => [...new Set(arr)]

6、数组扁平化

ES5 实现:递归。

function flatten(arr) {
    var result = [];
    for (var i = 0, len = arr.length; i < len; i++) {
        if (Array.isArray(arr[i])) {
            result = result.concat(flatten(arr[i]))
        } else {
            result.push(arr[i])
        }
    }
    return result;
}

ES6 实现:

function flatten(arr) {
    while (arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
    }
    return arr;
}

7、深浅拷贝

浅拷贝:只考虑对象类型。

function shallowCopy(obj) {
    if (typeof obj !== 'object') return
    
    let newObj = obj instanceof Array ? [] : {}
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key]
        }
    }
    return newObj
}

简单版深拷贝:只考虑普通对象属性,不考虑内置对象和函数。

function deepClone(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
        }
    }
    return newObj;
}

8、事件总线(发布订阅模式)

class EventEmitter {
    constructor() {
        this.cache = {}
    }
    on(name, fn) {
        if (this.cache[name]) {
            this.cache[name].push(fn)
        } else {
            this.cache[name] = [fn]
        }
    }
    off(name, fn) {
        let tasks = this.cache[name]
        if (tasks) {
            const index = tasks.findIndex(f => f === fn || f.callback === fn)
            if (index >= 0) {
                tasks.splice(index, 1)
            }
        }
    }
    emit(name, once = false, ...args) {
        if (this.cache[name]) {
            // 创建副本,如果回调函数内继续注册相同事件,会造成死循环
            let tasks = this.cache[name].slice()
            for (let fn of tasks) {
                fn(...args)
            }
            if (once) {
                delete this.cache[name]
            }
        }
    }
}

// 测试
let eventBus = new EventEmitter()
let fn1 = function(name, age) {
	console.log(`${name} ${age}`)
}
let fn2 = function(name, age) {
	console.log(`hello, ${name} ${age}`)
}
eventBus.on('aaa', fn1)
eventBus.on('aaa', fn2)
eventBus.emit('aaa', false, '布兰', 12)
// '布兰 12'
// 'hello, 布兰 12'

9、解析 URL 参数为对象

function parseParam(url) {
    const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
    const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
    let paramsObj = {};
    // 将 params 存到对象中
    paramsArr.forEach(param => {
        if (/=/.test(param)) { // 处理有 value 的参数
            let [key, val] = param.split('='); // 分割 key 和 value
            val = decodeURIComponent(val); // 解码
            val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
    
            if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
                paramsObj[key] = [].concat(paramsObj[key], val);
            } else { // 如果对象没有这个 key,创建 key 并设置值
                paramsObj[key] = val;
            }
        } else { // 处理没有 value 的参数
            paramsObj[param] = true;
        }
    })
    
    return paramsObj;
}

10、字符串模板

function render(template, data) {
    const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
    if (reg.test(template)) { // 判断模板里是否有模板字符串
        const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段
        template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
        return render(template, data); // 递归的渲染并返回渲染后的结构
    }
    return template; // 如果模板没有模板字符串直接返回
}

11、图片懒加载

let imgList = [...document.querySelectorAll('img')]
let length = imgList.length

// 修正错误,需要加上自执行
- const imgLazyLoad = function() {
+ const imgLazyLoad = (function() {
    let count = 0
    
   return function() {
        let deleteIndexList = []
        imgList.forEach((img, index) => {
            let rect = img.getBoundingClientRect()
            if (rect.top < window.innerHeight) {
                img.src = img.dataset.src
                deleteIndexList.push(index)
                count++
                if (count === length) {
                    document.removeEventListener('scroll', imgLazyLoad)
                }
            }
        })
        imgList = imgList.filter((img, index) => !deleteIndexList.includes(index))
   }
- }
+ })()

// 这里最好加上防抖处理
document.addEventListener('scroll', imgLazyLoad)

12、函数柯里化

function curry(fn) {
    let judge = (...args) => {
        if (args.length == fn.length) return fn(...args)
        return (...arg) => judge(...args, ...arg)
    }
    return judge
}

13、偏函数

什么是偏函数?偏函数就是将一个 n 参的函数转换成固定 x 参的函数,剩余参数(n - x)将在下次调用全部传入。举个例子:

function add(a, b, c) {
    return a + b + c
}
let partialAdd = partial(add, 1)
partialAdd(2, 3)

发现没有,其实偏函数和函数柯里化有点像,所以根据函数柯里化的实现,能够能很快写出偏函数的实现:

function partial(fn, ...args) {
    return (...arg) => {
        return fn(...args, ...arg)
    }
}

14、JSONP

SONP 核心原理:script 标签不受同源策略约束,所以可以用来进行跨域请求,优点是兼容性好,但是只能用于 GET 请求;

const jsonp = ({ url, params, callbackName }) => {
    const generateUrl = () => {
        let dataSrc = ''
        for (let key in params) {
            if (params.hasOwnProperty(key)) {
                dataSrc += `${key}=${params[key]}&`
            }
        }
        dataSrc += `callback=${callbackName}`
        return `${url}?${dataSrc}`
    }
    return new Promise((resolve, reject) => {
        const scriptEle = document.createElement('script')
        scriptEle.src = generateUrl()
        document.body.appendChild(scriptEle)
        window[callbackName] = data => {
            resolve(data)
            document.removeChild(scriptEle)
        }
    })
}

15、AJAX

const getJSON = function(url) {
    return new Promise((resolve, reject) => {
        const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp');
        xhr.open('GET', url, false);
        xhr.setRequestHeader('Accept', 'application/json');
        xhr.onreadystatechange = function() {
            if (xhr.readyState !== 4) return;
            if (xhr.status === 200 || xhr.status === 304) {
                resolve(xhr.responseText);
            } else {
                reject(new Error(xhr.responseText));
            }
        }
        xhr.send();
    })
}

16、实现 new 关键字

new 运算符用来创建用户自定义的对象类型的实例或者具有构造函数的内置对象的实例。

实现要点:

  1. new 会产生一个新对象;
  2. 新对象需要能够访问到构造函数的属性,所以需要重新指定它的原型;
  3. 构造函数可能会显示返回;
function objectFactory() {
    var obj = new Object()
    Constructor = [].shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    var ret = Constructor.apply(obj, arguments);
    
    // ret || obj 这里这么写考虑了构造函数显示返回 null 的情况
    return typeof ret === 'object' ? ret || obj : obj;
};

使用:

function person(name, age) {
    this.name = name
    this.age = age
}
let p = objectFactory(person, '布兰', 12)
console.log(p)  // { name: '布兰', age: 12 }

17、实现 instanceof 关键字

instanceof 就是判断构造函数的 prototype 属性是否出现在实例的原型链上。

function instanceOf(left, right) {
    let proto = left.__proto__
    while (true) {
        if (proto === null) return false
        if (proto === right.prototype) {
            return true
        }
        proto = proto.__proto__
    }
}

18、实现 Object.create

Object.create2 = function(proto, propertyObject = undefined) {
    if (typeof proto !== 'object' && typeof proto !== 'function') {
        throw new TypeError('Object prototype may only be an Object or null.')
    if (propertyObject == null) {
        new TypeError('Cannot convert undefined or null to object')
    }
    function F() {}
    F.prototype = proto
    const obj = new F()
    if (propertyObject != undefined) {
        Object.defineProperties(obj, propertyObject)
    }
    if (proto === null) {
        // 创建一个没有原型对象的对象,Object.create(null)
        obj.__proto__ = null
    }
    return obj
}

19、实现 Object.assign

Object.assign2 = function(target, ...source) {
    if (target == null) {
        throw new TypeError('Cannot convert undefined or null to object')
    }
    let ret = Object(target) 
    source.forEach(function(obj) {
        if (obj != null) {
            for (let key in obj) {
                if (obj.hasOwnProperty(key)) {
                    ret[key] = obj[key]
                }
            }
        }
    })
    return ret
}

20、实现 JSON.stringify

JSON.stringify([, replacer [, space]) 方法是将一个 JavaScript 值(对象或者数组)转换为一个 JSON 字符串。此处模拟实现,不考虑可选的第二个参数 replacer 和第三个参数 space

  • 基本数据类型:

undefined 转换之后仍是 undefined(类型也是 undefined)
boolean 值转换之后是字符串 “false”/“true”
number 类型(除了 NaN 和 Infinity)转换之后是字符串类型的数值
symbol 转换之后是 undefined
null 转换之后是字符串 “null”
string 转换之后仍是string
NaN 和 Infinity 转换之后是字符串 “null”

  • 函数类型:转换之后是 undefined
  • 如果是对象类型(非函数)

如果是一个数组:如果属性值中出现了 undefined、任意的函数以及 symbol,转换成字符串 “null” ;
如果是 RegExp 对象:返回 {} (类型是 string);
如果是 Date 对象,返回 Date 的 toJSON 字符串值;
如果是普通对象;
如果有 toJSON() 方法,那么序列化 toJSON() 的返回值。
如果属性值中出现了 undefined、任意的函数以及 symbol 值,忽略。
所有以 symbol 为属性键的属性都会被完全忽略掉。

  • 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。

21、实现 JSON.parse

介绍 2 种方法实现:

  1. eval 实现;
var json = '{"a":"1", "b":2}';
var obj = eval("(" + json + ")");  // obj 就是 json 反序列化之后得到的对象

但是直接调用 eval 会存在安全问题,如果数据中可能不是 json 数据,而是可执行的 JavaScript 代码,那很可能会造成 XSS 攻击。因此,在调用 eval 之前,需要对数据进行校验。

var rx_one = /^[\],:{}\s]*$/;
var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
var rx_four = /(?:^|:|,)(?:\s*\[)+/g;

if (
    rx_one.test(
        json.replace(rx_two, "@")
            .replace(rx_three, "]")
            .replace(rx_four, "")
    )
) {
    var obj = eval("(" +json + ")");
}
  1. new Function 实现;

Function 与 eval 有相同的字符串参数特性。

var json = '{"name":"小姐姐", "age":20}';
var obj = (new Function('return ' + json))();

22、实现 Promise

实现 Promise 需要完全读懂 Promise A+ 规范,不过从总体的实现上看,有如下几个点需要考虑到:

  • then 需要支持链式调用,所以得返回一个新的 Promise;
  • 处理异步问题,所以得先用 onResolvedCallbacks 和 onRejectedCallbacks 分别把成功和失败的回调存起来;
  • 为了让链式调用正常进行下去,需要判断 onFulfilled 和 onRejected 的类型;
  • onFulfilled 和 onRejected 需要被异步调用,这里用 setTimeout 模拟异步;
  • 处理 Promise 的 resolve;

Promise.resolve

Promsie.resolve(value) 可以将任何值转成值为 value 状态是 fulfilled 的 Promise,但如果传入的值本身是 Promise 则会原样返回它。

Promise.resolve = function(value) {
    // 如果是 Promsie,则直接输出它
    if(value instanceof Promise){
        return value
    }
    return new Promise(resolve => resolve(value))
}

Promise.reject

和 Promise.resolve() 类似,Promise.reject() 会实例化一个 rejected 状态的 Promise。但与 Promise.resolve() 不同的是,如果给 Promise.reject() 传递一个 Promise 对象,则这个对象会成为新 Promise 的值。

Promise.reject = function(reason) {
    return new Promise((resolve, reject) => reject(reason))
}

Promise.all

Promise.all 的规则是这样的:

  • 传入的所有 Promsie 都是 fulfilled,则返回由他们的值组成的,状态为 fulfilled 的新 Promise;
  • 只要有一个 Promise 是 rejected,则返回 rejected 状态的新 Promsie,且它的值是第一个 rejected 的 Promise 的值;
  • 只要有一个 Promise 是 pending,则返回一个 pending 状态的新 Promise;
Promise.all = function(promiseArr) {
    let index = 0, result = []
    return new Promise((resolve, reject) => {
        promiseArr.forEach((p, i) => {
            Promise.resolve(p).then(val => {
                index++
                result[i] = val
                if (index === promiseArr.length) {
                    resolve(result)
                }
            }, err => {
                reject(err)
            })
        })
    })
}

Promise.race

Promise.race 会返回一个由所有可迭代实例中第一个 fulfilled 或 rejected 的实例包装后的新实例。

Promise.race = function(promiseArr) {
    return new Promise((resolve, reject) => {
        promiseArr.forEach(p => {
            Promise.resolve(p).then(val => {
                resolve(val)
            }, err => {
                rejecte(err)
            })
        })
    })
}

Promise.allSettled

Promise.allSettled 的规则是这样:

  • 所有 Promise 的状态都变化了,那么新返回一个状态是 fulfilled 的 Promise,且它的值是一个数组,数组的每项由所有 Promise 的值和状态组成的对象;
  • 如果有一个是 pending 的 Promise,则返回一个状态是 pending 的新实例;
Promise.allSettled = function(promiseArr) {
    let result = []
        
    return new Promise((resolve, reject) => {
        promiseArr.forEach((p, i) => {
            Promise.resolve(p).then(val => {
                result.push({
                    status: 'fulfilled',
                    value: val
                })
                if (result.length === promiseArr.length) {
                    resolve(result) 
                }
            }, err => {
                result.push({
                    status: 'rejected',
                    reason: err
                })
                if (result.length === promiseArr.length) {
                    resolve(result) 
                }
            })
        })  
    })   
}

Promise.any

Promise.any 的规则是这样:

  • 空数组或者所有 Promise 都是 rejected,则返回状态是 rejected 的新 Promsie,且值为 AggregateError 的错误;
  • 只要有一个是 fulfilled 状态的,则返回第一个是 fulfilled 的新实例;
  • 其他情况都会返回一个 pending 的新实例;
Promise.any = function(promiseArr) {
    let index = 0
    return new Promise((resolve, reject) => {
        if (promiseArr.length === 0) return 
        promiseArr.forEach((p, i) => {
            Promise.resolve(p).then(val => {
                resolve(val)
                
            }, err => {
                index++
                if (index === promiseArr.length) {
                  reject(new AggregateError('All promises were rejected'))
                }
            })
        })
    })
}

PS:本文内容大多数来自
作者:大海我来了
链接:https://juejin.cn/post/6946022649768181774
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

23、JS数组求并集,交集和差集

ES7
ES7新增了一个Array.prototype.includes的数组方法,用于返回一个数组是否包含指定元素,结合filter方法。

// 并集
let union = a.concat(b.filter(v => !a.includes(v))) // [1,2,3,4,5]
// 交集
let intersection = a.filter(v => b.includes(v)) // [2]
// 差集
let difference = a.concat(b).filter(v => a.includes(v) && !b.includes(v)) // [1,3]

ES6
ES6中新增的一个Array.from方法,用于将类数组对象和可遍历对象转化为数组。只要类数组有length长度,基本都可以转化为数组。结合Set结构实现数学集求解。

	let a = [1, 2, 3]
    console.log(a)
    let b = [2, 4, 5]
    let aSet = new Set(a)
    let bSet = new Set(b)    
    // 并集
    let union = Array.from(new Set(a.concat(b))) // [1,2,3,4,5]
    console.log(union)
    // 交集
    let intersection = Array.from(new Set(a.filter(v => bSet.has(v)))// [2]
    )
    // 差集
    let differenceNew = Array.from(new Set(a.concat(b).filter(v => aSet.has(v) && !bSet.has(v))) [1,3]
    )
    console.log(differenceNew)

ES5
ES5可以利用filter和indexOf进行数学集操作,但是,由于indexOf方法中NaN永远返回-1,所以需要进行兼容处理

    var a = [1,2,3];
    var b = [2,4,5]; 
    // 并集
    var union = a.concat(b.filter(function(v) {
        return a.indexOf(v) === -1})) // [1,2,3,4,5] 
    // 交集
    var intersection = a.filter(function(v){ return b.indexOf(v) > -1 }) // [2] 
    // 差集
    var difference = a.filter(function(v){ return b.indexOf(v) === -1 })// [1,3] 
    console.log(union)
    console.log(intersection)
    console.log(difference)

24、在js实现矩阵转置

var arr=[[2,4,6,8],[8,9,0,-1],[9,6,2,1]];
    //定义一个新的数组
    var arr2=[];
    for(var i=0;i<arr[0].length;i++){
        arr2[i]=[];
    }
    for(var i=0;i<arr.length;i++){
        for(var j=0;j<arr[i].length;j++){
            arr2[j][i]=arr[i][j];
        }
    }
    //成功!遍历arr2就是一个成功的数组
    for(var i=0;i<arr2.length;i++){
        for(var j=0;j<arr2[i].length;j++){
            document.writeln(arr2[i][j]+"&nbsp");
        }
        document.writeln("<br/>");
    }
//重点在于新二维数组的初始化。为什么要预先fill(0)?提前规定好所需空间,防止系统分配过多空间造成浪费。追问:时间复杂度? O(mn)。空间复杂度?O(1)
var arr2=[];
    for(var i=0;i<arr[0].length;i++){
        arr2[i]=[];
    }

25、反转链表

/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/
function ReverseList(pHead)
{
    // write code here
    var pre = null;
    var cur = pHead;
    var next = null;
    while(cur){
        next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
    return pre;
}
时间复杂度:O(n), 遍历一次链表
空间复杂度:O(1)

26、js 找出数组中重复次数>n的值并输出

//统计一个数组中有多少个不重复的单词:
    // 不用reduce时:
    var arr = ["李","李","设","弟","弟","生","生","李"]; 
    var obj = {}; 
    function getRepeatNum(){        
        for(var i= 0, l = arr.length; i< l; i++){ 
            var item = arr[i]; 
            obj[item] = (obj[item] +1 ) || 1; 
        } 
        return obj; 
    }
    console.log(getRepeatNum());
    // 用reduce时:
    var arr = ["李","李","设","弟","弟","生","生","李"]; 
    function getRepeatNum(){ 
        return arr.reduce(function(prev,next){ 
            prev[next] = (prev[next] + 1) || 1; 
            return prev; 
        },{}); 
    } 
    console.log(getRepeatNum());
    //得到对应obj后在循环一次比较哪个出现次数大n
    for (var i in obj) {
    if (obj[i] > n) {
        console.log(i);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值