关于javascript:前端关于面试你可能需要收集的面试题

34次阅读

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

Proxy 能够实现什么性能?

在 Vue3.0 中通过 Proxy 来替换本来的 Object.defineProperty 来实现数据响应式。

Proxy 是 ES6 中新增的性能,它能够用来自定义对象中的操作。

let p = new Proxy(target, handler)

target 代表须要增加代理的对象,handler 用来自定义对象中的操作,比方能够用来自定义 set 或者 get 函数。

上面来通过 Proxy 来实现一个数据响应式:

let onWatch = (obj, setBind, getLogger) => {
  let handler = {get(target, property, receiver) {getLogger(target, property)
      return Reflect.get(target, property, receiver)
    },
    set(target, property, value, receiver) {setBind(value, property)
      return Reflect.set(target, property, value)
    }
  }
  return new Proxy(obj, handler)
}
let obj = {a: 1}
let p = onWatch(
  obj,
  (v, property) => {console.log(` 监听到属性 ${property}扭转为 ${v}`)
  },
  (target, property) => {console.log(`'${property}' = ${target[property]}`)
  }
)
p.a = 2 // 监听到属性 a 扭转
p.a // 'a' = 2

在上述代码中,通过自定义 setget 函数的形式,在本来的逻辑中插入了咱们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。

当然这是简略版的响应式实现,如果须要实现一个 Vue 中的响应式,须要在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要应用 Proxy 替换本来的 API 起因在于 Proxy 无需一层层递归为每个属性增加代理,一次即可实现以上操作,性能上更好,并且本来的实现有一些数据更新不能监听到,然而 Proxy 能够完满监听到任何形式的数据扭转,惟一缺点就是浏览器的兼容性不好。

script 标签中 defer 和 async 的区别

如果没有 defer 或 async 属性,浏览器会立刻加载并执行相应的脚本。它不会期待后续加载的文档元素,读取到就会开始加载和执行,这样就阻塞了后续文档的加载。

defer 和 async 属性都是去异步加载内部的 JS 脚本文件,它们都不会阻塞页面的解析,其区别如下:

  • 执行程序: 多个带 async 属性的标签,不能保障加载的程序;多个带 defer 属性的标签,依照加载程序执行;
  • 脚本是否并行执行:async 属性,示意 后续文档的加载和执行与 js 脚本的加载和执行是并行进行的 ,即异步执行;defer 属性,加载后续文档的过程和 js 脚本的加载(此时仅加载不执行) 是并行进行的(异步),js 脚本须要等到文档所有元素解析实现之后才执行,DOMContentLoaded 事件触发执行之前。

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

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 的区别

偏函数

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

数组扁平化

ES5 递归写法 —— isArray()、concat()

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

如果想实现第二个参数(指定“拉平”的层数),能够这样实现,前面的几种能够本人相似实现:

function flat(arr, level = 1) {var res = [];
    for(var i = 0; i < arr.length; i++) {if(Array.isArray(arr[i]) || level >= 1) {res = res.concat(flat(arr[i]), level - 1);
        }
        else {res.push(arr[i]);
        }
    }
    return res;
}

ES6 递归写法 — reduce()、concat()、isArray()

function flat(arr) {
    return arr.reduce((pre, cur) => pre.concat(Array.isArray(cur) ? flat(cur) : cur), []);
}

ES6 迭代写法 — 扩大运算符(…)、some()、concat()、isArray()

ES6 的扩大运算符(…) 只能扁平化一层

function flat(arr) {return [].concat(...arr);
}

全副扁平化 :遍历原数组,若arr 中含有数组则应用一次扩大运算符,直至没有为止。

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

toString/join & split

调用数组的 toString()/join() 办法(它会主动扁平化解决),将数组变为字符串而后再用 split 宰割还原为数组。因为 split 宰割后造成的数组的每一项值为字符串,所以须要用一个 map 办法遍历数组将其每一项转换为数值型。

function flat(arr){return arr.toString().split(',').map(item => Number(item));
    // return arr.join().split(',').map(item => Number(item));
}

应用正则

JSON.stringify(arr).replace(/[|]/g, '') 会先将数组 arr 序列化为字符串,而后应用 replace() 办法将字符串中所有的[] 替换成空字符,从而达到扁平化解决,此时的后果为 arr 不蕴含 [] 的字符串。最初通过JSON.parse() 解析字符串。

