关于javascript:前端必会面试题总结

52次阅读

共计 22854 个字符,预计需要花费 58 分钟才能阅读完成。

HTTPS 的特点

HTTPS 的 长处 如下:

  • 应用 HTTPS 协定能够认证用户和服务器,确保数据发送到正确的客户端和服务器;
  • 应用 HTTPS 协定能够进行加密传输、身份认证,通信更加平安,避免数据在传输过程中被窃取、批改,确保数据安全性;
  • HTTPS 是现行架构下最平安的解决方案,尽管不是相对的平安,然而大幅减少了中间人攻打的老本;

HTTPS 的 毛病 如下:

  • HTTPS 须要做服务器和客户端单方的加密个解密解决,消耗更多服务器资源,过程简单;
  • HTTPS 协定握手阶段比拟费时,减少页面的加载工夫;
  • SSL 证书是免费的,性能越弱小的证书费用越高;
  • HTTPS 连贯服务器端资源占用高很多,反对访客稍多的网站须要投入更大的老本;
  • SSL 证书须要绑定 IP,不能再同一个 IP 上绑定多个域名。

Promise.all

形容:所有 promise 的状态都变成 fulfilled,就会返回一个状态为 fulfilled 的数组(所有promisevalue)。只有有一个失败,就返回第一个状态为 rejectedpromise 实例的 reason

实现

Promise.all = function(promises) {return new Promise((resolve, reject) => {if(Array.isArray(promises)) {if(promises.length === 0) return resolve(promises);
            let result = [];
            let count = 0;
            promises.forEach((item, index) => {Promise.resolve(item).then(
                    value => {
                        count++;
                        result[index] = value;
                        if(count === promises.length) resolve(result);
                    }, 
                    reason => reject(reason)
                );
            })
        }
        else return reject(new TypeError("Argument is not iterable"));
    });
}

DOCTYPE(⽂档类型) 的作⽤

DOCTYPE 是 HTML5 中一种规范通用标记语言的文档类型申明,它的目标是 通知浏览器(解析器)应该以什么样(html 或 xhtml)的文档类型定义来解析文档,不同的渲染模式会影响浏览器对 CSS 代码甚⾄ JavaScript 脚本的解析。它必须申明在 HTML ⽂档的第⼀⾏。

浏览器渲染页面的两种模式(可通过 document.compatMode 获取,比方,语雀官网的文档类型是CSS1Compat):

  • CSS1Compat:规范模式(Strick mode),默认模式,浏览器应用 W3C 的规范解析渲染页面。在规范模式中,浏览器以其反对的最高规范出现页面。
  • BackCompat:怪异模式(混淆模式)(Quick mode),浏览器应用本人的怪异模式解析渲染页面。在怪异模式中,页面以一种比拟宽松的向后兼容的形式显示。

代码输入后果

var A = {n: 4399};
var B =  function(){this.n = 9999};
var C =  function(){var n = 8888};
B.prototype = A;
C.prototype = A;
var b = new B();
var c = new C();
A.n++
console.log(b.n);
console.log(c.n);

输入后果:9999 4400

解析:

  1. console.log(b.n),在查找 b.n 是首先查找 b 对象本身有没有 n 属性,如果没有会去原型(prototype)上查找,当执行 var b = new B()时,函数外部 this.n=9999(此时 this 指向 b) 返回 b 对象,b 对象有本身的 n 属性,所以返回 9999。
  2. console.log(c.n),同理,当执行 var c = new C()时,c 对象没有本身的 n 属性,向上查找,找到原型(prototype)上的 n 属性,因为 A.n++(此时对象 A 中的 n 为 4400),所以返回 4400。

代码输入后果

console.log(1)

setTimeout(() => {console.log(2)
})

new Promise(resolve =>  {console.log(3)
  resolve(4)
}).then(d => console.log(d))

setTimeout(() => {console.log(5)
  new Promise(resolve =>  {resolve(6)
  }).then(d => console.log(d))
})

