写点什么

前端经典面试题(有答案)

作者:loveX001
  • 2023-02-14
    浙江
  • 本文字数:10641 字

    阅读完需:约 35 分钟

对 this 对象的理解

this 是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际开发中,this 的指向可以通过四种调用模式来判断。


  • 第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象。

  • 第二种是方法调用模式,如果一个函数作为一个对象的方法来调用时,this 指向这个对象。

  • 第三种是构造器调用模式,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。

  • 第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。其中 apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组。call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。


这四种方式,使用构造器调用模式的优先级最高,然后是 apply、call 和 bind 调用模式,然后是方法调用模式,然后是函数调用模式。

说一下 HTTP 3.0

HTTP/3 基于 UDP 协议实现了类似于 TCP 的多路复用数据流、传输可靠性等功能,这套功能被称为 QUIC 协议。


  1. 流量控制、传输可靠性功能:QUIC 在 UDP 的基础上增加了一层来保证数据传输可靠性,它提供了数据包重传、拥塞控制、以及其他一些 TCP 中的特性。

  2. 集成 TLS 加密功能:目前 QUIC 使用 TLS1.3,减少了握手所花费的 RTT 数。

  3. 多路复用:同一物理连接上可以有多个独立的逻辑数据流,实现了数据流的单独传输,解决了 TCP 的队头阻塞问题。

  4. 快速握手:由于基于 UDP,可以实现使用 0 ~ 1 个 RTT 来建立连接。

原型/原型链

__proto__和 prototype 关系__proto__constructor对象独有的。2️⃣prototype属性是函数独有的


在 js 中我们是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个 prototype 属性值,这个属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。当我们使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针被称为对象的原型。一般来说我们是不应该能够获取到这个值的,但是现在浏览器中都实现了 proto 属性来让我们访问这个属性,但是我们最好不要使用这个属性,因为它不是规范中规定的。ES5 中新增了一个 Object.getPrototypeOf() 方法,我们可以通过这个方法来获取对象的原型。


当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是 Object.prototype 所以这就是我们新建的对象为什么能够使用 toString() 等方法的原因。


特点:JavaScript 对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与 之相关的对象也会继承这一改变


  • 原型(prototype): 一个简单的对象,用于实现对象的 属性继承。可以简单的理解成对象的爹。在 FirefoxChrome 中,每个JavaScript对象中都包含一个__proto__(非标准)的属性指向它爹(该对象的原型),可obj.__proto__进行访问。

  • 构造函数: 可以通过new来 新建一个对象 的函数。

  • 实例: 通过构造函数和new创建出来的对象,便是实例。 实例通过__proto__指向原型,通过constructor指向构造函数。


Object为例,我们常用的Object便是一个构造函数,因此我们可以通过它构建实例。


// 实例const instance = new Object()
复制代码


则此时, 实例为instance, 构造函数为Object,我们知道,构造函数拥有一个prototype的属性指向原型,因此原型为:


// 原型const prototype = Object.prototype
复制代码


这里我们可以来看出三者的关系:


  • 实例.__proto__ === 原型

  • 原型.constructor === 构造函数

  • 构造函数.prototype === 原型


// 这条线其实是是基于原型进行获取的,可以理解成一条基于原型的映射线// 例如: // const o = new Object()// o.constructor === Object   --> true// o.__proto__ = null;// o.constructor === Object   --> false实例.constructor === 构造函数
复制代码



原型链


原型链是由原型对象组成,每个对象都有 __proto__ 属性,指向了创建该对象的构造函数的原型,__proto__ 将对象连接起来组成了原型链。是一个用来实现继承和共享属性的有限的对象链


  • 属性查找机制: 当查找对象的属性时,如果实例对象自身不存在该属性,则沿着原型链往上一级查找,找到时则输出,不存在时,则继续沿着原型链往上一级查找,直至最顶级的原型对象Object.prototype,如还是没找到,则输出undefined

  • 属性修改机制: 只会修改实例对象本身的属性,如果不存在,则进行添加该属性,如果需要修改原型的属性时,则可以用: b.prototype.x = 2;但是这样会造成所有继承于该对象的实例的属性发生改变。


js 获取原型的方法


  • p.proto

  • p.constructor.prototype

  • Object.getPrototypeOf(p)