function flat(arr) {return JSON.parse("[" + JSON.stringify(arr).replace(/\[|\]/g,'') +"]");
}

类数组转化为数组

类数组是具备 length 属性,但不具备数组原型上的办法。常见的类数组有 arguments、DOM 操作方法返回的后果 (如document.querySelectorAll('div')) 等。

扩大运算符(…)

留神:扩大运算符只能作用于 iterable 对象,即领有 Symbol(Symbol.iterator) 属性值。

let arr = [...arrayLike]

Array.from()

let arr = Array.from(arrayLike);

Array.prototype.slice.call()

let arr = Array.prototype.slice.call(arrayLike);

Array.apply()

let arr = Array.apply(null, arrayLike);

concat + apply

let arr = Array.prototype.concat.apply([], arrayLike);

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

代码输入后果

console.log('1');

setTimeout(function() {console.log('2');
    process.nextTick(function() {console.log('3');
    })
    new Promise(function(resolve) {console.log('4');
        resolve();}).then(function() {console.log('5')
    })
})
process.nextTick(function() {console.log('6');
})
new Promise(function(resolve) {console.log('7');
    resolve();}).then(function() {console.log('8')
})

setTimeout(function() {console.log('9');
    process.nextTick(function() {console.log('10');
    })
    new Promise(function(resolve) {console.log('11');
        resolve();}).then(function() {console.log('12')
    })
})

输入后果如下:

1
7
6
8
2
4
3
5
9
11
10
12

(1)第一轮事件循环流程剖析如下:

  • 整体 script 作为第一个宏工作进入主线程,遇到console.log,输入 1。
  • 遇到setTimeout,其回调函数被散发到宏工作 Event Queue 中。暂且记为setTimeout1
  • 遇到process.nextTick(),其回调函数被散发到微工作 Event Queue 中。记为process1
  • 遇到 Promisenew Promise 间接执行,输入 7。then被散发到微工作 Event Queue 中。记为then1
  • 又遇到了setTimeout,其回调函数被散发到宏工作 Event Queue 中,记为setTimeout2
宏工作 Event Queue 微工作 Event Queue
setTimeout1 process1
setTimeout2 then1

上表是第一轮事件循环宏工作完结时各 Event Queue 的状况,此时曾经输入了 1 和 7。发现了 process1then1两个微工作:

  • 执行process1,输入 6。
  • 执行then1,输入 8。

第一轮事件循环正式完结,这一轮的后果是输入 1,7,6,8。

(2)第二轮工夫循环从 **setTimeout1** 宏工作开始:

  • 首先输入 2。接下来遇到了process.nextTick(),同样将其散发到微工作 Event Queue 中,记为process2
  • new Promise立刻执行输入 4,then也散发到微工作 Event Queue 中,记为then2
宏工作 Event Queue 微工作 Event Queue
setTimeout2 process2
then2

第二轮事件循环宏工作完结,发现有 process2then2两个微工作能够执行:

  • 输入 3。
  • 输入 5。

第二轮事件循环完结,第二轮输入 2,4,3,5。

(3)第三轮事件循环开始,此时只剩 setTimeout2 了,执行。

  • 间接输入 9。
  • process.nextTick() 散发到微工作 Event Queue 中。记为process3
  • 间接执行new Promise,输入 11。
  • then 散发到微工作 Event Queue 中,记为then3
宏工作 Event Queue 微工作 Event Queue
process3
then3

第三轮事件循环宏工作执行完结,执行两个微工作 process3then3

  • 输入 10。
  • 输入 12。

第三轮事件循环完结,第三轮输入 9,11,10,12。

整段代码,共进行了三次事件循环,残缺的输入为 1,7,6,8,2,4,3,5,9,11,10,12。

说一下原型链和原型链的继承吧

  • 所有一般的 [[Prototype]] 链最终都会指向内置的 Object.prototype,其蕴含了 JavaScript 中许多通用的性能
  • 为什么能创立“类”,借助一种非凡的属性:所有的函数默认都会领有一个名为 prototype 的共有且不可枚举的属性,它会指向另外一个对象,这个对象通常被称为函数的原型
function Person(name) {this.name = name;}