setTimeout(() => {console.log(7)
})

console.log(8)

输入后果如下:

1
3
8
4
2
5
6
7

代码执行过程如下:

  1. 首先执行 script 代码,打印出 1;
  2. 遇到第一个定时器,退出到宏工作队列;
  3. 遇到 Promise,执行代码,打印出 3,遇到 resolve,将其退出到微工作队列;
  4. 遇到第二个定时器,退出到宏工作队列;
  5. 遇到第三个定时器,退出到宏工作队列;
  6. 继续执行 script 代码,打印出 8,第一轮执行完结;
  7. 执行微工作队列,打印出第一个 Promise 的 resolve 后果:4;
  8. 开始执行宏工作队列,执行第一个定时器,打印出 2;
  9. 此时没有微工作,继续执行宏工作中的第二个定时器,首先打印出 5,遇到 Promise,首选打印出 6,遇到 resolve,将其退出到微工作队列;
  10. 执行微工作队列,打印出 6;
  11. 执行宏工作队列中的最初一个定时器,打印出 7。

晓得 ES6 的 Class 嘛?Static 关键字有理解嘛

为这个类的函数对象间接增加办法,而不是加在这个函数对象的原型对象上

常见的图片格式及应用场景

(1)BMP,是无损的、既反对索引色也反对间接色的点阵图。这种图片格式简直没有对数据进行压缩,所以 BMP 格局的图片通常是较大的文件。

(2)GIF是无损的、采纳索引色的点阵图。采纳 LZW 压缩算法进行编码。文件小,是 GIF 格局的长处,同时,GIF 格局还具备反对动画以及通明的长处。然而 GIF 格局仅反对 8bit 的索引色,所以 GIF 格局实用于对色调要求不高同时须要文件体积较小的场景。

(3)JPEG是有损的、采纳间接色的点阵图。JPEG 的图片的长处是采纳了间接色,得益于更丰盛的色调,JPEG 非常适合用来存储照片,与 GIF 相比,JPEG 不适宜用来存储企业 Logo、线框类的图。因为有损压缩会导致图片含糊,而间接色的选用,又会导致图片文件较 GIF 更大。

(4)PNG-8是无损的、应用索引色的点阵图。PNG 是一种比拟新的图片格式,PNG- 8 是十分好的 GIF 格局替代者,在可能的状况下,应该尽可能的应用 PNG- 8 而不是 GIF,因为在雷同的图片成果下,PNG- 8 具备更小的文件体积。除此之外,PNG- 8 还反对透明度的调节,而 GIF 并不反对。除非须要动画的反对,否则没有理由应用 GIF 而不是 PNG-8。

(5)PNG-24是无损的、应用间接色的点阵图。PNG-24 的长处在于它压缩了图片的数据,使得同样成果的图片,PNG-24 格局的文件大小要比 BMP 小得多。当然,PNG24 的图片还是要比 JPEG、GIF、PNG- 8 大得多。

(6)SVG是无损的矢量图。SVG 是矢量图意味着 SVG 图片由直线和曲线以及绘制它们的办法组成。当放大 SVG 图片时,看到的还是线和曲线,而不会呈现像素点。这意味着 SVG 图片在放大时,不会失真,所以它非常适合用来绘制 Logo、Icon 等。

(7)WebP是谷歌开发的一种新图片格式,WebP 是同时反对有损和无损压缩的、应用间接色的点阵图。从名字就可以看进去它是为 Web 而生的,什么叫为 Web 而生呢?就是说雷同品质的图片,WebP 具备更小的文件体积。当初网站上充斥了大量的图片,如果可能升高每一个图片的文件大小,那么将大大减少浏览器和服务器之间的数据传输量,进而升高拜访提早,晋升拜访体验。目前只有 Chrome 浏览器和 Opera 浏览器反对 WebP 格局,兼容性不太好。

  • 在无损压缩的状况下,雷同品质的 WebP 图片,文件大小要比 PNG 小 26%;
  • 在有损压缩的状况下,具备雷同图片精度的 WebP 图片,文件大小要比 JPEG 小 25%~34%;
  • WebP 图片格式反对图片透明度,一个无损压缩的 WebP 图片,如果要反对透明度只须要 22% 的分外文件大小。

