首屏和白屏工夫如何计算
首屏工夫的计算,能够由 Native WebView 提供的相似 onload 的办法实现,在 ios 下对应的是 webViewDidFinishLoad,在 android 下对应的是onPageFinished事件。
白屏的定义有多种。能够认为“没有任何内容”是白屏,能够认为“网络或服务异样”是白屏,能够认为“数据加载中”是白屏,能够认为“图片加载不进去”是白屏。场景不同,白屏的计算形式就不雷同。
办法1:当页面的元素数小于x时,则认为页面白屏。比方“没有任何内容”,能够获取页面的DOM节点数,判断DOM节点数少于某个阈值X,则认为白屏。 办法2:当页面呈现业务定义的错误码时,则认为是白屏。比方“网络或服务异样”。 办法3:当页面呈现业务定义的特征值时,则认为是白屏。比方“数据加载中”。
具体阐明 Event loop
家喻户晓 JS 是门非阻塞单线程语言,因为在最后 JS 就是为了和浏览器交互而诞生的。如果 JS 是门多线程的语言话,咱们在多个线程中解决 DOM 就可能会产生问题(一个线程中新加节点,另一个线程中删除节点),当然能够引入读写锁解决这个问题。
JS 在执行的过程中会产生执行环境,这些执行环境会被程序的退出到执行栈中。如果遇到异步的代码,会被挂起并退出到 Task(有多种 task) 队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出须要执行的代码并放入执行栈中执行,所以实质上来说 JS 中的异步还是同步行为。
console.log('script start');setTimeout(function() { console.log('setTimeout');}, 0);console.log('script end');
以上代码尽管 setTimeout
延时为 0,其实还是异步。这是因为 HTML5 标准规定这个函数第二个参数不得小于 4 毫秒,有余会主动减少。所以 setTimeout
还是会在 script end
之后打印。
不同的工作源会被调配到不同的 Task 队列中,工作源能够分为 微工作(microtask) 和 宏工作(macrotask)。在 ES6 标准中,microtask 称为 jobs
,macrotask 称为 task
。
console.log('script start');setTimeout(function() { console.log('setTimeout');}, 0);new Promise((resolve) => { console.log('Promise') resolve()}).then(function() { console.log('promise1');}).then(function() { console.log('promise2');});console.log('script end');// script start => Promise => script end => promise1 => promise2 => setTimeout
以上代码尽管 setTimeout
写在 Promise
之前,然而因为 Promise
属于微工作而 setTimeout
属于宏工作,所以会有以上的打印。
微工作包含 process.nextTick
,promise
,Object.observe
,MutationObserver
宏工作包含 script
, setTimeout
,setInterval
,setImmediate
,I/O
,UI rendering
很多人有个误区,认为微工作快于宏工作,其实是谬误的。因为宏工作中包含了 script
,浏览器会先执行一个宏工作,接下来有异步代码的话就先执行微工作。
所以正确的一次 Event loop 程序是这样的
- 执行同步代码,这属于宏工作
- 执行栈为空,查问是否有微工作须要执行
- 执行所有微工作
- 必要的话渲染 UI
- 而后开始下一轮 Event loop,执行宏工作中的异步代码
通过上述的 Event loop 程序可知,如果宏工作中的异步代码有大量的计算并且须要操作 DOM 的话,为了更快的 界面响应,咱们能够把操作 DOM 放入微工作中。
Node 中的 Event loop
Node 中的 Event loop 和浏览器中的不雷同。
Node 的 Event loop 分为6个阶段,它们会依照程序重复运行
┌───────────────────────┐┌─>│ timers ││ └──────────┬────────────┘│ ┌──────────┴────────────┐│ │ I/O callbacks ││ └──────────┬────────────┘│ ┌──────────┴────────────┐│ │ idle, prepare ││ └──────────┬────────────┘ ┌───────────────┐│ ┌──────────┴────────────┐ │ incoming: ││ │ poll │<──connections─── ││ └──────────┬────────────┘ │ data, etc. ││ ┌──────────┴────────────┐ └───────────────┘│ │ check ││ └──────────┬────────────┘│ ┌──────────┴────────────┐└──┤ close callbacks │ └───────────────────────┘
timer
timers 阶段会执行 setTimeout
和 setInterval
一个 timer
指定的工夫并不是精确工夫,而是在达到这个工夫后尽快执行回调,可能会因为零碎正在执行别的事务而提早。
上限的工夫有一个范畴:[1, 2147483647]
,如果设定的工夫不在这个范畴,将被设置为1。
I/O
I/O 阶段会执行除了 close 事件,定时器和 setImmediate
的回调
idle, prepare
idle, prepare 阶段外部实现
poll
poll 阶段很重要,这一阶段中,零碎会做两件事件
- 执行到点的定时器
- 执行 poll 队列中的事件
并且当 poll 中没有定时器的状况下,会发现以下两件事件
- 如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者零碎限度
如果 poll 队列为空,会有两件事产生
- 如果有
setImmediate
须要执行,poll 阶段会进行并且进入到 check 阶段执行setImmediate
- 如果没有
setImmediate
须要执行,会期待回调被退出到队列中并立刻执行回调
- 如果有
如果有别的定时器须要被执行,会回到 timer 阶段执行回调。
check
check 阶段执行 setImmediate
close callbacks
close callbacks 阶段执行 close 事件
并且在 Node 中,有些状况下的定时器执行程序是随机的
setTimeout(() => { console.log('setTimeout');}, 0);setImmediate(() => { console.log('setImmediate');})// 这里可能会输入 setTimeout,setImmediate// 可能也会相同的输入,这取决于性能// 因为可能进入 event loop 用了不到 1 毫秒,这时候会执行 setImmediate// 否则会执行 setTimeout
当然在这种状况下,执行程序是雷同的
var fs = require('fs')fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); });});// 因为 readFile 的回调在 poll 中执行// 发现有 setImmediate ,所以会立刻跳到 check 阶段执行回调// 再去 timer 阶段执行 setTimeout// 所以以上输入肯定是 setImmediate,setTimeout
下面介绍的都是 macrotask 的执行状况,microtask 会在以上每个阶段实现后立刻执行。
setTimeout(()=>{ console.log('timer1') Promise.resolve().then(function() { console.log('promise1') })}, 0)setTimeout(()=>{ console.log('timer2') Promise.resolve().then(function() { console.log('promise2') })}, 0)// 以上代码在浏览器和 node 中打印状况是不同的// 浏览器中打印 timer1, promise1, timer2, promise2// node 中打印 timer1, timer2, promise1, promise2
Node 中的 process.nextTick
会先于其余 microtask 执行。
setTimeout(() => { console.log("timer1"); Promise.resolve().then(function() { console.log("promise1"); });}, 0);process.nextTick(() => { console.log("nextTick");});// nextTick, timer1, promise1
new 一个函数产生了什么
结构调用:
- 发明一个全新的对象
- 这个对象会被执行 [[Prototype]] 连贯,将这个新对象的 [[Prototype]] 链接到这个构造函数.prototype 所指向的对象
- 这个新对象会绑定到函数调用的 this
- 如果函数没有返回其余对象,那么 new 表达式中的函数调用会主动返回这个新对象
代码输入后果
Promise.reject('err!!!') .then((res) => { console.log('success', res) }, (err) => { console.log('error', err) }).catch(err => { console.log('catch', err) })
输入后果如下:
error err!!!
咱们晓得,.then
函数中的两个参数:
- 第一个参数是用来解决Promise胜利的函数
- 第二个则是解决失败的函数
也就是说Promise.resolve('1')
的值会进入胜利的函数,Promise.reject('2')
的值会进入失败的函数。
在这道题中,谬误间接被then
的第二个参数捕捉了,所以就不会被catch
捕捉了,输入后果为:error err!!!'
然而,如果是像上面这样:
Promise.resolve() .then(function success (res) { throw new Error('error!!!') }, function fail1 (err) { console.log('fail1', err) }).catch(function fail2 (err) { console.log('fail2', err) })
在then
的第一参数中抛出了谬误,那么他就不会被第二个参数不活了,而是被前面的catch
捕捉到。
事件是什么?事件模型?
事件是用户操作网页时产生的交互动作,比方 click/move, 事件除了用户触发的动作外,还能够是文档加载,窗口滚动和大小调整。事件被封装成一个 event 对象,蕴含了该事件产生时的所有相干信息( event 的属性)以及能够对事件进行的操作( event 的办法)。
事件是用户操作网页时产生的交互动作或者网页自身的一些操作,古代浏览器一共有三种事件模型:
- DOM0 级事件模型,这种模型不会流传,所以没有事件流的概念,然而当初有的浏览器反对以冒泡的形式实现,它能够在网页中间接定义监听函数,也能够通过 js 属性来指定监听函数。所有浏览器都兼容这种形式。间接在dom对象上注册事件名称,就是DOM0写法。
- IE 事件模型,在该事件模型中,一次事件共有两个过程,事件处理阶段和事件冒泡阶段。事件处理阶段会首先执行指标元素绑定的监听事件。而后是事件冒泡阶段,冒泡指的是事件从指标元素冒泡到 document,顺次查看通过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过attachEvent 来增加监听函数,能够增加多个监听函数,会按程序顺次执行。
- DOM2 级事件模型,在该事件模型中,一次事件共有三个过程,第一个过程是事件捕捉阶段。捕捉指的是事件从 document 始终向下流传到指标元素,顺次查看通过的节点是否绑定了事件监听函数,如果有则执行。前面两个阶段和 IE 事件模型的两个阶段雷同。这种事件模型,事件绑定的函数是addEventListener,其中第三个参数能够指定事件是否在捕捉阶段执行。
Vue通信
1.props和$emit2.地方事件总线 EventBus(根本不必)3.vuex(官网举荐状态管理器)4.$parent和$children当然还有一些其余方法,但根本不罕用,或者用起来太简单来。 介绍来通信的形式,还能够扩大说一下应用场景,如何应用,注意事项之类的。
参考 前端进阶面试题具体解答
箭头函数和一般函数有啥区别?箭头函数能当构造函数吗?
- 一般函数通过 function 关键字定义, this 无奈联合词法作用域应用,在运行时绑定,只取决于函数的调用形式,在哪里被调用,调用地位。(取决于调用者,和是否独立运行)
箭头函数应用被称为 “胖箭头” 的操作
=>
定义,箭头函数不利用一般函数 this 绑定的四种规定,而是依据外层(函数或全局)的作用域来决定 this,且箭头函数的绑定无奈被批改(new 也不行)。- 箭头函数罕用于回调函数中,包含事件处理器或定时器
- 箭头函数和 var self = this,都试图取代传统的 this 运行机制,将 this 的绑定拉回到词法作用域
- 没有原型、没有 this、没有 super,没有 arguments,没有 new.target
不能通过 new 关键字调用
- 一个函数外部有两个办法:[[Call]] 和 [[Construct]],在通过 new 进行函数调用时,会执行 [[construct]] 办法,创立一个实例对象,而后再执行这个函数体,将函数的 this 绑定在这个实例对象上
- 当间接调用时,执行 [[Call]] 办法,间接执行函数体
- 箭头函数没有 [[Construct]] 办法,不能被用作结构函数调用,当应用 new 进行函数调用时会报错。
function foo() { return (a) => { console.log(this.a); }}var obj1 = { a: 2}var obj2 = { a: 3 }var bar = foo.call(obj1);bar.call(obj2);
哪些状况会导致内存透露
以下四种状况会造成内存的透露:
- 意外的全局变量: 因为应用未声明的变量,而意外的创立了一个全局变量,而使这个变量始终留在内存中无奈被回收。
- 被忘记的计时器或回调函数: 设置了 setInterval 定时器,而遗记勾销它,如果循环函数有对外部变量的援用的话,那么这个变量会被始终留在内存中,而无奈被回收。
- 脱离 DOM 的援用: 获取一个 DOM 元素的援用,而前面这个元素被删除,因为始终保留了对这个元素的援用,所以它也无奈被回收。
- 闭包: 不合理的应用闭包,从而导致某些变量始终被留在内存当中。
代码输入后果
const promise = new Promise((resolve, reject) => { console.log(1); setTimeout(() => { console.log("timerStart"); resolve("success"); console.log("timerEnd"); }, 0); console.log(2);});promise.then((res) => { console.log(res);});console.log(4);
输入后果如下:
124timerStarttimerEndsuccess
代码执行过程如下:
- 首先遇到Promise构造函数,会先执行外面的内容,打印
1
; - 遇到定时器
steTimeout
,它是一个宏工作,放入宏工作队列; - 持续向下执行,打印出2;
- 因为
Promise
的状态此时还是pending
,所以promise.then
先不执行; - 继续执行上面的同步工作,打印出4;
- 此时微工作队列没有工作,继续执行下一轮宏工作,执行
steTimeout
; - 首先执行
timerStart
,而后遇到了resolve
,将promise
的状态改为resolved
且保留后果并将之前的promise.then
推入微工作队列,再执行timerEnd
; - 执行完这个宏工作,就去执行微工作
promise.then
,打印出resolve
的后果。
快排--工夫复杂度 nlogn~ n^2 之间
题目形容:实现一个快排
实现代码如下:
function quickSort(arr) { if (arr.length < 2) { return arr; } const cur = arr[arr.length - 1]; const left = arr.filter((v, i) => v <= cur && i !== arr.length - 1); const right = arr.filter((v) => v > cur); return [...quickSort(left), cur, ...quickSort(right)];}// console.log(quickSort([3, 6, 2, 4, 1]));
代码输入后果
var a = 1;function printA(){ console.log(this.a);}var obj={ a:2, foo:printA, bar:function(){ printA(); }}obj.foo(); // 2obj.bar(); // 1var foo = obj.foo;foo(); // 1
输入后果: 2 1 1
解析:
- obj.foo(),foo 的this指向obj对象,所以a会输入2;
- obj.bar(),printA在bar办法中执行,所以此时printA的this指向的是window,所以会输入1;
- foo(),foo是在全局对象中执行的,所以其this指向的是window,所以会输入1;
同步和异步的区别
- 同步指的是当一个过程在执行某个申请时,如果这个申请须要期待一段时间能力返回,那么这个过程会始终期待上来,直到音讯返回为止再持续向下执行。
- 异步指的是当一个过程在执行某个申请时,如果这个申请须要期待一段时间能力返回,这个时候过程会持续往下执行,不会阻塞期待音讯的返回,当音讯返回时零碎再告诉过程进行解决。
懒加载与预加载的区别
这两种形式都是进步网页性能的形式,两者次要区别是一个是提前加载,一个是缓慢甚至不加载。懒加载对服务器前端有肯定的缓解压力作用,预加载则会减少服务器前端压力。
- 懒加载也叫提早加载,指的是在长网页中提早加载图片的机会,当用户须要拜访时,再去加载,这样能够进步网站的首屏加载速度,晋升用户的体验,并且能够缩小服务器的压力。它实用于图片很多,页面很长的电商网站的场景。懒加载的实现原理是,将页面上的图片的 src 属性设置为空字符串,将图片的实在门路保留在一个自定义属性中,当页面滚动的时候,进行判断,如果图片进入页面可视区域内,则从自定义属性中取出实在门路赋值给图片的 src 属性,以此来实现图片的提早加载。
- 预加载指的是将所需的资源提前申请加载到本地,这样前面在须要用到时就间接从缓存取资源。 通过预加载可能缩小用户的等待时间,进步用户的体验。我理解的预加载的最罕用的形式是应用 js 中的 image 对象,通过为 image 对象来设置 scr 属性,来实现图片的预加载。
继承
原型链继承
function Animal() { this.colors = ['black', 'white']}Animal.prototype.getColor = function() { return this.colors}function Dog() {}Dog.prototype = new Animal()let dog1 = new Dog()dog1.colors.push('brown')let dog2 = new Dog()console.log(dog2.colors) // ['black', 'white', 'brown']
原型链继承存在的问题:
- 问题1:原型中蕴含的援用类型属性将被所有实例共享;
- 问题2:子类在实例化的时候不能给父类构造函数传参;
借用构造函数实现继承
function Animal(name) { this.name = name this.getName = function() { return this.name }}function Dog(name) { Animal.call(this, name)}Dog.prototype = new Animal()
借用构造函数实现继承解决了原型链继承的 2 个问题:援用类型共享问题以及传参问题。然而因为办法必须定义在构造函数中,所以会导致每次创立子类实例都会创立一遍办法。
组合继承
组合继承联合了原型链和盗用构造函数,将两者的长处集中了起来。根本的思路是应用原型链继承原型上的属性和办法,而通过盗用构造函数继承实例属性。这样既能够把办法定义在原型上以实现重用,又能够让每个实例都有本人的属性。
function Animal(name) { this.name = name this.colors = ['black', 'white']}Animal.prototype.getName = function() { return this.name}function Dog(name, age) { Animal.call(this, name) this.age = age}Dog.prototype = new Animal()Dog.prototype.constructor = Doglet dog1 = new Dog('奶昔', 2)dog1.colors.push('brown')let dog2 = new Dog('哈赤', 1)console.log(dog2) // { name: "哈赤", colors: ["black", "white"], age: 1 }
寄生式组合继承
组合继承曾经绝对欠缺了,但还是存在问题,它的问题就是调用了 2 次父类构造函数,第一次是在 new Animal(),第二次是在 Animal.call() 这里。
所以解决方案就是不间接调用父类构造函数给子类原型赋值,而是通过创立空函数 F 获取父类原型的正本。
寄生式组合继承写法上和组合继承根本相似,区别是如下这里:
- Dog.prototype = new Animal()- Dog.prototype.constructor = Dog+ function F() {}+ F.prototype = Animal.prototype+ let f = new F()+ f.constructor = Dog+ Dog.prototype = f
略微封装下下面增加的代码后:
function object(o) { function F() {} F.prototype = o return new F()}function inheritPrototype(child, parent) { let prototype = object(parent.prototype) prototype.constructor = child child.prototype = prototype}inheritPrototype(Dog, Animal)
如果你厌弃下面的代码太多了,还能够基于组合继承的代码改成最简略的寄生式组合继承:
- Dog.prototype = new Animal()- Dog.prototype.constructor = Dog+ Dog.prototype = Object.create(Animal.prototype)+ Dog.prototype.constructor = Dog
class 实现继承
class Animal { constructor(name) { this.name = name } getName() { return this.name }}class Dog extends Animal { constructor(name, age) { super(name) this.age = age }}
如果一个构造函数,bind了一个对象,用这个构造函数创立出的实例会继承这个对象的属性吗?为什么?
不会继承,因为依据 this 绑定四大规定,new 绑定的优先级高于 bind 显示绑定,通过 new 进行结构函数调用时,会创立一个新对象,这个新对象会代替 bind 的对象绑定,作为此函数的 this,并且在此函数没有返回对象的状况下,返回这个新建的对象
CDN的概念
CDN(Content Delivery Network,内容散发网络)是指一种通过互联网相互连贯的电脑网络零碎,利用最靠近每位用户的服务器,更快、更牢靠地将音乐、图片、视频、应用程序及其他文件发送给用户,来提供高性能、可扩展性及低成本的网络内容传递给用户。
典型的CDN零碎由上面三个局部组成:
- 散发服务零碎: 最根本的工作单元就是Cache设施,cache(边缘cache)负责间接响应最终用户的拜访申请,把缓存在本地的内容疾速地提供给用户。同时cache还负责与源站点进行内容同步,把更新的内容以及本地没有的内容从源站点获取并保留在本地。Cache设施的数量、规模、总服务能力是掂量一个CDN零碎服务能力的最根本的指标。
- 负载平衡零碎: 次要性能是负责对所有发动服务申请的用户进行拜访调度,确定提供给用户的最终理论拜访地址。两级调度体系分为全局负载平衡(GSLB)和本地负载平衡(SLB)。全局负载平衡次要依据用户就近性准则,通过对每个服务节点进行“最优”判断,确定向用户提供服务的cache的物理地位。本地负载平衡次要负责节点外部的设施负载平衡
- 经营管理系统: 经营管理系统分为经营治理和网络管理子系统,负责解决业务层面的与外界零碎交互所必须的收集、整顿、交付工作,蕴含客户治理、产品治理、计费治理、统计分析等性能。
事件循环机制 (Event Loop)
事件循环机制从整体上通知了咱们 JavaScript 代码的执行程序 Event Loop
即事件循环,是指浏览器或Node
的一种解决javaScript
单线程运行时不会阻塞的一种机制,也就是咱们常常应用异步的原理。
先执行 Script 脚本,而后清空微工作队列,而后开始下一轮事件循环,持续先执行宏工作,再清空微工作队列,如此往返。
- 宏工作:Script/setTimeout/setInterval/setImmediate/ I/O / UI Rendering
- 微工作:process.nextTick()/Promise
上诉的 setTimeout 和 setInterval 等都是工作源,真正进入工作队列的是他们散发的工作。
优先级
- setTimeout = setInterval 一个队列
- setTimeout > setImmediate
- process.nextTick > Promise
for (const macroTask of macroTaskQueue) { handleMacroTask(); for (const microTask of microTaskQueue) { handleMicroTask(microTask); }}
字符串模板
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
变量晋升
函数在运行的时候,会首先创立执行上下文,而后将执行上下文入栈,而后当此执行上下文处于栈顶时,开始运行执行上下文。
在创立执行上下文的过程中会做三件事:创立变量对象,创立作用域链,确定 this 指向,其中创立变量对象的过程中,首先会为 arguments 创立一个属性,值为 arguments,而后会扫码 function 函数申明,创立一个同名属性,值为函数的援用,接着会扫码 var 变量申明,创立一个同名属性,值为 undefined,这就是变量晋升。
instance 如何应用
右边能够是任意值,左边只能是函数
'hello tuture' instanceof String // false