乐趣区

关于前端:腾讯前端二面面试题附答案

常见的 HTTP 申请办法

  • GET: 向服务器获取数据;
  • POST:将实体提交到指定的资源,通常会造成服务器资源的批改;
  • PUT:上传文件,更新数据;
  • DELETE:删除服务器上的对象;
  • HEAD:获取报文首部,与 GET 相比,不返回报文主体局部;
  • OPTIONS:询问反对的申请办法,用来跨域申请;
  • CONNECT:要求在与代理服务器通信时建设隧道,应用隧道进行 TCP 通信;
  • TRACE: 回显服务器收到的申请,次要⽤于测试或诊断。

call apply bind

题目形容: 手写 call apply bind 实现

实现代码如下:

Function.prototype.myCall = function (context, ...args) {if (!context || context === null) {context = window;}
  // 发明惟一的 key 值  作为咱们结构的 context 外部办法名
  let fn = Symbol();
  context[fn] = this; //this 指向调用 call 的函数
  // 执行函数并返回后果 相当于把本身作为传入的 context 的办法进行调用了
  return context[fn](...args);
};

// apply 原理统一  只是第二个参数是传入的数组
Function.prototype.myApply = function (context, args) {if (!context || context === null) {context = window;}
  // 发明惟一的 key 值  作为咱们结构的 context 外部办法名
  let fn = Symbol();
  context[fn] = this;
  // 执行函数并返回后果
  return context[fn](...args);
};

//bind 实现要简单一点  因为他思考的状况比拟多 还要波及到参数合并(相似函数柯里化)

Function.prototype.myBind = function (context, ...args) {if (!context || context === null) {context = window;}
  // 发明惟一的 key 值  作为咱们结构的 context 外部办法名
  let fn = Symbol();
  context[fn] = this;
  let _this = this;
  //  bind 状况要简单一点
  const result = function (...innerArgs) {
    // 第一种状况 : 若是将 bind 绑定之后的函数当作构造函数,通过 new 操作符应用,则不绑定传入的 this,而是将 this 指向实例化进去的对象
    // 此时因为 new 操作符作用  this 指向 result 实例对象  而 result 又继承自传入的_this 依据原型链常识可得出以下论断
    // this.__proto__ === result.prototype   //this instanceof result =>true
    // this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true
    if (this instanceof _this === true) {
      // 此时 this 指向指向 result 的实例  这时候不须要扭转 this 指向
      this[fn] = _this;
      this[fn](...[...args, ...innerArgs]); // 这里应用 es6 的办法让 bind 反对参数合并
    } else {
      // 如果只是作为一般函数调用  那就很简略了 间接扭转 this 指向为传入的 context
      context[fn](...[...args, ...innerArgs]);
    }
  };
  // 如果绑定的是构造函数 那么须要继承构造函数原型属性和办法
  // 实现继承的形式: 应用 Object.create
  result.prototype = Object.create(this.prototype);
  return result;
};

// 用法如下

// function Person(name, age) {//   console.log(name); //'我是参数传进来的 name'
//   console.log(age); //'我是参数传进来的 age'
//   console.log(this); // 构造函数 this 指向实例对象
// }
// // 构造函数原型的办法
// Person.prototype.say = function() {//   console.log(123);
// }
// let obj = {
//   objName: '我是 obj 传进来的 name',
//   objAge: '我是 obj 传进来的 age'
// }
// // 一般函数
// function normalFun(name, age) {//   console.log(name);   //'我是参数传进来的 name'
//   console.log(age);   //'我是参数传进来的 age'
//   console.log(this); // 一般函数 this 指向绑定 bind 的第一个参数 也就是例子中的 obj
//   console.log(this.objName); //'我是 obj 传进来的 name'
//   console.log(this.objAge); //'我是 obj 传进来的 age'
// }

// 先测试作为结构函数调用
// let bindFun = Person.myBind(obj, '我是参数传进来的 name')
// let a = new bindFun('我是参数传进来的 age')
// a.say() //123

// 再测试作为一般函数调用
// let bindFun = normalFun.myBind(obj, '我是参数传进来的 name')
//  bindFun('我是参数传进来的 age')

偏函数

什么是偏函数?偏函数就是将一个 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)
    }
}

如上这个性能比较简单,当初咱们心愿偏函数能和柯里化一样能实现占位性能,比方:

function clg(a, b, c) {console.log(a, b, c)
}
let partialClg = partial(clg, '_', 2)
partialClg(1, 3)  // 顺次打印:1, 2, 3

_ 占的位其实就是 1 的地位。相当于:partial(clg, 1, 2),而后 partialClg(3)。明确了原理,咱们就来写实现:

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

实现函数原型办法

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

ES6 新个性

1.ES6 引入来严格模式
    变量必须申明后在应用
    函数的参数不能有同名属性, 否则报错
    不能应用 with 语句 (说实话我根本没用过)
    不能对只读属性赋值, 否则报错
    不能应用前缀 0 示意八进制数, 否则报错 (说实话我根本没用过)
    不能删除不可删除的数据, 否则报错
    不能删除变量 delete prop, 会报错, 只能删除属性 delete global[prop]
    eval 不会在它的外层作用域引入变量
    eval 和 arguments 不能被从新赋值
    arguments 不会主动反映函数参数的变动
    不能应用 arguments.caller (说实话我根本没用过)
    不能应用 arguments.callee (说实话我根本没用过)
    禁止 this 指向全局对象
    不能应用 fn.caller 和 fn.arguments 获取函数调用的堆栈 (说实话我根本没用过)
    减少了保留字(比方 protected、static 和 interface)2. 对于 let 和 const 新增的变量申明

3. 变量的解构赋值

4. 字符串的扩大
    includes():返回布尔值,示意是否找到了参数字符串。startsWith():返回布尔值,示意参数字符串是否在原字符串的头部。endsWith():返回布尔值,示意参数字符串是否在原字符串的尾部。5. 数值的扩大
    Number.isFinite()用来查看一个数值是否为无限的(finite)。Number.isNaN()用来查看一个值是否为 NaN。6. 函数的扩大
    函数参数指定默认值
7. 数组的扩大
    扩大运算符
8. 对象的扩大
    对象的解构
9. 新增 symbol 数据类型

10.Set 和 Map 数据结构 
    ES6 提供了新的数据结构 Set。它相似于数组,然而成员的值都是惟一的,没有反复的值。Set 自身是一个构造函数,用来生成 Set 数据结构。Map 它相似于对象,也是键值对的汇合,然而“键”的范畴不限于字符串,各种类型的值(包含对象)都能够当作键。11.Proxy
    Proxy 能够了解成,在指标对象之前架设一层“拦挡”,外界对该对象的拜访
    都必须先通过这层拦挡,因而提供了一种机制,能够对外界的拜访进行过滤和改写。Proxy 这个词的原意是代理,用在这里示意由它来“代理”某些操作,能够译为“代理器”。Vue3.0 应用了 proxy
12.Promise
    Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更正当和更弱小。特点是:对象的状态不受外界影响。一旦状态扭转,就不会再变,任何时候都能够失去这个后果。13.async 函数 
    async 函数对 Generator 函数的区别:(1)内置执行器。Generator 函数的执行必须靠执行器,而 async 函数自带执行器。也就是说,async 函数的执行,与一般函数截然不同,只有一行。(2)更好的语义。async 和 await,比起星号和 yield,语义更分明了。async 示意函数里有异步操作,await 示意紧跟在前面的表达式须要期待后果。(3)失常状况下,await 命令前面是一个 Promise 对象。如果不是,会被转成一个立刻 resolve 的 Promise 对象。(4)返回值是 Promise。async 函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象不便多了。你能够用 then 办法指定下一步的操作。14.Class 
    class 跟 let、const 一样:不存在变量晋升、不能反复申明...
    ES6 的 class 能够看作只是一个语法糖,它的绝大部分性能
    ES5 都能够做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。15.Module
    ES6 的模块主动采纳严格模式,不论你有没有在模块头部加上 "use strict";。import 和 export 命令以及 export 和 export default 的区别

代码输入后果

const async1 = async () => {console.log('async1');
  setTimeout(() => {console.log('timer1')
  }, 2000)
  await new Promise(resolve => {console.log('promise1')
  })
  console.log('async1 end')
  return 'async1 success'
} 
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .catch(4)
  .then(res => console.log(res))
setTimeout(() => {console.log('timer2')
}, 1000)

输入后果如下:

script start
async1
promise1
script end
1
timer2
timer1