参考:前端进阶面试题具体解答

代码输入后果

f = function() {return true;};   
g = function() {return false;};   
(function() {if (g() && [] == ![]) {f = function f() {return false;};   
      function g() {return true;}   
   }   
})();   
console.log(f());

输入后果:false

这里首先定义了两个变量 f 和 g,咱们晓得变量是能够从新赋值的。前面是一个匿名自执行函数,在 if 条件中调用了函数 g(),因为在匿名函数中,又从新定义了函数 g,就笼罩了内部定义的变量 g,所以,这里调用的是外部函数 g 办法,返回为 true。第一个条件通过,进入第二个条件。

第二个条件是[] == ![],先看 ![],在 JavaScript 中,当用于布尔运算时,比方在这里,对象的非空援用被视为 true,空援用 null 则被视为 false。因为这里不是一个 null, 而是一个没有元素的数组,所以 [] 被视为 true, 而 ![] 的后果就是 false 了。当一个布尔值参加到条件运算的时候,true 会被看作 1, 而 false 会被看作 0。当初条件变成了 [] == 0 的问题了,当一个对象参加条件比拟的时候,它会被求值,求值的后果是数组成为一个字符串,[] 的后果就是 ”,而 ” 会被当作 0,所以,条件成立。

两个条件都成立,所以会执行条件中的代码,f 在定义是没有应用 var,所以他是一个全局变量。因而,这里会通过闭包拜访到内部的变量 f, 从新赋值,当初执行 f 函数返回值曾经成为 false 了。而 g 则不会有这个问题,这里是一个函数内定义的 g,不会影响到内部的 g 函数。所以最初的后果就是 false。

闭包的利用场景

  • 柯里化 bind
  • 模块

实现函数原型办法

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;
}

实现 new 关键字

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

实现要点:

  • new 会产生一个新对象;
  • 新对象须要可能拜访到构造函数的属性,所以须要从新指定它的原型;
  • 构造函数可能会显示返回;
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}

实现 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__
    }
}

下面的 left.proto 这种写法能够换成 Object.getPrototypeOf(left)。

实现 Object.create

Object.create()办法创立一个新对象,应用现有的对象来提供新创建的对象的__proto__。

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
}

实现 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
}

实现 JSON.stringify

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

  1. 根本数据类型:

    • undefined 转换之后仍是 undefined(类型也是 undefined)
    • boolean 值转换之后是字符串 “false”/”true”
    • number 类型 (除了 NaN 和 Infinity) 转换之后是字符串类型的数值
    • symbol 转换之后是 undefined
    • null 转换之后是字符串 “null”
    • string 转换之后仍是 string
    • NaN 和 Infinity 转换之后是字符串 “null”
  2. 函数类型:转换之后是 undefined
  3. 如果是对象类型(非函数)

    • 如果是一个数组:如果属性值中呈现了 undefined、任意的函数以及 symbol,转换成字符串 “null”;
    • 如果是 RegExp 对象:返回 {} (类型是 string);
    • 如果是 Date 对象,返回 Date 的 toJSON 字符串值;
    • 如果是一般对象;

      • 如果有 toJSON() 办法,那么序列化 toJSON() 的返回值。
      • 如果属性值中呈现了 undefined、任意的函数以及 symbol 值,疏忽。
      • 所有以 symbol 为属性键的属性都会被齐全疏忽掉。
  4. 对蕴含循环援用的对象(对象之间互相援用,造成有限循环)执行此办法,会抛出谬误。
