理论上,既然样式表不改变 DOM 树,也就没有必要停下文档的解析等待它们。然而,存在一个问题,JavaScript 脚本执行时可能在文档的解析过程中请求样式信息,如果样式还没有加载和解析,脚本将得到错误的值,显然这将会导致很多问题。所以如果浏览器尚未完成 CSSOM 的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟 JavaScript 脚本执行和文档的解析,直至其完成 CSSOM 的下载和构建。也就是说,在这种情况下,浏览器会先下载和构建 CSSOM,然后再执行 JavaScript,最后再继续文档的解析。
浏览器渲染主要有以下步骤:
大致过程如图所示:
注意: 这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html 都解析完成之后再去构建和布局 render 树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。
symbol 有什么用处可以用来表示一个独一无二的变量防止命名冲突。但是面试官问还有吗?我没想出其他的用处就直接答我不知道了,还可以利用 symbol 不会被常规的方法(除了 Object.getOwnPropertySymbols 外)遍历到,所以可以用来模拟私有变量。
主要用来提供遍历接口,布置了 symbol.iterator 的对象才可以使用 for···of 循环,可以统一处理数据结构。调用之后回返回一个遍历器对象,包含有一个 next 方法,使用 next 方法后有两个返回值 value 和 done 分别表示函数当前执行位置的值和是否遍历完毕。
Symbol.for() 可以在全局访问 symbol
描述:所有 promise 的状态都变成 fulfilled,就会返回一个状态为 fulfilled 的数组(所有promise 的 value)。只要有一个失败,就返回第一个状态为 rejected 的 promise 实例的 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"));
});
}function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
console.log(instance.getSuperValue());输出结果:true
实际上,这段代码就是在实现原型链继承,SubType继承了SuperType,本质是重写了SubType的原型对象,代之以一个新类型的实例。SubType的原型被重写了,所以instance.constructor指向的是SuperType。具体如下:
1)Promise基本特性
2)Promise优点
3)Promise缺点
4)简单代码实现
最简单的Promise实现有7个主要属性, state(状态), value(成功返回值), reason(错误信息), resolve方法, reject方法, then方法
class Promise{
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
}
};
let reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
}
};
try {
// 立即执行函数
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
let x = onFulfilled(this.value);
};
if (this.state === 'rejected') {
let x = onRejected(this.reason);
};
}
}5)面试够用版
function myPromise(constructor){ let self=this;
self.status="pending" //定义状态改变前的初始状态
self.value=undefined;//定义状态为resolved的时候的状态
self.reason=undefined;//定义状态为rejected的时候的状态
function resolve(value){
//两个==="pending",保证了了状态的改变是不不可逆的
if(self.status==="pending"){
self.value=value;
self.status="resolved";
}
}
function reject(reason){
//两个==="pending",保证了了状态的改变是不不可逆的
if(self.status==="pending"){
self.reason=reason;
self.status="rejected";
}
}
//捕获构造异常
try{
constructor(resolve,reject);
}catch(e){
reject(e);
}
}
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
switch(self.status){
case "resolved": onFullfilled(self.value); break;
case "rejected": onRejected(self.reason); break;
default:
}
}
// 测试
var p=new myPromise(function(resolve,reject){resolve(1)});
p.then(function(x){console.log(x)})
//输出16)大厂专供版
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
const resolvePromise = (promise, x, resolve, reject) => {
if (x === promise) {
// If promise and x refer to the same object, reject promise with a TypeError as the reason.
reject(new TypeError('循环引用'))
}
// if x is an object or function,
if (x !== null && typeof x === 'object' || typeof x === 'function') {
// If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
let called
try { // If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
let then = x.then // Let then be x.then
// If then is a function, call it with x as this
if (typeof then === 'function') {
// If/when resolvePromise is called with a value y, run [[Resolve]](promise, y)
// If/when rejectPromise is called with a reason r, reject promise with r.
then.call(x, y => {
if (called) return
called = true
resolvePromise(promise, y, resolve, reject)
}, r => {
if (called) return
called = true
reject(r)
})
} else {
// If then is not a function, fulfill promise with x.
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
// If x is not an object or function, fulfill promise with x
resolve(x)
}
}
function Promise(excutor) {
let that = this; // 缓存当前promise实例例对象
that.status = PENDING; // 初始状态
that.value = undefined; // fulfilled状态时 返回的信息
that.reason = undefined; // rejected状态时 拒绝的原因
that.onFulfilledCallbacks = []; // 存储fulfilled状态对应的onFulfilled函数
that.onRejectedCallbacks = []; // 存储rejected状态对应的onRejected函数
function resolve(value) { // value成功态时接收的终值
if(value instanceof Promise) {
return value.then(resolve, reject);
}
// 实践中要确保 onFulfilled 和 onRejected ⽅方法异步执⾏行行,且应该在 then ⽅方法被调⽤用的那⼀一轮事件循环之后的新执⾏行行栈中执⾏行行。
setTimeout(() => {
// 调⽤用resolve 回调对应onFulfilled函数
if (that.status === PENDING) {
// 只能由pending状态 => fulfilled状态 (避免调⽤用多次resolve reject)
that.status = FULFILLED;
that.value = value;
that.onFulfilledCallbacks.forEach(cb => cb(that.value));
}
});
}
function reject(reason) { // reason失败态时接收的拒因
setTimeout(() => {
// 调⽤用reject 回调对应onRejected函数
if (that.status === PENDING) {
// 只能由pending状态 => rejected状态 (避免调⽤用多次resolve reject)
that.status = REJECTED;
that.reason = reason;
that.onRejectedCallbacks.forEach(cb => cb(that.reason));
}
});
}
// 捕获在excutor执⾏行行器器中抛出的异常
// new Promise((resolve, reject) => {
// throw new Error('error in excutor')
// })
try {
excutor(resolve, reject);
} catch (e) {
reject(e);
}
}
Promise.prototype.then = function(onFulfilled, onRejected) {
const that = this;
let newPromise;
// 处理理参数默认值 保证参数后续能够继续执⾏行行
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
onRejected = typeof onRejected === "function" ? onRejected : reason => {
throw reason;
};
if (that.status === FULFILLED) { // 成功态
return newPromise = new Promise((resolve, reject) => {
setTimeout(() => {
try{
let x = onFulfilled(that.value);
resolvePromise(newPromise, x, resolve, reject); //新的promise resolve 上⼀一个onFulfilled的返回值
} catch(e) {
reject(e); // 捕获前⾯面onFulfilled中抛出的异常then(onFulfilled, onRejected);
}
});
})
}
if (that.status === REJECTED) { // 失败态
return newPromise = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(that.reason);
resolvePromise(newPromise, x, resolve, reject);
} catch(e) {
reject(e);
}
});
});
}
if (that.status === PENDING) { // 等待态
// 当异步调⽤用resolve/rejected时 将onFulfilled/onRejected收集暂存到集合中
return newPromise = new Promise((resolve, reject) => {
that.onFulfilledCallbacks.push((value) => {
try {
let x = onFulfilled(value);
resolvePromise(newPromise, x, resolve, reject);
} catch(e) {
reject(e);
}
});
that.onRejectedCallbacks.push((reason) => {
try {
let x = onRejected(reason);
resolvePromise(newPromise, x, resolve, reject);
} catch(e) {
reject(e);
}
});
});
}
};参考 前端进阶面试题详细解答
HTTPS的优点如下:
HTTPS的缺点如下:
三者的区别:
使用场景:
是基于原型的动态语言,主要独特特性有 this、原型和原型链。
JS 严格意义上来说分为:语言标准部分(ECMAScript)+ 宿主环境部分
语言标准部分
2015 年发布 ES6,引入诸多新特性使得能够编写大型项目变成可能,标准自 2015 之后以年号代号,每年一更
宿主环境部分
.parent { position: relative;} .child { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%);}.parent {
position: relative;
}
.child {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
margin-top: -50px; /* 自身 height 的一半 */
margin-left: -50px; /* 自身 width 的一半 */
}.parent {
display: flex;
justify-content:center;
align-items:center;
}浮动的定义: 非IE浏览器下,容器不设高度且子元素浮动时,容器高度不能被内容撑开。 此时,内容会溢出到容器外面而影响布局。这种现象被称为浮动(溢出)。
浮动的工作原理:
浮动元素可以左右移动,直到遇到另一个浮动元素或者遇到它外边缘的包含框。浮动框不属于文档流中的普通流,当元素浮动之后,不会影响块级元素的布局,只会影响内联元素布局。此时文档流中的普通流就会表现得该浮动框不存在一样的布局模式。当包含框的高度小于浮动框的时候,此时就会出现“高度塌陷”。
浮动元素引起的问题?
清除浮动的方式如下:
height属性clear:both样式overflow:hidden或者overflow:auto.clearfix:after{
content: "\200B";
display: table;
height: 0;
clear: both;
}
.clearfix{
*zoom: 1;
}CSRF 攻击指的是跨站请求伪造攻击,攻击者诱导用户进入一个第三方网站,然后该网站向被攻击网站发送跨站请求。如果用户在被攻击网站中保存了登录状态,那么攻击者就可以利用这个登录状态,绕过后台的用户验证,冒充用户向服务器执行一些操作。
CSRF 攻击的本质是利用 cookie 会在同源请求中携带发送给服务器的特点,以此来实现用户的冒充。
常见的 CSRF 攻击有三种:
keep-alive 来设置Range 来实现。二进制分帧 这是一次彻底的二进制协议,头信息和数据体都是二进制,并且统称为"帧":头信息帧和数据帧。头部压缩 HTTP 1.1版本会出现 User-Agent、Cookie、Accept、Server、Range 等字段可能会占用几百甚至几千字节,而 Body 却经常只有几十字节,所以导致头部偏重。HTTP 2.0 使用 HPACK 算法进行压缩。多路复用 复用TCP连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,且不用按顺序一一对应,这样子解决了队头阻塞的问题。服务器推送 允许服务器未经请求,主动向客户端发送资源,即服务器推送。请求优先级 可以设置数据帧的优先级,让服务端先处理重要资源,优化用户体验。首先要介绍ToPrimitive方法,这是 JavaScript 中每个值隐含的自带的方法,用来将值 (无论是基本类型值还是对象)转换为基本类型值。如果值为基本类型,则直接返回值本身;如果值为对象,其看起来大概是这样:
/*** @obj 需要转换的对象* @type 期望的结果类型*/
ToPrimitive(obj,type)type的值为number或者string。
(1)当type为number时规则如下:
obj的valueOf方法,如果为原始值,则返回,否则下一步;obj的toString方法,后续同上;TypeError 异常。(2)当type为string时规则如下:
obj的toString方法,如果为原始值,则返回,否则下一步;obj的valueOf方法,后续同上;TypeError 异常。可以看出两者的主要区别在于调用toString和valueOf的先后顺序。默认情况下:
type默认为string;type默认为number。总结上面的规则,对于 Date 以外的对象,转换为基本类型的大概规则可以概括为一个函数:
var objToNumber = value => Number(value.valueOf().toString())
objToNumber([]) === 0
objToNumber({}) === NaN而 JavaScript 中的隐式类型转换主要发生在+、-、*、/以及==、>、<这些运算符之间。而这些运算符只能操作基本类型值,所以在进行这些运算前的第一步就是将两边的值用ToPrimitive转换成基本类型,再进行操作。
以下是基本类型的值在不同操作符的情况下隐式转换的规则 (对于对象,其会被ToPrimitive转换成基本类型,所以最终还是要应用基本类型转换规则):
+操作符 +操作符的两边有至少一个string类型变量时,两边的变量都会被隐式转换为字符串;其他情况下两边的变量都会被转换为数字。1 + '23' // '123'
1 + false // 1
1 + Symbol() // Uncaught TypeError: Cannot convert a Symbol value to a number
'1' + false // '1false'
false + true // 1-、*、\操作符NaN也是一个数字
1 * '23' // 23
1 * false // 0
1 / 'aa' // NaN==操作符操作符两边的值都尽量转成number:
3 == true // false, 3 转为number为3,true转为number为1
'0' == false //true, '0'转为number为0,false转为number为0
'0' == 0 // '0'转为number为0<和>比较符如果两边都是字符串,则比较字母表顺序:
'ca' < 'bd' // false
'a' < 'b' // true其他情况下,转换为数字再比较:
'12' < 13 // true
false > -1 // true以上说的是基本类型的隐式转换,而对象会被ToPrimitive转换为基本类型再进行转换:
var a = {}
a > 2 // false其对比过程如下:
a.valueOf() // {}, 上面提到过,ToPrimitive默认type为number,所以先valueOf,结果还是个对象,下一步
a.toString() // "[object Object]",现在是一个字符串了
Number(a.toString()) // NaN,根据上面 < 和 > 操作符的规则,要转换成数字
NaN > 2 //false,得出比较结果又比如:
var a = {name:'Jack'}
var b = {age: 18}
a + b // "[object Object][object Object]"运算过程如下:
a.valueOf() // {},上面提到过,ToPrimitive默认type为number,所以先valueOf,结果还是个对象,下一步
a.toString() // "[object Object]"
b.valueOf() // 同理
b.toString() // "[object Object]"
a + b // "[object Object][object Object]"HTTPS的通信过程如下:
DNS服务器解析域名的过程:
比如要查询 IP 地址,首先会在浏览器的缓存中查找是否有该域名的缓存,如果不存在就将请求发送到本地的 DNS 服务器中,本地DNS服务器会判断是否存在该域名的缓存,如果不存在,则向根域名服务器发送一个请求,根域名服务器返回负责 .com 的顶级域名服务器的 IP 地址的列表。然后本地 DNS 服务器再向其中一个负责 .com 的顶级域名服务器发送一个请求,负责 .com 的顶级域名服务器返回负责 .baidu 的权威域名服务器的 IP 地址列表。然后本地 DNS 服务器再向其中一个权威域名服务器发送一个请求,最后权威域名服务器返回一个对应的主机名的 IP 地址列表。
HTTP 协议是基于 TCP/IP,并且使用了请求-应答的通信模式,所以性能的关键就在这两点里。
HTTP协议有两种连接模式,一种是持续连接,一种非持续连接。
(1)非持续连接指的是服务器必须为每一个请求的对象建立和维护一个全新的连接。
(2)持续连接下,TCP 连接默认不关闭,可以被多个请求复用。采用持续连接的好处是可以避免每次建立 TCP 连接三次握手时所花费的时间。
对于不同版本的采用不同的连接方式:
HTTP/1.1 采用了长连接的方式,这使得管道(pipeline)网络传输成为了可能。
管道(pipeline)网络传输是指:可以在同一个 TCP 连接里面,客户端可以发起多个请求,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。但是服务器还是按照顺序回应请求。如果前面的回应特别慢,后面就会有许多请求排队等着。这称为队头堵塞。
HTTP 传输的报文必须是一发一收,但是,里面的任务被放在一个任务队列中串行执行,一旦队首的请求处理太慢,就会阻塞后面请求的处理。这就是HTTP队头阻塞问题。
队头阻塞的解决方案: (1)并发连接:对于一个域名允许分配多个长连接,那么相当于增加了任务队列,不至于一个队伍的任务阻塞其它所有任务。
(2)域名分片:将域名分出很多二级域名,它们都指向同样的一台服务器,能够并发的长连接数变多,解决了队头阻塞的问题。
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
setTimeout(() => {
console.log('timer1')
}, 0)
}
async function async2() {
setTimeout(() => {
console.log('timer2')
}, 0)
console.log("async2");
}
async1();
setTimeout(() => {
console.log('timer3')
}, 0)
console.log("start")输出结果如下:
async1 start
async2
start
async1 end
timer2
timer3
timer1代码的执行过程如下:
async1,打印出async1 start;async2,进入async2,遇到定时器timer2,加入宏任务队列,之后打印async2;async2阻塞了后面代码的执行,所以执行后面的定时器timer3,将其加入宏任务队列,之后打印start;async1 end,遇到定时器timer1,将其加入宏任务队列;timer2,timer3,timer1,没有微任务,所以直接所有的宏任务按照先进先出的原则执行。content(元素内容) + padding(内边距) + border(边框) + margin(外边距)
延伸:box-sizing
content-box:默认值,总宽度 = margin + border + padding + widthborder-box:盒子宽度包含 padding 和 border,总宽度 = margin + widthinherit:从父元素继承 box-sizing 属性原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。