代码的执行过程如下:

  1. 首先执行同步带吗,打印出 script start;
  2. 遇到定时器 timer1 将其退出宏工作队列;
  3. 之后是执行 Promise,打印出 promise1,因为 Promise 没有返回值,所以前面的代码不会执行;
  4. 而后执行同步代码,打印出 script end;
  5. 继续执行上面的 Promise,.then 和.catch 冀望参数是一个函数,这里传入的是一个数字,因而就会产生值浸透,将 resolve(1)的值传到最初一个 then,间接打印出 1;
  6. 遇到第二个定时器,将其退出到微工作队列,执行微工作队列,按程序顺次执行两个定时器,然而因为定时器工夫的起因,会在两秒后先打印出 timer2,在四秒后打印出 timer1。

ES6模块与 CommonJS 模块有什么异同?

ES6 Module 和 CommonJS 模块的区别:

  • CommonJS 是对模块的浅拷⻉,ES6 Module 是对模块的引⽤,即 ES6 Module 只存只读,不能扭转其值,也就是指针指向不能变,相似 const;
  • import 的接⼝是 read-only(只读状态),不能批改其变量值。即不能批改其变量的指针指向,但能够扭转变量外部指针指向,能够对 commonJS 对从新赋值(扭转指针指向),然而对 ES6 Module 赋值会编译报错。

ES6 Module 和 CommonJS 模块的共同点:

  • CommonJS 和 ES6 Module 都能够对引⼊的对象进⾏赋值,即对对象外部属性的值进⾏扭转。

懒加载的实现原理

图片的加载是由 src 引起的,当对 src 赋值时,浏览器就会申请图片资源。依据这个原理,咱们应用 HTML5 的 data-xxx 属性来贮存图片的门路,在须要加载图片的时候,将 data-xxx 中图片的门路赋值给src,这样就实现了图片的按需加载,即懒加载。

留神:data-xxx 中的 xxx 能够自定义,这里咱们应用 data-src 来定义。

懒加载的实现重点在于确定用户须要加载哪张图片,在浏览器中,可视区域内的资源就是用户须要的资源。所以当图片呈现在可视区域时,获取图片的实在地址并赋值给图片即可。

应用原生 JavaScript 实现懒加载:

知识点:

(1)window.innerHeight 是浏览器可视区的高度

(2)document.body.scrollTop || document.documentElement.scrollTop 是浏览器滚动的过的间隔

(3)imgs.offsetTop 是元素顶部间隔文档顶部的高度(包含滚动条的间隔)

(4)图片加载条件:img.offsetTop < window.innerHeight + document.body.scrollTop;

图示: 代码实现:

<div class="container">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
</div>
<script>
var imgs = document.querySelectorAll('img');
function lozyLoad(){
        var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
        var winHeight= window.innerHeight;
        for(var i=0;i < imgs.length;i++){if(imgs[i].offsetTop < scrollTop + winHeight ){imgs[i].src = imgs[i].getAttribute('data-src');
            }
        }
    }
  window.onscroll = lozyLoad();
</script>

如何判断一个对象是否属于某个类?

  • 第一种形式,应用 instanceof 运算符来判断构造函数的 prototype 属性是否呈现在对象的原型链中的任何地位。
  • 第二种形式,通过对象的 constructor 属性来判断,对象的 constructor 属性指向该对象的构造函数,然而这种形式不是很平安,因为 constructor 属性能够被改写。
  • 第三种形式,如果须要判断的是某个内置的援用类型的话,能够应用 Object.prototype.toString() 办法来打印对象的[[Class]] 属性来进行判断。

原函数形参定长(此时 fn.length 是个不变的常数)

// 写法 1 - 不保留参数, 递归部分函数
function curry(fn) {let judge = (...args) => {
        // 递归完结条件
        if(args.length === fn.length) return fn(...args);
        return (...arg) => judge(...args, ...arg);
    }
    return judge;
}

// 写法 2 - 保留参数, 递归整体函数
function curry(fn) {
    // 保留参数,除去第一个函数参数
    let presentArgs = [].slice.call(arguments, 1);
    // 返回一个新函数
    return function(){
        // 新函数调用时会持续传参
        let allArgs = [...presentArgs, ...arguments];
        // 递归完结条件
        if(allArgs.length === fn.length) {
            // 如果参数够了,就执行原函数
            return fn(,,,allArgs);
        }
        // 否则持续柯里化
        else return curry(fn, ...allArgs);
    }
}