Person.prototype.constructor = Person
  • 在产生 new 结构函数调用时,会将创立的新对象的 [[Prototype]] 链接到 Person.prototype 指向的对象,这个机制就被称为原型链继承
  • 办法定义在原型上,属性定义在构造函数上
  • 首先要说一下 JS 原型和实例的关系:每个构造函数(constructor)都有一个原型对象(prototype),这个原型对象蕴含一个指向此构造函数的指针属性,通过 new 进行结构函数调用生成的实例,此实例蕴含一个指向原型对象的指针,也就是通过 [[Prototype]] 链接到了这个原型对象
  • 而后说一下 JS 中属性的查找:当咱们试图援用实例对象的某个属性时,是依照这样的形式去查找的,首先查找实例对象上是否有这个属性,如果没有找到,就去结构这个实例对象的构造函数的 prototype 所指向的对象下来查找,如果还找不到,就从这个 prototype 对象所指向的构造函数的 prototype 原型对象下来查找
  • 什么是原型链:这样逐级查找形似一个链条,且通过 [[Prototype]] 属性链接,所以被称为原型链
  • 什么是原型链继承,类比类的继承:当有两个构造函数 A 和 B,将一个构造函数 A 的原型对象的,通过其 [[Prototype]] 属性链接到另外一个 B 构造函数的原型对象时,这个过程被称之为原型继承。

标准答案更正确的解释

什么是原型链?

当对象查找一个属性的时候,如果没有在本身找到,那么就会查找本身的原型,如果原型还没有找到,那么会持续查找原型的原型,直到找到 Object.prototype 的原型时,此时原型为 null,查找进行。
这种通过 通过原型链接的逐级向上的查找链被称为原型链

什么是原型继承?

一个对象能够应用另外一个对象的属性或者办法,就称之为继承。具体是通过将这个对象的原型设置为另外一个对象,这样依据原型链的规定,如果查找一个对象属性且在本身不存在时,就会查找另外一个对象,相当于一个对象能够应用另外一个对象的属性和办法了。

手写题:Promise 原理

class MyPromise {constructor(fn) {this.callbacks = [];
    this.state = "PENDING";
    this.value = null;

    fn(this._resolve.bind(this), this._reject.bind(this));
  }

  then(onFulfilled, onRejected) {return new MyPromise((resolve, reject) =>
      this._handle({
        onFulfilled: onFulfilled || null,
        onRejected: onRejected || null,
        resolve,
        reject,
      })
    );
  }

  catch(onRejected) {return this.then(null, onRejected);
  }

  _handle(callback) {if (this.state === "PENDING") {this.callbacks.push(callback);

      return;
    }

    let cb =
      this.state === "FULFILLED" ? callback.onFulfilled : callback.onRejected;
    if (!cb) {
      cb = this.state === "FULFILLED" ? callback.resolve : callback.reject;
      cb(this.value);

      return;
    }

    let ret;

    try {ret = cb(this.value);
      cb = this.state === "FULFILLED" ? callback.resolve : callback.reject;
    } catch (error) {
      ret = error;
      cb = callback.reject;
    } finally {cb(ret);
    }
  }

  _resolve(value) {if (value && (typeof value === "object" || typeof value === "function")) {
      let then = value.then;

      if (typeof then === "function") {then.call(value, this._resolve.bind(this), this._reject.bind(this));

        return;
      }
    }

    this.state === "FULFILLED";
    this.value = value;
    this.callbacks.forEach((fn) => this._handle(fn));
  }

  _reject(error) {
    this.state === "REJECTED";
    this.value = error;
    this.callbacks.forEach((fn) => this._handle(fn));
  }
}

const p1 = new Promise(function (resolve, reject) {setTimeout(() => reject(new Error("fail")), 3000);
});

const p2 = new Promise(function (resolve, reject) {setTimeout(() => resolve(p1), 1000);
});

p2.then((result) => console.log(result)).catch((error) => console.log(error));

代码输入后果

var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log(this.foo);  
        console.log(self.foo);  
        (function() {console.log(this.foo);  
            console.log(self.foo);  
        }());
    }
};
myObject.func();

输入后果:bar bar undefined bar

解析:

  1. 首先 func 是由 myObject 调用的,this 指向 myObject。又因为 var self = this; 所以 self 指向 myObject。
  2. 这个立刻执行匿名函数表达式是由 window 调用的,this 指向 window。立刻执行匿名函数的作用域处于 myObject.func 的作用域中,在这个作用域找不到 self 变量,沿着作用域链向上查找 self 变量,找到了指向 myObject 对象的 self。

