图片懒加载
与一般的图片懒加载不同,如下这个多做了 2 个精心解决:
- 图片全副加载实现后移除事件监听;
- 加载完的图片,从 imgList 移除;
let imgList = [...document.querySelectorAll('img')]
let length = imgList.length
// 修改谬误,须要加上自执行
- const imgLazyLoad = function() {+ const imgLazyLoad = (function() {
let count = 0
return function() {let deleteIndexList = []
imgList.forEach((img, index) => {let rect = img.getBoundingClientRect()
if (rect.top < window.innerHeight) {
img.src = img.dataset.src
deleteIndexList.push(index)
count++
if (count === length) {document.removeEventListener('scroll', imgLazyLoad)
}
}
})
imgList = imgList.filter((img, index) => !deleteIndexList.includes(index))
}
- }
+ })()
// 这里最好加上防抖解决
document.addEventListener('scroll', imgLazyLoad)
vue-router
mode
hash
history
跳转
this.$router.push()
<router-link to=""></router-link>
占位
<router-view></router-view>
vue-router 源码实现
- 作为一个插件存在: 实现
VueRouter
类和install
办法 - 实现两个全局组件:
router-view
用于显示匹配组件内容,router-link
用于跳转 - 监控
url
变动: 监听hashchange
或popstate
事件 - 响应最新
url
: 创立一个响应式的属性current
,当它扭转时获取对应组件并显示
// 咱们的插件:// 1. 实现一个 Router 类并挂载期实例
// 2. 实现两个全局组件 router-link 和 router-view
let Vue;
class VueRouter {
// 外围工作:// 1. 监听 url 变动
constructor(options) {
this.$options = options;
// 缓存 path 和 route 映射关系
// 这样找组件更快
this.routeMap = {}
this.$options.routes.forEach(route => {this.routeMap[route.path] = route
})
// 数据响应式
// 定义一个响应式的 current,则如果他变了,那么应用它的组件会 rerender
Vue.util.defineReactive(this, 'current', '')
// 请确保 onHashChange 中 this 指向以后实例
window.addEventListener('hashchange', this.onHashChange.bind(this))
window.addEventListener('load', this.onHashChange.bind(this))
}
onHashChange() {// console.log(window.location.hash);
this.current = window.location.hash.slice(1) || '/'
}
}
// 插件须要实现 install 办法
// 接管一个参数,Vue 构造函数,次要用于数据响应式
VueRouter.install = function (_Vue) {
// 保留 Vue 构造函数在 VueRouter 中应用
Vue = _Vue
// 工作 1:应用混入来做 router 挂载这件事件
Vue.mixin({beforeCreate() {
// 只有根实例才有 router 选项
if (this.$options.router) {Vue.prototype.$router = this.$options.router}
}
})
// 工作 2:实现两个全局组件
// router-link: 生成一个 a 标签,在 url 前面添 // <router-link to="/about">aaa</router-link>
Vue.component('router-link', {
props: {
to: {
type: String,
required: true
},
},
render(h) {// h(tag, props, children)
return h('a',
{attrs: { href: '#' + this.to} },
this.$slots.default
)
// 应用 jsx
// return <a href={'#'+this.to}>{this.$slots.default}</a>
}
})
Vue.component('router-view', {render(h) {
// 依据 current 获取组件并 render
// current 怎么获取?
// console.log('render',this.$router.current);
// 获取要渲染的组件
let component = null
const {routeMap, current} = this.$router
if (routeMap[current]) {component = routeMap[current].component
}
return h(component)
}
})
}
export default VueRouter
为什么 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
并发与并行的区别?
- 并发是宏观概念,我别离有工作 A 和工作 B,在一段时间内通过工作间的切换实现了这两个工作,这种状况就能够称之为并发。
- 并行是宏观概念,假如 CPU 中存在两个外围,那么我就能够同时实现工作 A、B。同时实现多个工作的状况就能够称之为并行。
过程与线程的概念
从实质上说,过程和线程都是 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 运行环境),这就意味着浏览器会耗费更多的内存资源。
- 更简单的体系架构:浏览器各模块之间耦合性高、扩展性差等问题,会导致当初的架构曾经很难适应新的需要了。
JavaScript 类数组对象的定义?
一个领有 length 属性和若干索引属性的对象就能够被称为类数组对象,类数组对象和数组相似,然而不能调用数组的办法。常见的类数组对象有 arguments 和 DOM 办法的返回后果,还有一个函数也能够被看作是类数组对象,因为它含有 length 属性值,代表可接管的参数个数。
常见的类数组转换为数组的办法有这样几种:
(1)通过 call 调用数组的 slice 办法来实现转换
Array.prototype.slice.call(arrayLike);
(2)通过 call 调用数组的 splice 办法来实现转换
Array.prototype.splice.call(arrayLike, 0);
(3)通过 apply 调用数组的 concat 办法来实现转换
Array.prototype.concat.apply([], arrayLike);
(4)通过 Array.from 办法来实现转换
Array.from(arrayLike);
代码输入后果
async function async1() {console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {console.log("async2");
}
console.log("script start");
setTimeout(function() {console.log("setTimeout");
}, 0);
async1();
new Promise(resolve => {console.log("promise1");
resolve();}).then(function() {console.log("promise2");
});
console.log('script end')
输入后果如下:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
代码执行过程如下:
- 结尾定义了 async1 和 async2 两个函数,然而并未执行,执行 script 中的代码,所以打印出 script start;
- 遇到定时器 Settimeout,它是一个宏工作,将其退出到宏工作队列;
- 之后执行函数 async1,首先打印出 async1 start;
- 遇到 await,执行 async2,打印出 async2,并阻断前面代码的执行,将前面的代码退出到微工作队列;
- 而后跳出 async1 和 async2,遇到 Promise,打印出 promise1;
- 遇到 resolve,将其退出到微工作队列,而后执行前面的 script 代码,打印出 script end;
- 之后就该执行微工作队列了,首先打印出 async1 end,而后打印出 promise2;
- 执行完微工作队列,就开始执行宏工作队列中的定时器,打印出 setTimeout。
代码输入后果
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
; - 而后执行 async2 前面的代码,打印出
async1 end
,遇到定时器 timer1,将其退出宏工作队列; - 最初,宏工作队列有三个工作,先后顺序为
timer2
,timer3
,timer1
,没有微工作,所以间接所有的宏工作依照先进先出的准则执行。
label 的作用是什么?如何应用?
label 标签来定义表单控件的关系:当用户抉择 label 标签时,浏览器会主动将焦点转到和 label 标签相干的表单控件上。
- 应用办法 1:
<label for="mobile">Number:</label>
<input type="text" id="mobile"/>
- 应用办法 2:
<label>Date:<input type="text"/></label>
参考前端进阶面试题具体解答
事件委托的应用场景
场景:给页面的所有的 a 标签增加 click 事件,代码如下:
document.addEventListener("click", function(e) {if (e.target.nodeName == "A")
console.log("a");
}, false);
然而这些 a 标签可能蕴含一些像 span、img 等元素,如果点击到了这些 a 标签中的元素,就不会触发 click 事件,因为事件绑定上在 a 标签元素上,而触发这些外部的元素时,e.target 指向的是触发 click 事件的元素(span、img 等其余元素)。
这种状况下就能够应用事件委托来解决,将事件绑定在 a 标签的外部元素上,当点击它的时候,就会逐级向上查找,晓得找到 a 标签为止,代码如下:
document.addEventListener("click", function(e) {
var node = e.target;
while (node.parentNode.nodeName != "BODY") {if (node.nodeName == "A") {console.log("a");
break;
}
node = node.parentNode;
}
}, false);
代码输入后果
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。
点击刷新按钮或者按 F5、按 Ctrl+F5(强制刷新)、地址栏回车有什么区别?
- 点击刷新按钮或者按 F5: 浏览器间接对本地的缓存文件过期,然而会带上 If-Modifed-Since,If-None-Match,这就意味着服务器会对文件查看新鲜度,返回后果可能是 304,也有可能是 200。
- 用户按 Ctrl+F5(强制刷新): 浏览器不仅会对本地文件过期,而且不会带上 If-Modifed-Since,If-None-Match,相当于之前素来没有申请过,返回后果是 200。
- 地址栏回车:浏览器发动申请,依照失常流程,本地查看是否过期,而后服务器查看新鲜度,最初返回内容。
什么是文档的预解析?
Webkit 和 Firefox 都做了这个优化,当执行 JavaScript 脚本时,另一个线程解析剩下的文档,并加载前面须要通过网络加载的资源。这种形式能够使资源并行加载从而使整体速度更快。须要留神的是,预解析并不扭转 DOM 树,它将这个工作留给主解析过程,本人只解析内部资源的援用,比方内部脚本、样式表及图片。
ES6 中模板语法与字符串解决
ES6 提出了“模板语法”的概念。在 ES6 以前,拼接字符串是很麻烦的事件:
var name = 'css'
var career = 'coder'
var hobby = ['coding', 'writing']
var finalString = 'my name is' + name + ', I work as a' + career + ', I love' + hobby[0] + 'and' + hobby[1]
仅仅几个变量,写了这么多加号,还要时刻小心外面的空格和标点符号有没有跟错中央。然而有了模板字符串,拼接难度直线降落:
var name = 'css'
var career = 'coder'
var hobby = ['coding', 'writing']
var finalString = `my name is ${name}, I work as a ${career} I love ${hobby[0]} and ${hobby[1]}`
字符串不仅更容易拼了,也更易读了,代码整体的品质都变高了。这就是模板字符串的第一个劣势——容许用 ${}的形式嵌入变量。但这还不是问题的要害,模板字符串的要害劣势有两个:
- 在模板字符串中,空格、缩进、换行都会被保留
- 模板字符串齐全反对“运算”式的表达式,能够在 ${}里实现一些计算
基于第一点,能够在模板字符串里无障碍地间接写 html 代码:
let list = ` <ul> <li> 列表项 1 </li> <li> 列表项 2 </li> </ul>`;
console.log(message); // 正确输入,不存在报错
基于第二点,能够把一些简略的计算和调用丢进 ${} 来做:
function add(a, b) {const finalString = `${a} + ${b} = ${a+b}`
console.log(finalString)
}
add(1, 2) // 输入 '1 + 2 = 3'
除了模板语法外,ES6 中还新增了一系列的字符串办法用于晋升开发效率:
(1)存在性断定:在过来,当判断一个字符 / 字符串是否在某字符串中时,只能用 indexOf > -1 来做。当初 ES6 提供了三个办法:includes、startsWith、endsWith,它们都会返回一个布尔值来通知你是否存在。
- includes:判断字符串与子串的蕴含关系:
const son = 'haha'
const father = 'xixi haha hehe'
father.includes(son) // true
- startsWith:判断字符串是否以某个 / 某串字符结尾:
const father = 'xixi haha hehe'
father.startsWith('haha') // false
father.startsWith('xixi') // true
- endsWith:判断字符串是否以某个 / 某串字符结尾:
const father = 'xixi haha hehe'
father.endsWith('hehe') // true
(2)主动反复:能够应用 repeat 办法来使同一个字符串输入屡次(被间断复制屡次):
const sourceCode = 'repeat for 3 times;'
const repeated = sourceCode.repeat(3)
console.log(repeated) // repeat for 3 times;repeat for 3 times;repeat for 3 times;
JS 数据类型
根本类型:Number、Boolean、String、null、undefined、symbol(ES6 新增的),BigInt(ES2020)
援用类型:Object,对象子类型(Array,Function)
原型批改、重写
function Person(name) {this.name = name}
// 批改原型
Person.prototype.getName = function() {}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
// 重写原型
Person.prototype = {getName: function() {}}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // false
能够看到批改原型的时候 p 的构造函数不是指向 Person 了,因为间接给 Person 的原型对象间接用对象赋值时,它的构造函数指向的了根构造函数 Object,所以这时候p.constructor === Object
,而不是p.constructor === Person
。要想成立,就要用 constructor 指回来:
Person.prototype = {getName: function() {}}
var p = new Person('hello')
p.constructor = Person
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
对 CSS 工程化的了解
CSS 工程化是为了解决以下问题:
- 宏观设计:CSS 代码如何组织、如何拆分、模块构造怎么设计?
- 编码优化:怎么写出更好的 CSS?
- 构建:如何解决我的 CSS,能力让它的打包后果最优?
- 可维护性:代码写完了,如何最小化它后续的变更老本?如何确保任何一个共事都能轻松接手?
以下三个方向都是时下比拟风行的、普适性十分好的 CSS 工程化实际:
- 预处理器:Less、Sass 等;
- 重要的工程化插件:PostCss;
- Webpack loader 等。
基于这三个方向,能够衍生出一些具备典型意义的子问题,这里咱们一一来看:
(1)预处理器:为什么要用预处理器?它的呈现是为了解决什么问题?
预处理器,其实就是 CSS 世界的“轮子”。预处理器反对咱们写一种相似 CSS、但理论并不是 CSS 的语言,而后把它编译成 CSS 代码:那为什么写 CSS 代码写得好好的,偏偏要转去写“类 CSS”呢?这就和原本用 JS 也能够实现所有性能,但最初却写 React 的 jsx 或者 Vue 的模板语法一样——为了爽!要想晓得有了预处理器有多爽,首先要晓得的是传统 CSS 有多不爽。随着前端业务复杂度的进步,前端工程中对 CSS 提出了以下的诉求:
- 宏观设计上:咱们心愿能优化 CSS 文件的目录构造,对现有的 CSS 文件实现复用;
- 编码优化上:咱们心愿能写出构造清晰、扼要易懂的 CSS,须要它具备高深莫测的嵌套层级关系,而不是无差别的一铺到底写法;咱们心愿它具备变量特色、计算能力、循环能力等等更强的可编程性,这样咱们能够少写一些无用的代码;
- 可维护性上:更强的可编程性意味着更优质的代码构造,实现复用意味着更简略的目录构造和更强的拓展能力,这两点如果能做到,天然会带来更强的可维护性。
这三点是传统 CSS 所做不到的,也正是预处理器所解决掉的问题。预处理器广泛会具备这样的个性:
- 嵌套代码的能力,通过嵌套来反映不同 css 属性之间的层级关系;
- 反对定义 css 变量;
- 提供计算函数;
- 容许对代码片段进行 extend 和 mixin;
- 反对循环语句的应用;
- 反对将 CSS 文件模块化,实现复用。
(2)PostCss:PostCss 是如何工作的?咱们在什么场景下会应用 PostCss?
它和预处理器的不同就在于,预处理器解决的是 类 CSS,而 PostCss 解决的就是 CSS 自身。Babel 能够将高版本的 JS 代码转换为低版本的 JS 代码。PostCss 做的是相似的事件:它能够编译尚未被浏览器广泛支持的先进的 CSS 语法,还能够主动为一些须要额定兼容的语法减少前缀。更强的是,因为 PostCss 有着弱小的插件机制,反对各种各样的扩大,极大地强化了 CSS 的能力。
PostCss 在业务中的应用场景十分多:
- 进步 CSS 代码的可读性:PostCss 其实能够做相似预处理器能做的工作;
- 当咱们的 CSS 代码须要适配低版本浏览器时,PostCss 的 Autoprefixer 插件能够帮忙咱们主动减少浏览器前缀;
- 容许咱们编写面向未来的 CSS:PostCss 可能帮忙咱们编译 CSS next 代码;
(3)Webpack 能解决 CSS 吗?如何实现? Webpack 能解决 CSS 吗:
- Webpack 在裸奔的状态下,是不能解决 CSS 的,Webpack 自身是一个面向 JavaScript 且只能解决 JavaScript 代码的模块化打包工具;
- Webpack 在 loader 的辅助下,是能够解决 CSS 的。
如何用 Webpack 实现对 CSS 的解决:
- Webpack 中操作 CSS 须要应用的两个要害的 loader:css-loader 和 style-loader
-
留神,答出“用什么”有时候可能还不够,面试官会狐疑你是不是在背答案,所以你还须要理解每个 loader 都做了什么事件:
- css-loader:导入 CSS 模块,对 CSS 代码进行编译解决;
- style-loader:创立 style 标签,把 CSS 内容写入标签。
在理论应用中,css-loader 的执行程序肯定要安顿在 style-loader 的后面。因为只有实现了编译过程,才能够对 css 代码进行插入;若提前插入了未编译的代码,那么 webpack 是无奈了解这坨货色的,它会无情报错。
transition 和 animation 的区别
- transition 是适度属性,强调适度,它的实现须要触发一个事件(比方鼠标挪动下来,焦点,点击等)才执行动画。它相似于 flash 的补间动画,设置一个开始关键帧,一个完结关键帧。
- animation 是动画属性,它的实现不须要触发事件,设定好工夫之后能够本人执行,且能够循环一个动画。它也相似于 flash 的补间动画,然而它能够设置多个关键帧(用 @keyframe 定义)实现动画。
代码输入后果
function a() {console.log(this);
}
a.call(null);
打印后果:window 对象
依据 ECMAScript262 标准规定:如果第一个参数传入的对象调用者是 null 或者 undefined,call 办法将把全局对象(浏览器上是 window 对象)作为 this 的值。所以,不论传入 null 还是 undefined,其 this 都是全局对象 window。所以,在浏览器上答案是输入 window 对象。
要留神的是,在严格模式中,null 就是 null,undefined 就是 undefined:
'use strict';
function a() {console.log(this);
}
a.call(null); // null
a.call(undefined); // undefined
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