// 测试
function add(a, b, c, d) {return a + b + c + d;}
console.log(add(1, 2, 3, 4));
let addCurry = curry(add);
// 以下后果都返回 10
console.log(addCurry(1)(2)(3)(4));  
console.log(addCurry(1)(2, 3, 4));
console.log(addCurry(1, 2)(3)(4));
console.log(addCurry(1, 2)(3, 4));
console.log(addCurry(1, 2, 3)(4));
console.log(addCurry(1, 2, 3, 4));

常⽤的 meta 标签有哪些

meta 标签由 namecontent 属性定义,用来形容网页文档的属性 ,比方网页的作者,网页形容,关键词等,除了 HTTP 规范固定了一些name 作为大家应用的共识,开发者还能够自定义 name。

罕用的 meta 标签:
(1)charset,用来形容 HTML 文档的编码类型:

<meta charset="UTF-8" >

(2)keywords,页面关键词:

<meta name="keywords" content="关键词" />

(3)description,页面形容:

<meta name="description" content="页面形容内容" />

(4)refresh,页面重定向和刷新:

<meta http-equiv="refresh" content="0;url=" />

(5)viewport,适配挪动端,能够管制视口的大小和比例:

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

其中,content 参数有以下几种:

  • width viewport:宽度(数值 /device-width)
  • height viewport:高度(数值 /device-height)
  • initial-scale:初始缩放比例
  • maximum-scale:最大缩放比例
  • minimum-scale:最小缩放比例
  • user-scalable:是否容许用户缩放(yes/no)

(6)搜索引擎索引形式:

<meta name="robots" content="index,follow" />

其中,content 参数有以下几种:

  • all:文件将被检索,且页面上的链接能够被查问;
  • none:文件将不被检索,且页面上的链接不能够被查问;
  • index:文件将被检索;
  • follow:页面上的链接能够被查问;
  • noindex:文件将不被检索;
  • nofollow:页面上的链接不能够被查问。

POST 和 PUT 申请的区别

  • PUT 申请是向服务器端发送数据,从而批改数据的内容,然而不会减少数据的品种等,也就是说无论进行多少次 PUT 操作,其后果并没有不同。(能够了解为时 更新数据
  • POST 申请是向服务器端发送数据,该申请会扭转数据的品种等资源,它会创立新的内容。(能够了解为是 创立数据

说一下怎么把类数组转换为数组?

// 通过 call 调用数组的 slice 办法来实现转换
Array.prototype.slice.call(arrayLike)

// 通过 call 调用数组的 splice 办法来实现转换
Array.prototype.splice.call(arrayLike,0)

// 通过 apply 调用数组的 concat 办法来实现转换
Array.prototype.concat.apply([],arrayLike)

// 通过 Array.from 办法来实现转换
Array.from(arrayLike)

async/await 的劣势

繁多的 Promise 链并不能发现 async/await 的劣势,然而,如果须要解决由多个 Promise 组成的 then 链的时候,劣势就能体现进去了(很有意思,Promise 通过 then 链来解决多层回调的问题,当初又用 async/await 来进一步优化它)。

假如一个业务,分多个步骤实现,每个步骤都是异步的,而且依赖于上一个步骤的后果。依然用 setTimeout 来模仿异步操作:

/** * 传入参数 n,示意这个函数执行的工夫(毫秒)* 执行的后果是 n + 200,这个值将用于下一步骤 */
function takeLongTime(n) {
    return new Promise(resolve => {setTimeout(() => resolve(n + 200), n);
    });
}
function step1(n) {console.log(`step1 with ${n}`);
    return takeLongTime(n);
}
function step2(n) {console.log(`step2 with ${n}`);
    return takeLongTime(n);
}
function step3(n) {console.log(`step3 with ${n}`);
    return takeLongTime(n);
}

当初用 Promise 形式来实现这三个步骤的解决:

function doIt() {console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms

输入后果 resultstep3() 的参数 700 + 200 = 900doIt() 程序执行了三个步骤,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 计算的后果统一。

如果用 async/await 来实现呢,会是这样:

async function doIt() {console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}
doIt();

后果和之前的 Promise 实现是一样的,然而这个代码看起来是不是清晰得多,简直跟同步代码一样

Promise.allSettled

形容 :等到所有promise 都返回后果,就返回一个 promise 实例。

实现

Promise.allSettled = 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] = {
                            status: 'fulfilled',
                            value: value
                        };
                        if(count === promises.length) resolve(result);
                    }, 
                    reason => {
                        count++;
                        result[index] = {
                            status: 'rejected'.
                            reason: reason
                        };
                        if(count === promises.length) resolve(result);
                    }
                );
            });
        }
        else return reject(new TypeError("Argument is not iterable"));
    });
}

