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')
—- 问题知识点分割线 —-
说一说前端性能优化计划
三个方面来阐明前端性能优化
一:webapck 优化与开启 gzip 压缩
1.babel-loader 用 include 或 exclude 来帮咱们防止不必要的转译,不转译 node_moudules 中的 js 文件
其次在缓存以后转译的 js 文件,设置 loader: 'babel-loader?cacheDirectory=true' 2. 文件采纳按需加载等等 3. 具体的做法非常简单,只须要你在你的 request headers 中加上这么一句:accept-encoding:gzip 4. 图片优化,采纳 svg 图片或者字体图标 5. 浏览器缓存机制,它又分为强缓存和协商缓存二:本地存储——从 Cookie 到 Web Storage、IndexedDB 阐明一下 SessionStorage 和 localStorage 还有 cookie 的区别和优缺点三:代码优化 1. 事件代理 2. 事件的节流和防抖 3. 页面的回流和重绘 4.EventLoop 事件循环机制 5. 代码优化等等
—- 问题知识点分割线 —-
插入排序 – 工夫复杂度 n^2
题目形容: 实现一个插入排序
实现代码如下:
function insertSort(arr) {for (let i = 1; i < arr.length; i++) {
let j = i;
let target = arr[j];
while (j > 0 && arr[j - 1] > target) {arr[j] = arr[j - 1];
j--;
}
arr[j] = target;
}
return arr;
}
// console.log(insertSort([3, 6, 2, 4, 1]));
—- 问题知识点分割线 —-
如何进攻 XSS 攻打?
能够看到 XSS 危害如此之大,那么在开发网站时就要做好进攻措施,具体措施如下:
- 能够从浏览器的执行来进行预防,一种是应用纯前端的形式,不必服务器端拼接后返回(不应用服务端渲染)。另一种是对须要插入到 HTML 中的代码做好充沛的本义。对于 DOM 型的攻打,次要是前端脚本的不牢靠而造成的,对于数据获取渲染和字符串拼接的时候应该对可能呈现的恶意代码状况进行判断。
- 应用 CSP,CSP 的实质是建设一个白名单,通知浏览器哪些内部资源能够加载和执行,从而避免恶意代码的注入攻打。
- CSP 指的是内容安全策略,它的实质是建设一个白名单,通知浏览器哪些内部资源能够加载和执行。咱们只须要配置规定,如何拦挡由浏览器本人来实现。
- 通常有两种形式来开启 CSP,一种是设置 HTTP 首部中的 Content-Security-Policy,一种是设置 meta 标签的形式
- 对一些敏感信息进行爱护,比方 cookie 应用 http-only,使得脚本无奈获取。也能够应用验证码,防止脚本伪装成用户执行一些操作。
—- 问题知识点分割线 —-
大数相加
题目形容: 实现一个 add 办法实现两个大数相加
let a = "9007199254740991";
let b = "1234567899999999999";
function add(a ,b){//...}
实现代码如下:
function add(a ,b){
// 取两个数字的最大长度
let maxLength = Math.max(a.length, b.length);
// 用 0 去补齐长度
a = a.padStart(maxLength , 0);//"0009007199254740991"
b = b.padStart(maxLength , 0);//"1234567899999999999"
// 定义加法过程中须要用到的变量
let t = 0;
let f = 0; //"进位"
let sum = "";
for(let i=maxLength-1 ; i>=0 ; i--){t = parseInt(a[i]) + parseInt(b[i]) + f;
f = Math.floor(t/10);
sum = t%10 + sum;
}
if(f!==0){sum = '' + f + sum;}
return sum;
}
—- 问题知识点分割线 —-
margin 和 padding 的应用场景
- 须要在 border 外侧增加空白,且空白处不须要背景(色)时,应用 margin;
- 须要在 border 内测增加空白,且空白处须要背景(色)时,应用 padding。
—- 问题知识点分割线 —-
JavaScript 为什么要进行变量晋升,它导致了什么问题?
变量晋升的体现是,无论在函数中何处地位申明的变量,如同都被晋升到了函数的首部,能够在变量申明前拜访到而不会报错。
造成变量申明晋升的 实质起因 是 js 引擎在代码执行前有一个解析的过程,创立了执行上下文,初始化了一些代码执行时须要用到的对象。当拜访一个变量时,会到以后执行上下文中的作用域链中去查找,而作用域链的首端指向的是以后执行上下文的变量对象,这个变量对象是执行上下文的一个属性,它蕴含了函数的形参、所有的函数和变量申明,这个对象的是在代码解析的时候创立的。
首先要晓得,JS 在拿到一个变量或者一个函数的时候,会有两步操作,即解析和执行。
-
在解析阶段,JS 会查看语法,并对函数进行预编译。解析的时候会先创立一个全局执行上下文环境,先把代码中行将执行的变量、函数申明都拿进去,变量先赋值为 undefined,函数先申明好可应用。在一个函数执行之前,也会创立一个函数执行上下文环境,跟全局执行上下文相似,不过函数执行上下文会多出 this、arguments 和函数的参数。
- 全局上下文:变量定义,函数申明
- 函数上下文:变量定义,函数申明,this,arguments
- 在执行阶段,就是依照代码的程序顺次执行。
那为什么会进行变量晋升呢?次要有以下两个起因:
- 进步性能
- 容错性更好
(1)进步性能 在 JS 代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了进步性能,如果没有这一步,那么每次执行代码前都必须从新解析一遍该变量(函数),而这是没有必要的,因为变量(函数)的代码并不会扭转,解析一遍就够了。
在解析的过程中,还会为函数生成预编译代码。在预编译时,会统计申明了哪些变量、创立了哪些函数,并对函数的代码进行压缩,去除正文、不必要的空白等。这样做的益处就是每次执行函数时都能够间接为该函数调配栈空间(不须要再解析一遍去获取代码中申明了哪些变量,创立了哪些函数),并且因为代码压缩的起因,代码执行也更快了。
(2)容错性更好
变量晋升能够在肯定水平上进步 JS 的容错性,看上面的代码:
a = 1;var a;console.log(a);
如果没有变量晋升,这两行代码就会报错,然而因为有了变量晋升,这段代码就能够失常执行。
尽管,在能够开发过程中,能够完全避免这样写,然而有时代码很简单的时候。可能因为忽略而先应用后定义了,这样也不会影响失常应用。因为变量晋升的存在,而会失常运行。
总结:
- 解析和预编译过程中的申明晋升能够进步性能,让函数能够在执行时事后为变量调配栈空间
- 申明晋升还能够进步 JS 代码的容错性,使一些不标准的代码也能够失常执行
变量晋升尽管有一些长处,然而他也会造成肯定的问题,在 ES6 中提出了 let、const 来定义变量,它们就没有变量晋升的机制。上面看一下变量晋升可能会导致的问题:
var tmp = new Date();
function fn(){console.log(tmp);
if(false){var tmp = 'hello world';}
}
fn(); // undefined
在这个函数中,本来是要打印出外层的 tmp 变量,然而因为变量晋升的问题,内层定义的 tmp 被提到函数外部的最顶部,相当于笼罩了外层的 tmp,所以打印后果为 undefined。
var tmp = 'hello world';
for (var i = 0; i < tmp.length; i++) {console.log(tmp[i]);
}
console.log(i); // 11
因为遍历时定义的 i 会变量晋升成为一个全局变量,在函数完结之后不会被销毁,所以打印进去 11。
—- 问题知识点分割线 —-
如何取得对象非原型链上的属性?
应用后 hasOwnProperty()
办法来判断属性是否属于原型链的属性:
function iterate(obj){var res=[];
for(var key in obj){if(obj.hasOwnProperty(key))
res.push(key+':'+obj[key]);
}
return res;
}
—- 问题知识点分割线 —-
|| 和 && 操作符的返回值?
|| 和 && 首先会对第一个操作数执行条件判断,如果其不是布尔值就先强制转换为布尔类型,而后再执行条件判断。
- 对于 || 来说,如果条件判断后果为 true 就返回第一个操作数的值,如果为 false 就返回第二个操作数的值。
- && 则相同,如果条件判断后果为 true 就返回第二个操作数的值,如果为 false 就返回第一个操作数的值。
|| 和 && 返回它们其中一个操作数的值,而非条件判断的后果
—- 问题知识点分割线 —-
TLS/SSL 的工作原理
TLS/SSL全称 平安传输层协定(Transport Layer Security), 是介于 TCP 和 HTTP 之间的一层平安协定,不影响原有的 TCP 协定和 HTTP 协定,所以应用 HTTPS 基本上不须要对 HTTP 页面进行太多的革新。
TLS/SSL 的性能实现次要依赖三类根本算法:散列函数 hash、对称加密 、 非对称加密。这三类算法的作用如下:
- 基于散列函数验证信息的完整性
- 对称加密算法采纳协商的秘钥对数据加密
- 非对称加密实现身份认证和秘钥协商
(1)散列函数 hash
常见的散列函数有 MD5、SHA1、SHA256。该函数的特点是单向不可逆,对输出数据十分敏感,输入的长度固定,任何数据的批改都会扭转散列函数的后果,能够用于避免信息篡改并验证数据的完整性。
特点: 在信息传输过程中,散列函数不能三都实现信息防篡改,因为传输是明文传输,中间人能够批改信息后从新计算信息的摘要,所以须要对传输的信息和信息摘要进行加密。
(2)对称加密
对称加密的办法是,单方应用同一个秘钥对数据进行加密和解密。然而对称加密的存在一个问题,就是如何保障秘钥传输的安全性,因为秘钥还是会通过网络传输的,一旦秘钥被其他人获取到,那么整个加密过程就毫无作用了。这就要用到非对称加密的办法。
常见的对称加密算法有 AES-CBC、DES、3DES、AES-GCM 等。雷同的秘钥能够用于信息的加密和解密。把握秘钥能力获取信息,避免信息窃听,其通信形式是一对一。
特点: 对称加密的劣势就是信息传输应用一对一,须要共享雷同的明码,明码的平安是保障信息安全的根底,服务器和 N 个客户端通信,须要维持 N 个明码记录且不能批改明码。
(3)非对称加密
非对称加密的办法是,咱们领有两个秘钥,一个是公钥,一个是私钥。公钥是公开的,私钥是窃密的。用私钥加密的数据,只有对应的公钥能力解密,用公钥加密的数据,只有对应的私钥能力解密。咱们能够将公钥颁布进来,任何想和咱们通信的客户,都能够应用咱们提供的公钥对数据进行加密,这样咱们就能够应用私钥进行解密,这样就能保证数据的平安了。然而非对称加密有一个毛病就是加密的过程很慢,因而如果每次通信都应用非对称加密的形式的话,反而会造成等待时间过长的问题。
常见的非对称加密算法有 RSA、ECC、DH 等。秘钥成对呈现,个别称为公钥(公开)和私钥(窃密)。公钥加密的信息只有私钥能够解开,私钥加密的信息只能公钥解开,因而把握公钥的不同客户端之间不能互相解密信息,只能和服务器进行加密通信,服务器能够实现一对多的的通信,客户端也能够用来验证把握私钥的服务器的身份。
特点: 非对称加密的特点就是信息一对多,服务器只须要维持一个私钥就能够和多个客户端进行通信,但服务器收回的信息可能被所有的客户端解密,且该算法的计算简单,加密的速度慢。
综合上述算法特点,TLS/SSL 的工作形式就是客户端应用非对称加密与服务器进行通信,实现身份的验证并协商对称加密应用的秘钥。对称加密算法采纳协商秘钥对信息以及信息摘要进行加密通信,不同节点之间采纳的对称秘钥不同,从而保障信息只能通信单方获取。这样就解决了两个办法各自存在的问题。
—- 问题知识点分割线 —-
说一说 SessionStorage 和 localStorage 还有 cookie
共同点:都是保留在浏览器端、且同源的
不同点:1.cookie 数据始终在同源的 http 申请中携带(即便不须要),即 cookie 在浏览器和服务器间来回传递。cookie 数据还有门路(path)的概念,能够限度 cookie 只属于某个门路下
sessionStorage 和 localStorage 不会主动把数据发送给服务器,仅在本地保留。2. 存储大小限度也不同,cookie 数据不能超过 4K,sessionStorage 和 localStorage 能够达到 5M
3.sessionStorage:仅在以后浏览器窗口敞开之前无效;localStorage:始终无效,窗口或浏览器敞开也始终保留,本地存储,因而用作持久数据;cookie:只在设置的 cookie 过期工夫之前无效,即便窗口敞开或浏览器敞开
4. 作用域不同
sessionStorage:不在不同的浏览器窗口中共享,即便是同一个页面;localstorage:在所有同源窗口中都是共享的;也就是说只有浏览器不敞开,数据依然存在
cookie: 也是在所有同源窗口中都是共享的. 也就是说只有浏览器不敞开,数据依然存在
—- 问题知识点分割线 —-
Node 中的 Event Loop 和浏览器中的有什么区别?process.nextTick 执行程序?
Node 中的 Event Loop 和浏览器中的是齐全不雷同的货色。
Node 的 Event Loop 分为 6 个阶段,它们会依照 程序 重复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量达到零碎设定的阈值,就会进入下一阶段。
(1)Timers(计时器阶段):首次进入事件循环,会从计时器阶段开始。此阶段会判断是否存在过期的计时器回调(蕴含 setTimeout 和 setInterval),如果存在则会执行所有过期的计时器回调,执行结束后,如果回调中触发了相应的微工作,会接着执行所有微工作,执行完微工作后再进入 Pending callbacks 阶段。
(2)Pending callbacks:执行推延到下一个循环迭代的 I / O 回调(零碎调用相干的回调)。
(3)Idle/Prepare:仅供外部应用。
(4)Poll(轮询阶段):
- 当回调队列不为空时:会执行回调,若回调中触发了相应的微工作,这里的微工作执行机会和其余中央有所不同,不会等到所有回调执行结束后才执行,而是针对每一个回调执行结束后,就执行相应微工作。执行完所有的回调后,变为上面的状况。
- 当回调队列为空时(没有回调或所有回调执行结束):但如果存在有计时器(setTimeout、setInterval 和 setImmediate)没有执行,会完结轮询阶段,进入 Check 阶段。否则会阻塞并期待任何正在执行的 I / O 操作实现,并马上执行相应的回调,直到所有回调执行结束。
(5)Check(查问阶段):会查看是否存在 setImmediate 相干的回调,如果存在则执行所有回调,执行结束后,如果回调中触发了相应的微工作,会接着执行所有微工作,执行完微工作后再进入 Close callbacks 阶段。
(6)Close callbacks:执行一些敞开回调,比方 socket.on(‘close’, …)等。
上面来看一个例子,首先在有些状况下,定时器的执行程序其实是 随机 的
setTimeout(() => { console.log('setTimeout')}, 0)setImmediate(() => { console.log('setImmediate')})
对于以上代码来说,setTimeout
可能执行在前,也可能执行在后
- 首先
setTimeout(fn, 0) === setTimeout(fn, 1)
,这是由源码决定的 - 进入事件循环也是须要老本的,如果在筹备时候破费了大于 1ms 的工夫,那么在 timer 阶段就会间接执行
setTimeout
回调 - 那么如果筹备工夫破费小于 1ms,那么就是
setImmediate
回调先执行了
当然在某些状况下,他们的执行程序肯定是固定的,比方以下代码:
const fs = require('fs')
fs.readFile(__filename, () => {setTimeout(() => {console.log('timeout');
}, 0)
setImmediate(() => {console.log('immediate')
})
})
在上述代码中,setImmediate
永远 先执行。因为两个代码写在 IO 回调中,IO 回调是在 poll 阶段执行,当回调执行结束后队列为空,发现存在 setImmediate
回调,所以就间接跳转到 check 阶段去执行回调了。
下面都是 macrotask 的执行状况,对于 microtask 来说,它会在以上每个阶段实现前 清空 microtask 队列,
setTimeout(() => {console.log('timer21')
}, 0)
Promise.resolve().then(function() {console.log('promise1')
})
对于以上代码来说,其实和浏览器中的输入是一样的,microtask 永远执行在 macrotask 后面。
最初来看 Node 中的 process.nextTick
,这个函数其实是独立于 Event Loop 之外的,它有一个本人的队列,当每个阶段实现后,如果存在 nextTick 队列,就会 清空队列中的所有回调函数,并且优先于其余 microtask 执行。
setTimeout(() => {console.log('timer1')
Promise.resolve().then(function() {console.log('promise1')
})
}, 0)
process.nextTick(() => {console.log('nextTick')
process.nextTick(() => {console.log('nextTick')
process.nextTick(() => {console.log('nextTick')
process.nextTick(() => {console.log('nextTick')
})
})
})
})
对于以上代码,永远都是先把 nextTick 全副打印进去。
—- 问题知识点分割线 —-
title 与 h1 的区别、b 与 strong 的区别、i 与 em 的区别?
- strong 标签有语义,是起到减轻语气的成果,而 b 标签是没有的,b 标签只是一个简略加粗标签。b 标签之间的字符都设为粗体,strong 标签增强字符的语气都是通过粗体来实现的,而搜索引擎更偏重 strong 标签。
- title 属性没有明确意义只示意是个题目,H1 则示意档次明确的题目,对页面信息的抓取有很大的影响
- i 内容展现为斜体,em 示意强调的文本
—- 问题知识点分割线 —-
同样是重定向,307,303,302的区别?
302 是 http1.0 的协定状态码,在 http1.1 版本的时候为了细化 302 状态码⼜进去了两个 303 和 307。303 明确示意客户端该当采⽤ get ⽅法获取资源,他会把 POST 申请变为 GET 申请进⾏重定向。307 会遵循浏览器规范,不会从 post 变为 get。
—- 问题知识点分割线 —-
过程与线程的概念
从实质上说,过程和线程都是 CPU 工作工夫片的一个形容:
- 过程形容了 CPU 在运行指令及加载和保留上下文所需的工夫,放在利用上来说就代表了一个程序。
- 线程是过程中的更小单位,形容了执行一段指令所需的工夫。
过程是资源分配的最小单位,线程是 CPU 调度的最小单位。
一个过程就是一个程序的运行实例。具体解释就是,启动一个程序的时候,操作系统会为该程序创立一块内存,用来寄存代码、运行中的数据和一个执行工作的主线程,咱们把这样的一个运行环境叫 过程 。 过程是运行在虚拟内存上的,虚拟内存是用来解决用户对硬件资源的有限需要和无限的硬件资源之间的矛盾的。从操作系统角度来看,虚拟内存即交换文件;从处理器角度看,虚拟内存即虚拟地址空间。
如果程序很多时,内存可能会不够,操作系统为每个过程提供一套独立的虚拟地址空间,从而使得同一块物理内存在不同的过程中能够对应到不同或雷同的虚拟地址,变相的减少了程序能够应用的内存。
过程和线程之间的关系有以下四个特点:
(1)过程中的任意一线程执行出错,都会导致整个过程的解体。
(2)线程之间共享过程中的数据。
(3)当一个过程敞开之后,操作系统会回收过程所占用的内存, 当一个过程退出时,操作系统会回收该过程所申请的所有资源;即便其中任意线程因为操作不当导致内存透露,当过程退出时,这些内存也会被正确回收。
(4)过程之间的内容互相隔离。 过程隔离就是为了使操作系统中的过程互不烦扰,每一个过程只能拜访本人占有的数据,也就避免出现过程 A 写入数据到过程 B 的状况。正是因为过程之间的数据是严格隔离的,所以一个过程如果解体了,或者挂起了,是不会影响到其余过程的。如果过程之间须要进行数据的通信,这时候,就须要应用用于过程间通信的机制了。
Chrome 浏览器的架构图:从图中能够看出,最新的 Chrome 浏览器包含:
- 1 个浏览器主过程
- 1 个 GPU 过程
- 1 个网络过程
- 多个渲染过程
- 多个插件过程
这些过程的性能:
- 浏览器过程:次要负责界面显示、用户交互、子过程治理,同时提供存储等性能。
- 渲染过程:外围工作是将 HTML、CSS 和 JavaScript 转换为用户能够与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该过程中,默认状况下,Chrome 会为每个 Tab 标签创立一个渲染过程。出于平安思考,渲染过程都是运行在沙箱模式下。
- GPU 过程:其实,GPU 的应用初衷是为了实现 3D CSS 的成果,只是随后网页、Chrome 的 UI 界面都抉择采纳 GPU 来绘制,这使得 GPU 成为浏览器广泛的需要。最初,Chrome 在其多过程架构上也引入了 GPU 过程。
- 网络过程:次要负责页面的网络资源加载,之前是作为一个模块运行在浏览器过程外面的,直至最近才独立进去,成为一个独自的过程。
- 插件过程:次要是负责插件的运行,因插件易解体,所以须要通过插件过程来隔离,以保障插件过程解体不会对浏览器和页面造成影响。
所以,关上一个网页,起码须要四个过程:1 个网络过程、1 个浏览器过程、1 个 GPU 过程以及 1 个渲染过程。如果关上的页面有运行插件的话,还须要再加上 1 个插件过程。
尽管多过程模型晋升了浏览器的稳定性、流畅性和安全性,但同样不可避免地带来了一些问题:
- 更高的资源占用:因为每个过程都会蕴含公共根底构造的正本(如 JavaScript 运行环境),这就意味着浏览器会耗费更多的内存资源。
- 更简单的体系架构:浏览器各模块之间耦合性高、扩展性差等问题,会导致当初的架构曾经很难适应新的需要了。
—- 问题知识点分割线 —-
如何解决 1px 问题?
1px 问题指的是:在一些 Retina 屏幕
的机型上,挪动端页面的 1px 会变得很粗,呈现出不止 1px 的成果。起因很简略——CSS 中的 1px 并不能和挪动设施上的 1px 划等号。它们之间的比例关系有一个专门的属性来形容:
window.devicePixelRatio = 设施的物理像素 / CSS 像素。
关上 Chrome 浏览器,启动挪动端调试模式,在控制台去输入这个 devicePixelRatio
的值。这里选中 iPhone6/7/8 这系列的机型,输入的后果就是 2:这就意味着设置的 1px CSS 像素,在这个设施上理论会用 2 个物理像素单元来进行渲染,所以理论看到的肯定会比 1px 粗一些。解决 1px 问题的三种思路:
思路一:间接写 0.5px
如果之前 1px 的款式这样写:
border:1px solid #333
能够先在 JS 中拿到 window.devicePixelRatio 的值,而后把这个值通过 JSX 或者模板语法给到 CSS 的 data 里,达到这样的成果(这里用 JSX 语法做示范):
<div id="container" data-device={{window.devicePixelRatio}}></div>
而后就能够在 CSS 中用属性选择器来命中 devicePixelRatio 为某一值的状况,比如说这里尝试命中 devicePixelRatio 为 2 的状况:
#container[data-device="2"] {border:0.5px solid #333}
间接把 1px 改成 1/devicePixelRatio 后的值,这是目前为止最简略的一种办法。这种办法的缺点在于兼容性不行,IOS 零碎须要 8 及以上的版本,安卓零碎则间接不兼容。
思路二:伪元素先放大后放大
这个办法的可行性会更高,兼容性也更好。惟一的毛病是代码会变多。
思路是 先放大、后放大:在指标元素的前面追加一个 ::after 伪元素,让这个元素布局为 absolute 之后、整个伸开展铺在指标元素上,而后把它的宽和高都设置为指标元素的两倍,border 值设为 1px。接着借助 CSS 动画特效中的放缩能力,把整个伪元素放大为原来的 50%。此时,伪元素的宽高刚好能够和原有的指标元素对齐,而 border 也放大为了 1px 的二分之一,间接地实现了 0.5px 的成果。
代码如下:
#container[data-device="2"] {position: relative;}
#container[data-device="2"]::after{
position:absolute;
top: 0;
left: 0;
width: 200%;
height: 200%;
content:"";
transform: scale(0.5);
transform-origin: left top;
box-sizing: border-box;
border: 1px solid #333;
}
}
思路三:viewport 缩放来解决
这个思路就是对 meta 标签里几个要害属性下手:
<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">
这里针对像素比为 2 的页面,把整个页面缩放为了原来的 1 / 2 大小。这样,原本占用 2 个物理像素的 1px 款式,当初占用的就是规范的一个物理像素。依据像素比的不同,这个缩放比例能够被计算为不同的值,用 js 代码实现如下:
const scale = 1 / window.devicePixelRatio;
// 这里 metaEl 指的是 meta 标签对应的 Dom
metaEl.setAttribute('content', `width=device-width,user-scalable=no,initial-scale=${scale},maximum-scale=${scale},minimum-scale=${scale}`);
这样解决了,但这样做的副作用也很大,整个页面被缩放了。这时 1px 曾经被解决成物理像素大小,这样的大小在手机上显示边框很适合。然而,一些本来不须要被放大的内容,比方文字、图片等,也被无差别放大掉了。
—- 问题知识点分割线 —-
渐进加强和优雅降级之间的区别
(1)渐进加强(progressive enhancement):次要是针对低版本的浏览器进行页面重构,保障根本的性能状况下,再针对高级浏览器进行成果、交互等方面的改良和追加性能,以达到更好的用户体验。(2)优雅降级 graceful degradation:一开始就构建残缺的性能,而后再针对低版本的浏览器进行兼容。
两者区别:
- 优雅降级是从简单的现状开始的,并试图缩小用户体验的供应;而渐进加强是从一个十分根底的,可能起作用的版本开始的,并在此基础上一直裁减,以适应将来环境的须要;
- 降级(性能衰竭)意味着往回看,而渐进加强则意味着往前看,同时保障其根基处于平安地带。
“优雅降级”观点认为应该针对那些最高级、最欠缺的浏览器来设计网站。而将那些被认为“过期”或有性能缺失的浏览器下的测试工作安顿在开发周期的最初阶段,并把测试对象限定为支流浏览器(如 IE、Mozilla 等)的前一个版本。在这种设计范例下,旧版的浏览器被认为仅能提供“简陋却不妨 (poor, but passable)”的浏览体验。能够做一些小的调整来适应某个特定的浏览器。但因为它们并非咱们所关注的焦点,因而除了修复较大的谬误之外,其它的差别将被间接疏忽。
“渐进加强”观点则认为应关注于内容自身。内容是建设网站的诱因,有的网站展现它,有的则收集它,有的寻求,有的操作,还有的网站甚至会蕴含以上的种种,但相同点是它们全都波及到内容。这使得“渐进加强”成为一种更为正当的设计范例。这也是它立刻被 Yahoo 所驳回并用以构建其“分级式浏览器反对 (Graded Browser Support)”策略的起因所在。
—- 问题知识点分割线 —-
如何⽤ webpack 来优化前端性能?
⽤ webpack 优化前端性能是指优化 webpack 的输入后果,让打包的最终后果在浏览器运⾏疾速⾼效。
- 压缩代码:删除多余的代码、正文、简化代码的写法等等⽅式。能够利⽤ webpack 的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩 JS ⽂件,利⽤ cssnano(css-loader?minimize)来压缩 css
- 利⽤ CDN 减速: 在构建过程中,将引⽤的动态资源门路批改为 CDN 上对应的门路。能够利⽤ webpack 对于 output 参数和各 loader 的 publicPath 参数来批改资源门路
- Tree Shaking: 将代码中永远不会⾛到的⽚段删除掉。能够通过在启动 webpack 时追加参数 –optimize-minimize 来实现
- Code Splitting: 将代码按路由维度或者组件分块(chunk), 这样做到按需加载, 同时能够充沛利⽤浏览器缓存
- 提取公共第三⽅库: SplitChunksPlugin 插件来进⾏公共模块抽取, 利⽤浏览器缓存能够⻓期缓存这些⽆需频繁变动的公共代码
—- 问题知识点分割线 —-
什么是 margin 重叠问题?如何解决?
问题形容: 两个块级元素的上外边距和下外边距可能会合并(折叠)为一个外边距,其大小会取其中外边距值大的那个,这种行为就是外边距折叠。须要留神的是,浮动的元素和相对定位 这种脱离文档流的元素的外边距不会折叠。重叠只会呈现在 垂直方向。
计算准则: 折叠合并后外边距的计算准则如下:
- 如果两者都是负数,那么就去最大者
- 如果是一正一负,就会正值减去负值的绝对值
- 两个都是负值时,用 0 减去两个中绝对值大的那个
解决办法: 对于折叠的状况,次要有两种:兄弟之间重叠 和父子之间重叠(1)兄弟之间重叠
- 底部元素变为行内盒子:
display: inline-block
- 底部元素设置浮动:
float
- 底部元素的 position 的值为
absolute/fixed
(2)父子之间重叠
- 父元素退出:
overflow: hidden
- 父元素增加通明边框:
border:1px solid transparent
- 子元素变为行内盒子:
display: inline-block
- 子元素退出浮动属性或定位
—- 问题知识点分割线 —-
代码输入后果
Promise.resolve(1)
.then(res => {console.log(res);
return 2;
})
.catch(err => {return 3;})
.then(res => {console.log(res);
});
输入后果如下:
1
2
Promise 是能够链式调用的,因为每次调用 .then
或者 .catch
都会返回一个新的 promise,从而实现了链式调用, 它并不像个别工作的链式调用一样 return this。
下面的输入后果之所以顺次打印出 1 和 2,是因为 resolve(1)
之后走的是第一个 then 办法,并没有进 catch 里,所以第二个 then 中的 res 失去的实际上是第一个 then 的返回值。并且 return 2 会被包装成resolve(2)
,被最初的 then 打印输出 2。