function jsonStringify(data) {
    let dataType = typeof data;

    if (dataType !== 'object') {
        let result = data;
        //data 可能是 string/number/null/undefined/boolean
        if (Number.isNaN(data) || data === Infinity) {
            //NaN 和 Infinity 序列化返回 "null"
            result = "null";
        } else if (dataType === 'function' || dataType === 'undefined' || dataType === 'symbol') {
            //function、undefined、symbol 序列化返回 undefined
            return undefined;
        } else if (dataType === 'string') {result = '"'+ data +'"';}
        //boolean 返回 String()
        return String(result);
    } else if (dataType === 'object') {if (data === null) {return "null"} else if (data.toJSON && typeof data.toJSON === 'function') {return jsonStringify(data.toJSON());
        } else if (data instanceof Array) {let result = [];
            // 如果是数组
            //toJSON 办法能够存在于原型链中
            data.forEach((item, index) => {if (typeof item === 'undefined' || typeof item === 'function' || typeof item === 'symbol') {result[index] = "null";
                } else {result[index] = jsonStringify(item);
                }
            });
            result = "[" + result + "]";
            return result.replace(/'/g,'"');

        } else {
            // 一般对象
            /**             * 循环援用抛错(暂未检测,循环援用时,堆栈溢出)             * symbol key 疏忽             * undefined、函数、symbol 为属性值,被疏忽             */
            let result = [];
            Object.keys(data).forEach((item, index) => {if (typeof item !== 'symbol') {
                    //key 如果是 symbol 对象,疏忽
                    if (data[item] !== undefined && typeof data[item] !== 'function'
                        && typeof data[item] !== 'symbol') {
                        // 键值如果是 undefined、函数、symbol 为属性值,疏忽
                        result.push('"'+ item +'"' + ":" + jsonStringify(data[item]));
                    }
                }
            });
            return ("{" + result + "}").replace(/'/g,'"');
        }
    }
}

实现 JSON.parse

介绍 2 种办法实现:

  • eval 实现;
  • new Function 实现;

eval 实现

第一种形式最简略,也最直观,就是间接调用 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 + ")");
}

new Function 实现

Function 与 eval 有雷同的字符串参数个性。

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

实现 Promise