数据类型判断

typeof 能够正确辨认:Undefined、Boolean、Number、String、Symbol、Function 等类型的数据,然而对于其余的都会认为是 object,比方 Null、Date 等,所以通过 typeof 来判断数据类型会不精确。然而能够应用 Object.prototype.toString 实现。

function typeOf(obj) {-   let res = Object.prototype.toString.call(obj).split(' ')[1]
-   res = res.substring(0, res.length - 1).toLowerCase()
-   return res
// 更好的写法
+   return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase()}
typeOf([])        // 'array'
typeOf({})        // 'object'
typeOf(new Date)  // 'date'

HTTP 和 HTTPS 协定的区别

HTTP 和 HTTPS 协定的次要区别如下:

  • HTTPS 协定须要 CA 证书,费用较高;而 HTTP 协定不须要;
  • HTTP 协定是超文本传输协定,信息是明文传输的,HTTPS 则是具备安全性的 SSL 加密传输协定;
  • 应用不同的连贯形式,端口也不同,HTTP 协定端口是 80,HTTPS 协定端口是 443;
  • HTTP 协定连贯很简略,是无状态的;HTTPS 协定是有 SSL 和 HTTP 协定构建的可进行加密传输、身份认证的网络协议,比 HTTP 更加平安。

即时通讯的实现:短轮询、长轮询、SSE 和 WebSocket 间的区别?

短轮询和长轮询的目标都是用于实现客户端和服务器端的一个即时通讯。

短轮询的基本思路: 浏览器每隔一段时间向浏览器发送 http 申请,服务器端在收到申请后,不管是否有数据更新,都间接进行响应。这种形式实现的即时通信,实质上还是浏览器发送申请,服务器承受申请的一个过程,通过让客户端一直的进行申请,使得客户端可能模仿实时地收到服务器端的数据的变动。这种形式的长处是比较简单,易于了解。毛病是这种形式因为须要一直的建设 http 连贯,重大节约了服务器端和客户端的资源。当用户减少时,服务器端的压力就会变大,这是很不合理的。

长轮询的基本思路: 首先由客户端向服务器发动申请,当服务器收到客户端发来的申请后,服务器端不会间接进行响应,而是先将这个申请挂起,而后判断服务器端数据是否有更新。如果有更新,则进行响应,如果始终没有数据,则达到肯定的工夫限度才返回。客户端 JavaScript 响应处理函数会在解决完服务器返回的信息后,再次发出请求,从新建设连贯。长轮询和短轮询比起来,它的长处是显著缩小了很多不必要的 http 申请次数,相比之下节约了资源。长轮询的毛病在于,连贯挂起也会导致资源的节约。

SSE 的根本思维: 服务器应用流信息向服务器推送信息。严格地说,http 协定无奈做到服务器被动推送信息。然而,有一种变通方法,就是服务器向客户端申明,接下来要发送的是流信息。也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过去。这时,客户端不会敞开连贯,会始终等着服务器发过来的新的数据流,视频播放就是这样的例子。SSE 就是利用这种机制,应用流信息向浏览器推送信息。它基于 http 协定,目前除了 IE/Edge,其余浏览器都反对。它绝对于后面两种形式来说,不须要建设过多的 http 申请,相比之下节约了资源。

WebSocket 是 HTML5 定义的一个新协定议,与传统的 http 协定不同,该协定容许由服务器被动的向客户端推送信息。应用 WebSocket 协定的毛病是在服务器端的配置比较复杂。WebSocket 是一个全双工的协定,也就是通信单方是平等的,能够互相发送音讯,而 SSE 的形式是单向通信的,只能由服务器端向客户端推送信息,如果客户端须要发送信息就是属于下一个 http 申请了。

下面的四个通信协议,前三个都是基于 HTTP 协定的。

对于这四种即便通信协议,从性能的角度来看:WebSocket > 长连贯(SEE)> 长轮询 > 短轮询 然而,咱们如果思考浏览器的兼容性问题,程序就恰恰相反了: 短轮询 > 长轮询 > 长连贯(SEE)> WebSocket 所以,还是要依据具体的应用场景来判断应用哪种形式。

退出移动版