setInterval 模仿 setTimeout

形容 :应用setInterval 模仿实现 setTimeout 的性能。

思路 setTimeout 的个性是在指定的工夫内只执行一次,咱们只有在 setInterval 外部执行 callback 之后,把定时器关掉即可。

实现

const mySetTimeout = (fn, time) => {
    let timer = null;
    timer = setInterval(() => {
        // 敞开定时器,保障只执行一次 fn,也就达到了 setTimeout 的成果了
        clearInterval(timer);
        fn();}, time);
    // 返回用于敞开定时器的办法
    return () => clearInterval(timer);
}

// 测试
const cancel = mySetTimeout(() => {console.log(1);
}, 1000);  
// 一秒后打印 1

为什么 0.1+0.2 ! == 0.3,如何让其相等

在开发过程中遇到相似这样的问题:

let n1 = 0.1, n2 = 0.2
console.log(n1 + n2)  // 0.30000000000000004

这里失去的不是想要的后果,要想等于 0.3,就要把它进行转化:

(n1 + n2).toFixed(2) // 留神,toFixed 为四舍五入

toFixed(num) 办法可把 Number 四舍五入为指定小数位数的数字。那为什么会呈现这样的后果呢?

计算机是通过二进制的形式存储数据的,所以计算机计算 0.1+0.2 的时候,实际上是计算的两个数的二进制的和。0.1 的二进制是0.0001100110011001100...(1100 循环),0.2 的二进制是:0.00110011001100...(1100 循环),这两个数的二进制都是有限循环的数。那 JavaScript 是如何解决有限循环的二进制小数呢?

个别咱们认为数字包含整数和小数,然而在 JavaScript 中只有一种数字类型:Number,它的实现遵循 IEEE 754 规范,应用 64 位固定长度来示意,也就是规范的 double 双精度浮点数。在二进制迷信表示法中,双精度浮点数的小数局部最多只能保留 52 位,再加上后面的 1,其实就是保留 53 位有效数字,残余的须要舍去,听从“0 舍 1 入”的准则。

依据这个准则,0.1 和 0.2 的二进制数相加,再转化为十进制数就是:0.30000000000000004

上面看一下 双精度数是如何保留 的:

  • 第一局部(蓝色):用来存储符号位(sign),用来辨别正负数,0 示意负数,占用 1 位
  • 第二局部(绿色):用来存储指数(exponent),占用 11 位
  • 第三局部(红色):用来存储小数(fraction),占用 52 位

对于 0.1,它的二进制为:

0.00011001100110011001100110011001100110011001100110011001 10011...

转为迷信计数法(迷信计数法的后果就是浮点数):

1.1001100110011001100110011001100110011001100110011001*2^-4

能够看出 0.1 的符号位为 0,指数位为 -4,小数位为:

1001100110011001100110011001100110011001100110011001

那么问题又来了,指数位是正数,该如何保留 呢?

IEEE 标准规定了一个偏移量,对于指数局部,每次都加这个偏移量进行保留,这样即便指数是正数,那么加上这个偏移量也就是负数了。因为 JavaScript 的数字是双精度数,这里就以双精度数为例,它的指数局部为 11 位,能示意的范畴就是 0~2047,IEEE 固定 双精度数的偏移量为 1023

  • 当指数位不全是 0 也不全是 1 时(规格化的数值),IEEE 规定,阶码计算公式为 e-Bias。此时 e 最小值是 1,则 1 -1023= -1022,e 最大值是 2046,则 2046-1023=1023,能够看到,这种状况下取值范畴是-1022~1013
  • 当指数位全副是 0 的时候(非规格化的数值),IEEE 规定,阶码的计算公式为 1 -Bias,即 1 -1023= -1022。
  • 当指数位全副是 1 的时候(非凡值),IEEE 规定这个浮点数可用来示意 3 个非凡值,别离是正无穷,负无穷,NaN。具体的,小数位不为 0 的时候示意 NaN;小数位为 0 时,当符号位 s = 0 时示意正无穷,s= 1 时候示意负无穷。

对于下面的 0.1 的指数位为 -4,-4+1023 = 1019 转化为二进制就是:1111111011.