实现 Promise 须要齐全读懂 Promise A+ 标准,不过从总体的实现上看,有如下几个点须要思考到:

  • then 须要反对链式调用,所以得返回一个新的 Promise;
  • 解决异步问题,所以得先用 onResolvedCallbacks 和 onRejectedCallbacks 别离把胜利和失败的回调存起来;
  • 为了让链式调用失常进行上来,须要判断 onFulfilled 和 onRejected 的类型;
  • onFulfilled 和 onRejected 须要被异步调用,这里用 setTimeout 模仿异步;
  • 解决 Promise 的 resolve;
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class Promise {constructor(executor) {
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onResolvedCallbacks = [];
        this.onRejectedCallbacks = [];

        let resolve = (value) = > {if (this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value;
                this.onResolvedCallbacks.forEach((fn) = > fn());
            }
        };

        let reject = (reason) = > {if (this.status === PENDING) {
                this.status = REJECTED;
                this.reason = reason;
                this.onRejectedCallbacks.forEach((fn) = > fn());
            }
        };

        try {executor(resolve, reject);
        } catch (error) {reject(error);
        }
    }

    then(onFulfilled, onRejected) {
        // 解决 onFufilled,onRejected 没有传值的问题
        onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) = > v;
        // 因为谬误的值要让前面拜访到,所以这里也要抛出谬误,不然会在之后 then 的 resolve 中捕捉
        onRejected = typeof onRejected === "function" ? onRejected : (err) = > {throw err;};
        // 每次调用 then 都返回一个新的 promise
        let promise2 = new Promise((resolve, reject) = > {if (this.status === FULFILLED) {
                //Promise/A+ 2.2.4 --- setTimeout
                setTimeout(() = > {
                    try {let x = onFulfilled(this.value);
                        // x 可能是一个 proimise
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {reject(e);
                    }
                }, 0);
            }

            if (this.status === REJECTED) {
                //Promise/A+ 2.2.3
                setTimeout(() = > {
                    try {let x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {reject(e);
                    }
                }, 0);
            }

            if (this.status === 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;
    }
}
const resolvePromise = (promise2, x, resolve, reject) = > {
    // 本人期待本人实现是谬误的实现,用一个类型谬误,完结掉 promise  Promise/A+ 2.3.1
    if (promise2 === x) {
        return reject(new TypeError("Chaining cycle detected for promise #<Promise>"));
    }
    // Promise/A+ 2.3.3.3.3 只能调用一次
    let called;
    // 后续的条件要严格判断 保障代码能和别的库一起应用
    if ((typeof x === "object" && x != null) || typeof x === "function") {
        try {
            // 为了判断 resolve 过的就不必再 reject 了(比方 reject 和 resolve 同时调用的时候)Promise/A+ 2.3.3.1
            let then = x.then;
            if (typeof then === "function") {
            // 不要写成 x.then,间接 then.call 就能够了 因为 x.then 会再次取值,Object.defineProperty  Promise/A+ 2.3.3.3
                then.call(x, (y) = > {
                        // 依据 promise 的状态决定是胜利还是失败
                        if (called) return;
                        called = true;
                        // 递归解析的过程(因为可能 promise 中还有 promise)Promise/A+ 2.3.3.3.1
                        resolvePromise(promise2, y, resolve, reject);
                    }, (r) = > {
                        // 只有失败就失败 Promise/A+ 2.3.3.3.2
                        if (called) return;
                        called = true;
                        reject(r);
                    });
            } else {
                // 如果 x.then 是个一般值就间接返回 resolve 作为后果  Promise/A+ 2.3.3.4
                resolve(x);
            }
        } catch (e) {
            // Promise/A+ 2.3.3.2
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        // 如果 x 是个一般值就间接返回 resolve 作为后果  Promise/A+ 2.3.4
        resolve(x);
    }
};

Promise 写完之后能够通过 promises-aplus-tests 这个包对咱们写的代码进行测试,看是否合乎 A+ 标准。不过测试前还得加一段代码:

// promise.js
// 这里是下面写的 Promise 全副代码
Promise.defer = Promise.deferred = function () {let dfd = {}
    dfd.promise = new Promise((resolve,reject)=>{
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd;
}
module.exports = Promise;

全局装置:

npm i promises-aplus-tests -g

终端下执行验证命令:

promises-aplus-tests promise.js

下面写的代码能够顺利通过全副 872 个测试用例。

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'))
                }
            })
        })
    })
}

为什么 0.1 + 0.2 != 0.3,请详述理由

因为 JS 采纳 IEEE 754 双精度版本(64 位),并且只有采纳 IEEE 754 的语言都有该问题。

咱们都晓得计算机示意十进制是采纳二进制示意的,所以 0.1 在二进制示意为

// (0011) 示意循环
0.1 = 2^-4 * 1.10011(0011)

那么如何失去这个二进制的呢,咱们能够来演算下

小数算二进制和整数不同。乘法计算时,只计算小数位,整数位用作每一位的二进制,并且失去的第一位为最高位。所以咱们得出 0.1 = 2^-4 * 1.10011(0011),那么 0.2 的演算也根本如上所示,只须要去掉第一步乘法,所以得出 0.2 = 2^-3 * 1.10011(0011)

回来持续说 IEEE 754 双精度。六十四位中符号位占一位,整数位占十一位,其余五十二位都为小数位。因为 0.10.2 都是有限循环的二进制了,所以在小数位开端处须要判断是否进位(就和十进制的四舍五入一样)。

所以 2^-4 * 1.10011...001 进位后就变成了 2^-4 * 1.10011(0011 * 12 次)010。那么把这两个二进制加起来会得出 2^-2 * 1.0011(0011 * 11 次)0100 , 这个值算成十进制就是 0.30000000000000004

上面说一下原生解决办法,如下代码所示