总结



  • 每个函数都有 prototype 属性,除了 Function.prototype.bind(),该属性指向原型。

  • 每个对象都有 __proto__ 属性,指向了创建该对象的构造函数的原型。其实这个属性指向了 [[prototype]],但是 [[prototype]]是内部属性,我们并不能访问到,所以使用 _proto_来访问。

  • 对象可以通过 __proto__ 来寻找不属于该对象的属性,__proto__ 将对象连接起来组成了原型链。

为什么 0.1+0.2 ! == 0.3,如何让其相等

在开发过程中遇到类似这样的问题:


let n1 = 0.1, n2 = 0.2console.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
复制代码

escape、encodeURI、encodeURIComponent 的区别

  • encodeURI 是对整个 URI 进行转义,将 URI 中的非法字符转换为合法字符,所以对于一些在 URI 中有特殊意义的字符不会进行转义。

  • encodeURIComponent 是对 URI 的组成部分进行转义,所以一些特殊字符也会得到转义。

  • escape 和 encodeURI 的作用相同,不过它们对于 unicode 编码为 0xff 之外字符的时候会有区别,escape 是直接在字符的 unicode 编码前加上 %u,而 encodeURI 首先会将字符转换为 UTF-8 的格式,再在每个字节前加上 %。

什么是物理像素,逻辑像素和像素密度,为什么在移动端开发时需要用到 @3x, @2x 这种图片?

以 iPhone XS 为例,当写 CSS 代码时,针对于单位 px,其宽度为 414px & 896px,也就是说当赋予一个 DIV 元素宽度为 414px,这个 DIV 就会填满手机的宽度;


而如果有一把尺子来实际测量这部手机的物理像素,实际为 1242*2688 物理像素;经过计算可知,1242/414=3,也就是说,在单边上,一个逻辑像素=3 个物理像素,就说这个屏幕的像素密度为 3,也就是常说的 3 倍屏。


对于图片来说,为了保证其不失真,1 个图片像素至少要对应一个物理像素,假如原始图片是 500300 像素,那么在 3 倍屏上就要放一个 1500900 像素的图片才能保证 1 个物理像素至少对应一个图片像素,才能不失真。 当然,也可以针对所有屏幕,都只提供最高清图片。虽然低密度屏幕用不到那么多图片像素,而且会因为下载多余的像素造成带宽浪费和下载延迟,但从结果上说能保证图片在所有屏幕上都不会失真。


还可以使用 CSS 媒体查询来判断不同的像素密度,从而选择不同的图片:


my-image { background: (low.png); }@media only screen and (min-device-pixel-ratio: 1.5) {  #my-image { background: (high.png); }}
复制代码


参考 前端进阶面试题详细解答

line-height 的理解及其赋值方式

(1)line-height 的概念:


  • line-height 指一行文本的高度,包含了字间距,实际上是下一行基线到上一行基线距离;

  • 如果一个标签没有定义 height 属性,那么其最终表现的高度由 line-height 决定;

  • 一个容器没有设置高度,那么撑开容器高度的是 line-height,而不是容器内的文本内容;

  • 把 line-height 值设置为 height 一样大小的值可以实现单行文字的垂直居中;

  • line-height 和 height 都能撑开一个高度;


(2)line-height 的赋值方式:


  • 带单位:px 是固定值,而 em 会参考父元素 font-size 值计算自身的行高

  • 纯数字:会把比例传递给后代。例如,父级行高为 1.5,子元素字体为 18px,则子元素行高为 1.5 * 18 = 27px

  • 百分比:将计算后的值传递给后代

display:inline-block 什么时候会显示间隙?

  • 有空格时会有间隙,可以删除空格解决;

  • margin正值时,可以让margin使用负值解决;

  • 使用font-size时,可通过设置font-size:0letter-spacingword-spacing解决;

for...in 和 for...of 的区别

for…of 是 ES6 新增的遍历方式,允许遍历一个含有 iterator 接口的数据结构(数组、对象等)并且返回各项的值,和 ES3 中的 for…in 的区别如下


  • for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名;

  • for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链;

  • 对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值;


总结: for...in 循环主要是为了遍历对象而生,不适用于遍历数组;for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。

call() 和 apply() 的区别?

它们的作用一模一样,区别仅在于传入参数的形式的不同。


  • apply 接受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数。

  • call 传入的参数数量不固定,跟 apply 相同的是,第一个参数也是代表函数体内的 this 指向,从第二个参数开始往后,每个参数被依次传入函数。

Promise 的基本用法

(1)创建 Promise 对象

Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。


Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject


const promise = new Promise(function(resolve, reject) {  // ... some code  if (/* 异步操作成功 */){    resolve(value);  } else {    reject(error);  }});
复制代码


一般情况下都会使用new Promise()来创建 promise 对象,但是也可以使用promise.resolvepromise.reject这两个方法:


  • Promise.resolve


Promise.resolve(value)的返回值也是一个 promise 对象,可以对返回值进行.then 调用,代码如下:


Promise.resolve(11).then(function(value){  console.log(value); // 打印出11});
复制代码


resolve(11)代码中,会让 promise 对象进入确定(resolve状态),并将参数11传递给后面的then所指定的onFulfilled 函数;


创建 promise 对象可以使用new Promise的形式创建对象,也可以使用Promise.resolve(value)的形式创建 promise 对象;


  • Promise.reject


Promise.reject 也是new Promise的快捷形式,也创建一个 promise 对象。代码如下:


Promise.reject(new Error(“我错了,请原谅俺!!”));
复制代码


就是下面的代码 new Promise 的简单形式:


new Promise(function(resolve,reject){   reject(new Error("我错了!"));});
复制代码


下面是使用 resolve 方法和 reject 方法:


function testPromise(ready) {  return new Promise(function(resolve,reject){    if(ready) {      resolve("hello world");    }else {      reject("No thanks");    }  });};// 方法调用testPromise(true).then(function(msg){  console.log(msg);},function(error){  console.log(error);});
复制代码


上面的代码的含义是给testPromise方法传递一个参数,返回一个 promise 对象,如果为true的话,那么调用 promise 对象中的resolve()方法,并且把其中的参数传递给后面的then第一个函数内,因此打印出 “hello world”, 如果为false的话,会调用 promise 对象中的reject()方法,则会进入then的第二个函数内,会打印No thanks

(2)Promise 方法

Promise 有五个常用的方法:then()、catch()、all()、race()、finally。下面就来看一下这些方法。


  1. then()


当 Promise 执行的内容符合成功条件时,调用resolve函数,失败就调用reject函数。Promise 创建完了,那该如何调用呢?


promise.then(function(value) {  // success}, function(error) {  // failure});
复制代码


then方法可以接受两个回调函数作为参数。第一个回调函数是 Promise 对象的状态变为resolved时调用,第二个回调函数是 Promise 对象的状态变为rejected时调用。其中第二个参数可以省略。 then方法返回的是一个新的 Promise 实例(不是原来那个 Promise 实例)。因此可以采用链式写法,即then方法后面再调用另一个 then 方法。


当要写有顺序的异步事件时,需要串行时,可以这样写:


let promise = new Promise((resolve,reject)=>{    ajax('first').success(function(res){        resolve(res);    })})promise.then(res=>{    return new Promise((resovle,reject)=>{        ajax('second').success(function(res){            resolve(res)        })    })}).then(res=>{    return new Promise((resovle,reject)=>{        ajax('second').success(function(res){            resolve(res)        })    })}).then(res=>{
})
复制代码


那当要写的事件没有顺序或者关系时,还如何写呢?可以使用all 方法来解决。


2. catch()


Promise 对象除了有 then 方法,还有一个 catch 方法,该方法相当于then方法的第二个参数,指向reject的回调函数。不过catch方法还有一个作用,就是在执行resolve回调函数时,如果出现错误,抛出异常,不会停止运行,而是进入catch方法中。


p.then((data) => {     console.log('resolved',data);},(err) => {     console.log('rejected',err);     }); p.then((data) => {    console.log('resolved',data);}).catch((err) => {    console.log('rejected',err);});
复制代码


3. all()


all方法可以完成并行任务, 它接收一个数组,数组的每一项都是一个promise对象。当数组中所有的promise的状态都达到resolved的时候,all方法的状态就会变成resolved,如果有一个状态变成了rejected,那么all方法的状态就会变成rejected


javascriptlet promise1 = new Promise((resolve,reject)=>{    setTimeout(()=>{       resolve(1);    },2000)});let promise2 = new Promise((resolve,reject)=>{    setTimeout(()=>{       resolve(2);    },1000)});let promise3 = new Promise((resolve,reject)=>{    setTimeout(()=>{       resolve(3);    },3000)});Promise.all([promise1,promise2,promise3]).then(res=>{    console.log(res);    //结果为:[1,2,3] })
复制代码


调用all方法时的结果成功的时候是回调函数的参数也是一个数组,这个数组按顺序保存着每一个 promise 对象resolve执行时的值。


(4)race()


race方法和all一样,接受的参数是一个每项都是promise的数组,但是与all不同的是,当最先执行完的事件执行完之后,就直接返回该promise对象的值。如果第一个promise对象状态变成resolved,那自身的状态变成了resolved;反之第一个promise变成rejected,那自身状态就会变成rejected


let promise1 = new Promise((resolve,reject)=>{    setTimeout(()=>{       reject(1);    },2000)});let promise2 = new Promise((resolve,reject)=>{    setTimeout(()=>{       resolve(2);    },1000)});let promise3 = new Promise((resolve,reject)=>{    setTimeout(()=>{       resolve(3);    },3000)});Promise.race([promise1,promise2,promise3]).then(res=>{    console.log(res);    //结果:2},rej=>{    console.log(rej)};)
复制代码


那么race方法有什么实际作用呢?当要做一件事,超过多长时间就不做了,可以用这个方法来解决:


Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})
复制代码


5. finally()


finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。


promise.then(result => {···}).catch(error => {···}).finally(() => {···});
复制代码


上面代码中,不管promise最后的状态,在执行完thencatch指定的回调函数以后,都会执行finally方法指定的回调函数。


下面是一个例子,服务器使用 Promise 处理请求,然后使用finally方法关掉服务器。


server.listen(port)  .then(function () {    // ...  })  .finally(server.stop);
复制代码


finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。finally本质上是then方法的特例:


promise.finally(() => {  // 语句});// 等同于promise.then(  result => {    // 语句    return result;  },  error => {    // 语句    throw error;  });
复制代码


上面代码中,如果不使用finally方法,同样的语句需要为成功和失败两种情况各写一次。有了finally方法,则只需要写一次。

margin 和 padding 的使用场景

  • 需要在 border 外侧添加空白,且空白处不需要背景(色)时,使用 margin;

  • 需要在 border 内测添加空白,且空白处需要背景(色)时,使用 padding。

伪元素和伪类的区别和作用?

  • 伪元素:在内容元素的前后插入额外的元素或样式,但是这些元素实际上并不在文档中生成。它们只在外部显示可见,但不会在文档的源代码中找到它们,因此,称为“伪”元素。例如:


p::before {content:"第一章:";}p::after {content:"Hot!";}p::first-line {background:red;}p::first-letter {font-size:30px;}
复制代码


  • 伪类:将特殊的效果添加到特定选择器上。它是已有元素上添加类别的,不会产生新的元素。例如:


a:hover {color: #FF00FF}p:first-child {color: red}
复制代码


总结: 伪类是通过在元素选择器上加⼊伪类改变元素状态,⽽伪元素通过对元素的操作进⾏对元素的改变。

new 操作符的实现原理

new 操作符的执行过程:


(1)首先创建了一个新的空对象


(2)设置原型,将对象的原型设置为函数的 prototype 对象。


(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)


(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。


具体实现:


function objectFactory() {  let newObject = null;  let constructor = Array.prototype.shift.call(arguments);  let result = null;  // 判断参数是否是一个函数  if (typeof constructor !== "function") {    console.error("type error");    return;  }  // 新建一个空对象,对象的原型为构造函数的 prototype 对象  newObject = Object.create(constructor.prototype);  // 将 this 指向新建对象,并执行函数  result = constructor.apply(newObject, arguments);  // 判断返回对象  let flag = result && (typeof result === "object" || typeof result === "function");  // 判断返回结果  return flag ? result : newObject;}// 使用方法objectFactory(构造函数, 初始化参数);
复制代码

隐藏元素的方法有哪些

  • display: none:渲染树不会包含该渲染对象,因此该元素不会在页面中占据位置,也不会响应绑定的监听事件。

  • visibility: hidden:元素在页面中仍占据空间,但是不会响应绑定的监听事件。

  • opacity: 0:将元素的透明度设置为 0,以此来实现元素的隐藏。元素在页面中仍然占据空间,并且能够响应元素绑定的监听事件。

  • position: absolute:通过使用绝对定位将元素移除可视区域内,以此来实现元素的隐藏。

  • z-index: 负值:来使其他元素遮盖住该元素,以此来实现隐藏。

  • clip/clip-path :使用元素裁剪的方法来实现元素的隐藏,这种方法下,元素仍在页面中占据位置,但是不会响应绑定的监听事件。

  • **transform: scale(0,0)**:将元素缩放为 0,来实现元素的隐藏。这种方法下,元素仍在页面中占据位置,但是不会响应绑定的监听事件。

transition 和 animation 的区别

  • transition 是过度属性,强调过度,它的实现需要触发一个事件(比如鼠标移动上去,焦点,点击等)才执行动画。它类似于 flash 的补间动画,设置一个开始关键帧,一个结束关键帧。

  • animation 是动画属性,它的实现不需要触发事件,设定好时间之后可以自己执行,且可以循环一个动画。它也类似于 flash 的补间动画,但是它可以设置多个关键帧(用 @keyframe 定义)完成动画。

iframe 有那些优点和缺点?

iframe 元素会创建包含另外一个文档的内联框架(即行内框架)。


优点:


  • 用来加载速度较慢的内容(如广告)

  • 可以使脚本可以并行下载

  • 可以实现跨子域通信


缺点:


  • iframe 会阻塞主页面的 onload 事件

  • 无法被一些搜索引擎索识别

  • 会产生很多页面,不容易管理

CSS 预处理器/后处理器是什么?为什么要使用它们?

预处理器, 如:lesssassstylus,用来预编译sass或者less,增加了css代码的复用性。层级,mixin, 变量,循环, 函数等对编写以及开发 UI 组件都极为方便。


后处理器, 如: postCss,通常是在完成的样式表中根据css规范处理css,让其更加有效。目前最常做的是给css属性添加浏览器私有前缀,实现跨浏览器兼容性的问题。


css预处理器为css增加一些编程特性,无需考虑浏览器的兼容问题,可以在CSS中使用变量,简单的逻辑程序,函数等在编程语言中的一些基本的性能,可以让css更加的简洁,增加适应性以及可读性,可维护性等。


其它css预处理器语言:Sass(Scss), Less, Stylus, Turbine, Swithch css, CSS Cacheer, DT Css


使用原因:


  • 结构清晰, 便于扩展

  • 可以很方便的屏蔽浏览器私有语法的差异

  • 可以轻松实现多重继承

  • 完美的兼容了CSS代码,可以应用到老项目中

实现一个宽高自适应的正方形

  • 利用 vw 来实现:


.square {  width: 10%;  height: 10vw;  background: tomato;}
复制代码


  • 利用元素的 margin/padding 百分比是相对父元素 width 的性质来实现:


.square {  width: 20%;  height: 0;  padding-top: 20%;  background: orange;}
复制代码


  • 利用子元素的 margin-top 的值来实现:


.square {  width: 30%;  overflow: hidden;  background: yellow;}.square::after {  content: '';  display: block;  margin-top: 100%;}
复制代码

为什么需要浏览器缓存?

对于浏览器的缓存,主要针对的是前端的静态资源,最好的效果就是,在发起请求之后,拉取相应的静态资源,并保存在本地。如果服务器的静态资源没有更新,那么在下次请求的时候,就直接从本地读取即可,如果服务器的静态资源已经更新,那么我们再次请求的时候,就到服务器拉取新的资源,并保存在本地。这样就大大的减少了请求的次数,提高了网站的性能。这就要用到浏览器的缓存策略了。


所谓的浏览器缓存指的是浏览器将用户请求过的静态资源,存储到电脑本地磁盘中,当浏览器再次访问时,就可以直接从本地加载,不需要再去服务端请求了。


使用浏览器缓存,有以下优点:


  • 减少了服务器的负担,提高了网站的性能

  • 加快了客户端网页的加载速度

  • 减少了多余网络数据传输


用户头像

loveX001

关注

还未添加个人签名 2022-09-01 加入

还未添加个人简介

评论

发布
暂无评论
前端经典面试题(有答案)_JavaScript_loveX001_InfoQ写作社区