所以,0.1 示意为:

0 1111111011 1001100110011001100110011001100110011001100110011001

说了这么多,是时候该最开始的问题了,如何实现 0.1+0.2=0.3 呢?

对于这个问题,一个间接的解决办法就是设置一个误差范畴,通常称为“机器精度”。对 JavaScript 来说,这个值通常为 2 -52,在 ES6 中,提供了 Number.EPSILON 属性,而它的值就是 2 -52,只有判断 0.1+0.2-0.3 是否小于Number.EPSILON,如果小于,就能够判断为 0.1+0.2 ===0.3

function numberepsilon(arg1,arg2){return Math.abs(arg1 - arg2) < Number.EPSILON;        
}        

console.log(numberepsilon(0.1 + 0.2, 0.3)); // true

代码输入后果

function fn1(){console.log('fn1')
}
var fn2

fn1()
fn2()

fn2 = function() {console.log('fn2')
}

fn2()

输入后果:

fn1
Uncaught TypeError: fn2 is not a function
fn2

这里也是在考查变量晋升,关键在于第一个 fn2(),这时 fn2 仍是一个 undefined 的变量,所以会报错 fn2 不是一个函数。

setTimeout 模仿 setInterval

形容 :应用setTimeout 模仿实现 setInterval 的性能。

实现