parseFloat((0.1 + 0.2).toFixed(10))

V8 的垃圾回收机制是怎么的

V8 实现了精确式 GC,GC 算法采纳了分代式垃圾回收机制。因而,V8 将内存(堆)分为新生代和老生代两局部。

(1)新生代算法

新生代中的对象个别存活工夫较短,应用 Scavenge GC 算法。

在新生代空间中,内存空间分为两局部,别离为 From 空间和 To 空间。在这两个空间中,必然有一个空间是应用的,另一个空间是闲暇的。新调配的对象会被放入 From 空间中,当 From 空间被占满时,新生代 GC 就会启动了。算法会查看 From 空间中存活的对象并复制到 To 空间中,如果有失活的对象就会销毁。当复制实现后将 From 空间和 To 空间调换,这样 GC 就完结了。

(2)老生代算法

老生代中的对象个别存活工夫较长且数量也多,应用了两个算法,别离是标记革除算法和标记压缩算法。

先来说下什么状况下对象会呈现在老生代空间中:

  • 新生代中的对象是否曾经经验过一次 Scavenge 算法,如果经验过的话,会将对象从新生代空间移到老生代空间中。
  • To 空间的对象占比大小超过 25 %。在这种状况下,为了不影响到内存调配,会将对象从新生代空间移到老生代空间中。

老生代中的空间很简单,有如下几个空间

enum AllocationSpace {// TODO(v8:7464): Actually map this space's memory as read-only.
  RO_SPACE,    // 不变的对象空间
  NEW_SPACE,   // 新生代用于 GC 复制算法的空间
  OLD_SPACE,   // 老生代常驻对象空间
  CODE_SPACE,  // 老生代代码对象空间
  MAP_SPACE,   // 老生代 map 对象
  LO_SPACE,    // 老生代大空间对象
  NEW_LO_SPACE,  // 新生代大空间对象
  FIRST_SPACE = RO_SPACE,
  LAST_SPACE = NEW_LO_SPACE,
  FIRST_GROWABLE_PAGED_SPACE = OLD_SPACE,
  LAST_GROWABLE_PAGED_SPACE = MAP_SPACE
};

在老生代中,以下状况会先启动标记革除算法:

  • 某一个空间没有分块的时候
  • 空间中被对象超过肯定限度
  • 空间不能保障新生代中的对象挪动到老生代中

在这个阶段中,会遍历堆中所有的对象,而后标记活的对象,在标记实现后,销毁所有没有被标记的对象。在标记大型对内存时,可能须要几百毫秒能力实现一次标记。这就会导致一些性能上的问题。为了解决这个问题,2011 年,V8 从 stop-the-world 标记切换到增量标记。在增量标记期间,GC 将标记工作合成为更小的模块,能够让 JS 应用逻辑在模块间隙执行一会,从而不至于让利用呈现进展状况。但在 2018 年,GC 技术又有了一个重大突破,这项技术名为并发标记。该技术能够让 GC 扫描和标记对象时,同时容许 JS 运行。

革除对象后会造成堆内存呈现碎片的状况,当碎片超过肯定限度后会启动压缩算法。在压缩过程中,将活的对象向一端挪动,直到所有对象都挪动实现而后清理掉不须要的内存。

数组扁平化

数组扁平化就是将 [1, [2, [3]]] 这种多层的数组拍平成一层 [1, 2, 3]。应用 Array.prototype.flat 能够间接将多层数组拍平成一层:

[1, [2, [3]]].flat(2)  // [1, 2, 3]

当初就是要实现 flat 这种成果。

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;
}

用过 TypeScript 吗?它的作用是什么?

为 JS 增加类型反对,以及提供最新版的 ES 语法的反对,是的利于团队合作和排错,开发大型项目

