共计 11755 个字符,预计需要花费 30 分钟才能阅读完成。
首屏和白屏工夫如何计算
首屏工夫的计算,能够由 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 和 $emit
2. 地方事件总线 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);
输入后果如下:
1
2
4
timerStart
timerEnd
success
代码执行过程如下:
- 首先遇到 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(); // 2
obj.bar(); // 1
var 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 = Dog
let 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