const mySetInterval(fn, time) {
    let timer = null;
    const interval = () => {timer = setTimeout(() => {fn();  // time 工夫之后会执行真正的函数 fn
            interval();  // 同时再次调用 interval 自身}, time)
    }
    interval();  // 开始执行
    // 返回用于敞开定时器的函数
    return () => clearTimeout(timer);
}

// 测试
const cancel = mySetInterval(() => console.log(1), 400);
setTimeout(() => {cancel();
}, 1000);  
// 打印两次 1

代码输入后果

function foo() {console.log( this.a);
}

function doFoo() {foo();
}

var obj = {
  a: 1,
  doFoo: doFoo
};

var a = 2; 
obj.doFoo()

输入后果:2

在 Javascript 中,this 指向函数执行时的以后对象。在执行 foo 的时候,执行环境就是 doFoo 函数,执行环境为全局。所以,foo 中的 this 是指向 window 的,所以会打印出 2。

代码输入后果

function runAsync (x) {const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
  return p
}
Promise.race([runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log('result:', res))
  .catch(err => console.log(err))

输入后果如下:

1
'result:' 1
2
3

then 只会捕捉第一个胜利的办法,其余的函数尽管还会继续执行,然而不是被 then 捕捉了。

单行、多行文本溢出暗藏

  • 单行文本溢出
overflow: hidden;            // 溢出暗藏
text-overflow: ellipsis;      // 溢出用省略号显示
white-space: nowrap;         // 规定段落中的文本不进行换行
  • 多行文本溢出
overflow: hidden;            // 溢出暗藏
text-overflow: ellipsis;     // 溢出用省略号显示
display:-webkit-box;         // 作为弹性伸缩盒子模型显示。-webkit-box-orient:vertical; // 设置伸缩盒子的子元素排列形式:从上到下垂直排列
-webkit-line-clamp:3;        // 显示的行数

留神:因为下面的三个属性都是 CSS3 的属性,没有浏览器能够兼容,所以要在后面加一个-webkit- 来兼容一部分浏览器。

如何解释 React 的渲染流程

  • React 的渲染过程大抵统一,但协调并不相同,以 React 16 为分界线,分为 Stack ReconcilerFiber Reconciler。这里的协调从广义上来讲,特指 React 的 diff 算法,狭义上来讲,有时候也指 React 的 reconciler 模块,它通常蕴含了 diff 算法和一些公共逻辑。
  • 回到 Stack Reconciler 中,Stack Reconciler 外围调度形式是递归 调度的根本解决单位是事务 ,它的事务基类是 Transaction,这里的 事务是 React 团队从后端开发中退出的概念 。在 React 16 以前, 挂载次要通过 ReactMount 模块实现,更新通过 ReactUpdate 模块实现,模块之间互相拆散,落脚执行点也是事务。
  • React 16 及当前,协调改为了 Fiber Reconciler。它的调度形式次要有两个特点,第一个是合作式多任务模式 ,在这个模式下,线程会定时放弃本人的运行权力,交还给主线程,通过requestIdleCallback 实现。 第二个特点是策略优先级 ,调度工作通过标记 tag 的形式分优先级执行,比方动画,或者标记为 high 的工作能够优先执行。Fiber Reconciler 的根本单位是 FiberFiber 基于过来的 React Element 提供了二次封装,提供了指向父、子、兄弟节点的援用,为 diff 工作的双链表实现提供了根底。
  • 在新的架构下,整个生命周期被划分为 Render 和 Commit 两个阶段 Render 阶段的执行特点是可中断、可进行、无副作用,次要是通过结构 workInProgress 树计算出 diff。以 current 树为根底,将每个 Fiber 作为一个根本单位,自下而上一一节点查看并结构 workInProgress 树。这个过程不再是递归,而是基于循环来实现
  • 在执行上通过 requestIdleCallback 来调度执行每组工作,每组中的每个计算工作被称为 work,每个 work 实现后确认是否有优先级更高的 work 须要插入,如果有就让位,没有就持续。优先级通常是标记为动画或者 high 的会先解决。每实现一组后,将调度权交回主线程,直到下一次 requestIdleCallback 调用,再持续构建 workInProgress
  • commit 阶段须要解决 effect 列表,这里的 effect 列表蕴含了依据 diff 更新 DOM 树 回调生命周期 响应 ref 等。
  • 但肯定要留神,这个阶段是同步执行的,不可中断暂停,所以不要在 componentDidMountcomponentDidUpdatecomponentWiilUnmount中去执行重度耗费算力的工作
  • 如果只是个别的利用场景,比方治理后盾、H5 展现页等,两者性能差距并不大,但在动画、画布及手势等场景下,Stack Reconciler 的设计会占用占主线程,造成卡顿,而 fiber reconciler 的设计则能带来高性能的体现

程度垂直居中的实现

  • 利用相对定位,先将元素的左上角通过 top:50% 和 left:50% 定位到页面的核心,而后再通过 translate 来调整元素的中心点到页面的核心。该办法须要 思考浏览器兼容问题。
.parent {position: relative;} .child {position: absolute;    left: 50%;    top: 50%;    transform: translate(-50%,-50%);}
  • 利用相对定位,设置四个方向的值都为 0,并将 margin 设置为 auto,因为宽高固定,因而对应方向实现平分,能够实现程度和垂直方向上的居中。该办法实用于 盒子有宽高 的状况:
.parent {position: relative;}

.child {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}
  • 利用相对定位,先将元素的左上角通过 top:50% 和 left:50% 定位到页面的核心,而后再通过 margin 负值来调整元素的中心点到页面的核心。该办法实用于 盒子宽高已知 的状况
.parent {position: relative;}

.child {
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -50px;     /* 本身 height 的一半 */
    margin-left: -50px;    /* 本身 width 的一半 */
}
  • 应用 flex 布局,通过 align-items:center 和 justify-content:center 设置容器的垂直和程度方向上为居中对齐,而后它的子元素也能够实现垂直和程度的居中。该办法要 思考兼容的问题,该办法在挪动端用的较多:
.parent {
    display: flex;
    justify-content:center;
    align-items:center;
}

代码输入后果

function Person(name) {this.name = name}
var p2 = new Person('king');
console.log(p2.__proto__) //Person.prototype
console.log(p2.__proto__.__proto__) //Object.prototype
console.log(p2.__proto__.__proto__.__proto__) // null
console.log(p2.__proto__.__proto__.__proto__.__proto__)//null 前面没有了,报错
console.log(p2.__proto__.__proto__.__proto__.__proto__.__proto__)//null 前面没有了,报错
console.log(p2.constructor)//Person
console.log(p2.prototype)//undefined p2 是实例,没有 prototype 属性
console.log(Person.constructor)//Function 一个空函数
console.log(Person.prototype)// 打印出 Person.prototype 这个对象里所有的办法和属性
console.log(Person.prototype.constructor)//Person
console.log(Person.prototype.__proto__)// Object.prototype
console.log(Person.__proto__) //Function.prototype
console.log(Function.prototype.__proto__)//Object.prototype
console.log(Function.__proto__)//Function.prototype
console.log(Object.__proto__)//Function.prototype
console.log(Object.prototype.__proto__)//null

这道义题目考查原型、原型链的根底,记住就能够了。

正文完
 0