前端贮存的⽅式有哪些?

  • cookies:在 HTML5 规范前本地贮存的次要⽅式,长处是兼容性好,申请头⾃带 cookie ⽅便,毛病是⼤⼩只有 4k,⾃动申请头加⼊ cookie 节约流量,每个 domain 限度 20 个 cookie,使⽤起来麻烦,须要⾃⾏封装;
  • localStorage:HTML5 加⼊的以键值对 (Key-Value) 为规范的⽅式,长处是操作⽅便,永久性贮存(除⾮⼿动删除),⼤⼩为 5M,兼容 IE8+;
  • sessionStorage:与 localStorage 根本相似,区别是 sessionStorage 当⻚⾯敞开后会被清理,⽽且与 cookie、localStorage 不同,他不能在所有同源窗⼝中共享,是会话级别的贮存⽅式;
  • Web SQL:2010 年被 W3C 废除的本地数据库数据存储⽅案,然而支流浏览器(⽕狐除外)都曾经有了相干的实现,web sql 相似于 SQLite,是真正意义上的关系型数据库,⽤ sql 进⾏操作,当咱们⽤ JavaScript 时要进⾏转换,较为繁琐;
  • IndexedDB:是被正式纳⼊ HTML5 规范的数据库贮存⽅案,它是 NoSQL 数据库,⽤键值对进⾏贮存,能够进⾏疾速读取操作,⾮常适宜 web 场景,同时⽤ JavaScript 进⾏操作会⾮常便。

AJAX

const getJSON = function(url) {return new Promise((resolve, reject) => {const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.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();})
}

实现数组原型办法

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

基于 forEach 的实现可能很容易写出 map 的实现:

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

filter

同样,基于 forEach 的实现可能很容易写出 filter 的实现:

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

some

同样,基于 forEach 的实现可能很容易写出 some 的实现:

- Array.prototype.forEach2 = function(callback, thisArg) {+ 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
}

代码输入后果

Promise.resolve(1)
  .then(res => {console.log(res);
    return 2;
  })
  .catch(err => {return 3;})
  .then(res => {console.log(res);
  });

输入后果如下:

1   
2

Promise 是能够链式调用的,因为每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用, 它并不像个别工作的链式调用一样 return this。

下面的输入后果之所以顺次打印出 1 和 2,是因为 resolve(1) 之后走的是第一个 then 办法,并没有进 catch 里,所以第二个 then 中的 res 失去的实际上是第一个 then 的返回值。并且 return 2 会被包装成resolve(2),被最初的 then 打印输出 2。

字符串模板

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; // 如果模板没有模板字符串间接返回
}

测试:

let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let person = {
    name: '布兰',
    age: 12
}
render(template, person); // 我是布兰,年龄 12,性别 undefined

Promise.reject

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

在地址栏里输出一个地址回车会产生哪些事件

1、解析 URL:首先会对 URL 进行解析,剖析所须要应用的传输协定和申请的资源的门路。如果输出的 URL 中的协定或者主机名不非法,将会把地址栏中输出的内容传递给搜索引擎。如果没有问题,浏览器会查看 URL 中是否呈现了非法字符,如果存在非法字符,则对非法字符进行本义后再进行下一过程。2、缓存判断:浏览器会判断所申请的资源是否在缓存里,如果申请的资源在缓存里并且没有生效,那么就间接应用,否则向服务器发动新的申请。3、DNS 解析:下一步首先须要获取的是输出的 URL 中的域名的 IP 地址,首先会判断本地是否有该域名的 IP 地址的缓存,如果有则应用,如果没有则向本地 DNS 服务器发动申请。本地 DNS 服务器也会先查看是否存在缓存,如果没有就会先向根域名服务器发动申请,取得负责的顶级域名服务器的地址后,再向顶级域名服务器申请,而后取得负责的权威域名服务器的地址后,再向权威域名服务器发动申请,最终取得域名的 IP 地址后,本地 DNS 服务器再将这个 IP 地址返回给申请的用户。用户向本地 DNS 服务器发动申请属于递归申请,本地 DNS 服务器向各级域名服务器发动申请属于迭代申请。4、获取 MAC 地址:当浏览器失去 IP 地址后,数据传输还须要晓得目标主机 MAC 地址,因为应用层下发数据给传输层,TCP 协定会指定源端口号和目标端口号,而后下发给网络层。网络层会将本机地址作为源地址,获取的 IP 地址作为目标地址。而后将下发给数据链路层,数据链路层的发送须要退出通信单方的 MAC 地址,本机的 MAC 地址作为源 MAC 地址,目标 MAC 地址须要分状况解决。通过将 IP 地址与本机的子网掩码相与,能够判断是否与申请主机在同一个子网里,如果在同一个子网里,能够应用 APR 协定获取到目标主机的 MAC 地址,如果不在一个子网里,那么申请应该转发给网关,由它代为转发,此时同样能够通过 ARP 协定来获取网关的 MAC 地址,此时目标主机的 MAC 地址应该为网关的地址。5、TCP 三次握手:上面是 TCP 建设连贯的三次握手的过程,首先客户端向服务器发送一个 SYN 连贯申请报文段和一个随机序号,服务端接管到申请后向客户端发送一个 SYN ACK 报文段,确认连贯申请,并且也向客户端发送一个随机序号。客户端接管服务器的确认应答后,进入连贯建设的状态,同时向服务器也发送一个 ACK 确认报文段,服务器端接管到确认后,也进入连贯建设状态,此时单方的连贯就建设起来了。6、HTTPS 握手:如果应用的是 HTTPS 协定,在通信前还存在 TLS 的一个四次握手的过程。首先由客户端向服务器端发送应用的协定的版本号、一个随机数和能够应用的加密办法。服务器端收到后,确认加密的办法,也向客户端发送一个随机数和本人的数字证书。客户端收到后,首先查看数字证书是否无效,如果无效,则再生成一个随机数,并应用证书中的公钥对随机数加密,而后发送给服务器端,并且还会提供一个后面所有内容的 hash 值供服务器端测验。服务器端接管后,应用本人的私钥对数据解密,同时向客户端发送一个后面所有内容的 hash 值供客户端测验。这个时候单方都有了三个随机数,依照之前所约定的加密办法,应用这三个随机数生成一把秘钥,当前单方通信前,就应用这个秘钥对数据进行加密后再传输。7、返回数据:当页面申请发送到服务器端后,服务器端会返回一个 html 文件作为响应,浏览器接管到响应后,开始对 html 文件进行解析,开始页面的渲染过程。8、页面渲染:浏览器首先会依据 html 文件构建 DOM 树,依据解析到的 css 文件构建 CSSOM 树,如果遇到 script 标签,则判端是否含有 defer 或者 async 属性,要不然 script 的加载和执行会造成页面的渲染的阻塞。当 DOM 树和 CSSOM 树建设好后,依据它们来构建渲染树。渲染树构建好后,会依据渲染树来进行布局。布局实现后,最初应用浏览器的 UI 接口对页面进行绘制。这个时候整个页面就显示进去了。9、TCP 四次挥手:最初一步是 TCP 断开连接的四次挥手过程。若客户端认为数据发送实现,则它须要向服务端发送连贯开释申请。服务端收到连贯开释申请后,会通知应用层要开释 TCP 链接。而后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明客户端到服务端的连贯曾经开释,不再接管客户端发的数据了。然而因为 TCP 连贯是双向的,所以服务端仍旧能够发送数据给客户端。服务端如果此时还有没发完的数据会持续发送,结束后会向客户端发送连贯开释申请,而后服务端便进入 LAST-ACK 状态。客户端收到开释申请后,向服务端发送确认应答,此时客户端进入 TIME-WAIT 状态。该状态会继续 2MSL(最大段生存期,指报文段在网络中生存的工夫,超时会被摈弃)工夫,若该时间段内没有服务端的重发申请的话,就进入 CLOSED 状态。当服务端收到确认应答后,也便进入 CLOSED 状态。

正文完
 0