关于前端:社招前端必会面试题

2次阅读

共计 12052 个字符,预计需要花费 31 分钟才能阅读完成。

label 的作用是什么?如何应用?

label 标签来定义表单控件的关系:当用户抉择 label 标签时,浏览器会主动将焦点转到和 label 标签相干的表单控件上。

  • 应用办法 1:
<label for="mobile">Number:</label>
<input type="text" id="mobile"/>
  • 应用办法 2:
<label>Date:<input type="text"/></label>

如何⽤ 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 插件来进⾏公共模块抽取, 利⽤浏览器缓存能够⻓期缓存这些⽆需频繁变动的公共代码

Promise.resolve

Promise.resolve = function(value) {
    // 1. 如果 value 参数是一个 Promise 对象,则一成不变返回该对象
    if(value instanceof Promise) return value;
    // 2. 如果 value 参数是一个具备 then 办法的对象,则将这个对象转为 Promise 对象,并立刻执行它的 then 办法
    if(typeof value === "object" && 'then' in value) {return new Promise((resolve, reject) => {value.then(resolve, reject);
        });
    }
    // 3. 否则返回一个新的 Promise 对象,状态为 fulfilled
    return new Promise(resolve => resolve(value));
}

深拷贝浅拷贝

浅拷贝:浅拷贝通过 ES6 新个性 Object.assign()或者通过扩大运算法... 来达到浅拷贝的目标,浅拷贝批改
正本,不会影响原数据,但毛病是浅拷贝只能拷贝第一层的数据,且都是值类型数据,如果有援用型数据,批改
正本会影响原数据。深拷贝:通过利用 JSON.parse(JSON.stringify())来实现深拷贝的目标,但利用 JSON 拷贝也是有毛病的,当要拷贝的数据中含有 undefined/function/symbol 类型是无奈进行拷贝的,当然咱们想我的项目开发中须要
深拷贝的数据个别不会含有以上三种类型,如有须要能够本人在封装一个函数来实现。

数组去重

应用 indexOf/includes 实现

function unique(arr) {var res = [];
    for(var i = 0; i < arr.length; i++) {if(res.indexOf(arr[i]) === -1) res.push(arr[i]);
        // if(!res.includes(arr[i])) res.push(arr[i]);
    }
    return res;
}

应用 filter(forEach) + indexOf/includes 实现

// filter
function unique(arr) {var res = arr.filter((value, index) => {
        // 只存第一个呈现的元素
        return arr.indexOf(value) === index;
    });
    return res;
}
// forEach
function unique(arr) {var res = [];
    arr.forEach((value) => {if(!res.includes(value)) res.push(value);
    });
    return res;
}

非 API 版本(原生)实现

function unique(arr) {var res = [];
    for(var i = 0; i < arr.length; i++) {
        var flag = false;
        for(var j = 0; j < res.length; j++) {if(arr[i] === res[j]) {
                flag = true;
                break;
            }
        }
        if(flag === false) res.push(arr[i]);
    }
    return res;
}

ES6 应用 Set + 扩大运算符(…)/Array.from() 实现

function unique(arr) {// return [...new Set(arr)];
    return Array.from(new Set(arr));
}

setTimeout、setInterval、requestAnimationFrame 各有什么特点?

异步编程当然少不了定时器了,常见的定时器函数有 setTimeoutsetIntervalrequestAnimationFrame。最罕用的是setTimeout,很多人认为 setTimeout 是延时多久,那就应该是多久后执行。

其实这个观点是谬误的,因为 JS 是单线程执行的,如果后面的代码影响了性能,就会导致 setTimeout 不会按期执行。当然了,能够通过代码去修改 setTimeout,从而使定时器绝对精确:

let period = 60 * 1000 * 60 * 2
let startTime = new Date().getTime()
let count = 0
let end = new Date().getTime() + period
let interval = 1000
let currentInterval = interval
function loop() {
  count++
  // 代码执行所耗费的工夫
  let offset = new Date().getTime() - (startTime + count * interval);
  let diff = end - new Date().getTime()
  let h = Math.floor(diff / (60 * 1000 * 60))
  let hdiff = diff % (60 * 1000 * 60)
  let m = Math.floor(hdiff / (60 * 1000))
  let mdiff = hdiff % (60 * 1000)
  let s = mdiff / (1000)
  let sCeil = Math.ceil(s)
  let sFloor = Math.floor(s)
  // 失去下一次循环所耗费的工夫
  currentInterval = interval - offset 
  console.log('时:'+h, '分:'+m, '毫秒:'+s, '秒向上取整:'+sCeil, '代码执行工夫:'+offset, '下次循环距离'+currentInterval) 
  setTimeout(loop, currentInterval)
}
setTimeout(loop, currentInterval)

接下来看 setInterval,其实这个函数作用和 setTimeout 基本一致,只是该函数是每隔一段时间执行一次回调函数。

通常来说不倡议应用 setInterval。第一,它和 setTimeout 一样,不能保障在预期的工夫执行工作。第二,它存在执行累积的问题,请看以下伪代码

function demo() {setInterval(function(){console.log(2)
  },1000)
  sleep(2000)
}
demo()

以上代码在浏览器环境中,如果定时器执行过程中呈现了耗时操作,多个回调函数会在耗时操作完结当前同时执行,这样可能就会带来性能上的问题。

如果有循环定时器的需要,其实齐全能够通过 requestAnimationFrame 来实现:

function setInterval(callback, interval) {
  let timer
  const now = Date.now
  let startTime = now()
  let endTime = startTime
  const loop = () => {timer = window.requestAnimationFrame(loop)
    endTime = now()
    if (endTime - startTime >= interval) {startTime = endTime = now()
      callback(timer)
    }
  }
  timer = window.requestAnimationFrame(loop)
  return timer
}
let a = 0
setInterval(timer => {console.log(1)
  a++
  if (a === 3) cancelAnimationFrame(timer)
}, 1000)

首先 requestAnimationFrame 自带函数节流性能,根本能够保障在 16.6 毫秒内只执行一次(不掉帧的状况下),并且该函数的延时成果是准确的,没有其余定时器工夫不准的问题,当然你也能够通过该函数来实现 setTimeout

JavaScript 中如何进行隐式类型转换?

首先要介绍 ToPrimitive 办法,这是 JavaScript 中每个值隐含的自带的办法,用来将值(无论是根本类型值还是对象)转换为根本类型值。如果值为根本类型,则间接返回值自身;如果值为对象,其看起来大略是这样:

/*** @obj 须要转换的对象 * @type 冀望的后果类型 */
ToPrimitive(obj,type)

type的值为 number 或者string

(1)当 typenumber时规定如下:

  • 调用 objvalueOf办法,如果为原始值,则返回,否则下一步;
  • 调用 objtoString办法,后续同上;
  • 抛出TypeError 异样。

(2)当 typestring时规定如下:

  • 调用 objtoString办法,如果为原始值,则返回,否则下一步;
  • 调用 objvalueOf办法,后续同上;
  • 抛出TypeError 异样。

能够看出两者的次要区别在于调用 toStringvalueOf的先后顺序。默认状况下:

  • 如果对象为 Date 对象,则 type 默认为string
  • 其余状况下,type默认为number

总结下面的规定,对于 Date 以外的对象,转换为根本类型的大略规定能够概括为一个函数:

var objToNumber = value => Number(value.valueOf().toString())
objToNumber([]) === 0
objToNumber({}) === NaN

而 JavaScript 中的隐式类型转换次要产生在 +、-、*、/ 以及 ==、>、< 这些运算符之间。而这些运算符只能操作根本类型值,所以在进行这些运算前的第一步就是将两边的值用 ToPrimitive 转换成根本类型,再进行操作。

以下是根本类型的值在不同操作符的状况下隐式转换的规定(对于对象,其会被 ToPrimitive 转换成根本类型,所以最终还是要利用根本类型转换规定):

  1. +操作符 + 操作符的两边有至多一个 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
  1. -*\操作符

NaN也是一个数字

1 * '23' // 23
 1 * false // 0
 1 / 'aa' // NaN
  1. 对于 == 操作符

操作符两边的值都尽量转成number

3 == true // false, 3 转为 number 为 3,true 转为 number 为 1
'0' == false //true, '0' 转为 number 为 0,false 转为 number 为 0
'0' == 0 // '0' 转为 number 为 0
  1. 对于 <>比拟符

如果两边都是字符串,则比拟字母表程序:

'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]"

应用 clear 属性革除浮动的原理?

应用 clear 属性革除浮动,其语法如下:

clear:none|left|right|both

如果单看字面意思,clear:left 是“革除左浮动”,clear:right 是“革除右浮动”,实际上,这种解释是有问题的,因为浮动始终还在,并没有革除。

官网对 clear 属性解释:“元素盒子的边不能和后面的浮动元素相邻”,对元素设置 clear 属性是为了防止浮动元素对该元素的影响,而不是革除掉浮动。

还须要留神 clear 属性指的是元素盒子的边不能和后面的浮动元素相邻,留神这里“后面的”3 个字,也就是 clear 属性对“前面的”浮动元素是充耳不闻的。思考到 float 属性要么是 left,要么是 right,不可能同时存在,同时因为 clear 属性对“前面的”浮动元素充耳不闻,因而,当 clear:left 无效的时候,clear:right 必然有效,也就是此时 clear:left 等同于设置 clear:both;同样地,clear:right 如果无效也是等同于设置 clear:both。由此可见,clear:left 和 clear:right 这两个申明就没有任何应用的价值,至多在 CSS 世界中是如此,间接应用 clear:both 吧。

个别应用伪元素的形式革除浮动:

.clear::after{content:'';  display: block;   clear:both;}

clear 属性只有块级元素才无效的,而::after 等伪元素默认都是内联程度,这就是借助伪元素革除浮动影响时须要设置 display 属性值的起因。

HTML5 的离线贮存怎么应用,它的工作原理是什么

离线存储指的是:在用户没有与因特网连贯时,能够失常拜访站点或利用,在用户与因特网连贯时,更新用户机器上的缓存文件。

原理:HTML5 的离线存储是基于一个新建的 .appcache 文件的缓存机制(不是存储技术),通过这个文件上的解析清单离线存储资源,这些资源就会像 cookie 一样被存储了下来。之后当网络在处于离线状态下时,浏览器会通过被离线存储的数据进行页面展现

应用办法:(1)创立一个和 html 同名的 manifest 文件,而后在页面头部退出 manifest 属性:

<html lang="en" manifest="index.manifest">

(2)在 cache.manifest 文件中编写须要离线存储的资源:

CACHE MANIFEST
    #v0.11
    CACHE:
    js/app.js
    css/style.css
    NETWORK:
    resourse/logo.png
    FALLBACK:
    / /offline.html
  • CACHE: 示意须要离线存储的资源列表,因为蕴含 manifest 文件的页面将被主动离线存储,所以不须要把页面本身也列出来。
  • NETWORK: 示意在它上面列出来的资源只有在在线的状况下能力拜访,他们不会被离线存储,所以在离线状况下无奈应用这些资源。不过,如果在 CACHE 和 NETWORK 中有一个雷同的资源,那么这个资源还是会被离线存储,也就是说 CACHE 的优先级更高。
  • FALLBACK: 示意如果拜访第一个资源失败,那么就应用第二个资源来替换他,比方下面这个文件示意的就是如果拜访根目录下任何一个资源失败了,那么就去拜访 offline.html。

(3)在离线状态时,操作 window.applicationCache 进行离线缓存的操作。

如何更新缓存:

(1)更新 manifest 文件

(2)通过 javascript 操作

(3)革除浏览器缓存

注意事项:

(1)浏览器对缓存数据的容量限度可能不太一样(某些浏览器设置的限度是每个站点 5MB)。

(2)如果 manifest 文件,或者外部列举的某一个文件不能失常下载,整个更新过程都将失败,浏览器持续全副应用老的缓存。

(3)援用 manifest 的 html 必须与 manifest 文件同源,在同一个域下。

(4)FALLBACK 中的资源必须和 manifest 文件同源。

(5)当一个资源被缓存后,该浏览器间接申请这个绝对路径也会拜访缓存中的资源。

(6)站点中的其余页面即便没有设置 manifest 属性,申请的资源如果在缓存中也从缓存中拜访。

(7)当 manifest 文件产生扭转时,资源申请自身也会触发更新。

代码输入后果

var A = {n: 4399};
var B =  function(){this.n = 9999};
var C =  function(){var n = 8888};
B.prototype = A;
C.prototype = A;
var b = new B();
var c = new C();
A.n++
console.log(b.n);
console.log(c.n);

输入后果:9999 4400

解析:

  1. console.log(b.n),在查找 b.n 是首先查找 b 对象本身有没有 n 属性,如果没有会去原型(prototype)上查找,当执行 var b = new B()时,函数外部 this.n=9999(此时 this 指向 b) 返回 b 对象,b 对象有本身的 n 属性,所以返回 9999。
  2. console.log(c.n),同理,当执行 var c = new C()时,c 对象没有本身的 n 属性,向上查找,找到原型(prototype)上的 n 属性,因为 A.n++(此时对象 A 中的 n 为 4400),所以返回 4400。

代码输入后果

var x = 3;
var y = 4;
var obj = {
    x: 1,
    y: 6,
    getX: function() {
        var x = 5;
        return function() {return this.x;}();},
    getY: function() {
        var y = 7;
        return this.y;
    }
}
console.log(obj.getX()) // 3
console.log(obj.getY()) // 6

输入后果:3 6

解析:

  1. 咱们晓得,匿名函数的 this 是指向全局对象的,所以 this 指向 window,会打印出 3;
  2. getY 是由 obj 调用的,所以其 this 指向的是 obj 对象,会打印出 6。

use strict 是什么意思 ? 应用它区别是什么?

use strict 是一种 ECMAscript5 增加的(严格模式)运行模式,这种模式使得 Javascript 在更严格的条件下运行。设立严格模式的目标如下:

  • 打消 Javascript 语法的不合理、不谨严之处,缩小怪异行为;
  • 打消代码运行的不平安之处,保障代码运行的平安;
  • 进步编译器效率,减少运行速度;
  • 为将来新版本的 Javascript 做好铺垫。

区别:

  • 禁止应用 with 语句。
  • 禁止 this 关键字指向全局对象。
  • 对象不能有重名的属性。

数组的遍历办法有哪些

办法 是否扭转原数组 特点
forEach() 数组办法,不扭转原数组,没有返回值
map() 数组办法,不扭转原数组,有返回值,可链式调用
filter() 数组办法,过滤数组,返回蕴含符合条件的元素的数组,可链式调用
for…of for…of 遍历具备 Iterator 迭代器的对象的属性,返回的是数组的元素、对象的属性值,不能遍历一般的 obj 对象,将异步循环变成同步循环
every() 和 some() 数组办法,some()只有有一个是 true,便返回 true;而 every()只有有一个是 false,便返回 false.
find() 和 findIndex() 数组办法,find()返回的是第一个符合条件的值;findIndex()返回的是第一个返回条件的值的索引值
reduce() 和 reduceRight() 数组办法,reduce()对数组正序操作;reduceRight()对数组逆序操作

计算属性和 watch 有什么区别? 以及它们的使用场景?

// 区别
  computed 计算属性:依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值产生扭转,下一次获取 computed 的值时才会从新计算 computed 的值。watch 侦听器:更多的是察看的作用, 无缓存性, 相似与某些数据的监听回调, 每当监听的数据变动时都会执行回调进行后续操作
// 使用场景
  当须要进行数值计算, 并且依赖与其它数据时, 应该应用 computed, 因为能够利用 computed 的缓存属性, 防止每次获取值时都要从新计算。当须要在数据变动时执行异步或开销较大的操作时, 应该应用 watch, 应用 watch 选项容许执行异步操作(拜访一个 API), 限度执行该操作的频率,并在失去最终后果前,设置中间状态。这些都是计算属性无奈做到的。

webpack 配置入口进口

module.exports={
    // 入口文件的配置项
    entry:{},
    // 进口文件的配置项
    output:{},
    // 模块:例如解读 CSS, 图片如何转换,压缩
    module:{},
    // 插件,用于生产模版和各项性能
    plugins:[],
    // 配置 webpack 开发服务性能
    devServer:{}}
简略形容了一下这几个属性是干什么的。形容一下 npm run dev / npm run build 执行的是哪些文件
通过配置 proxyTable 来达到开发环境跨域的问题,而后又能够扩大和他聊聊跨域的产生,如何跨域
最初能够在聊聊 webpack 的优化,例如 babel-loader 的优化,gzip 压缩等等

Proxy 能够实现什么性能?

在 Vue3.0 中通过 Proxy 来替换本来的 Object.defineProperty 来实现数据响应式。

Proxy 是 ES6 中新增的性能,它能够用来自定义对象中的操作。

let p = new Proxy(target, handler)

target 代表须要增加代理的对象,handler 用来自定义对象中的操作,比方能够用来自定义 set 或者 get 函数。

上面来通过 Proxy 来实现一个数据响应式:

let onWatch = (obj, setBind, getLogger) => {
  let handler = {get(target, property, receiver) {getLogger(target, property)
      return Reflect.get(target, property, receiver)
    },
    set(target, property, value, receiver) {setBind(value, property)
      return Reflect.set(target, property, value)
    }
  }
  return new Proxy(obj, handler)
}
let obj = {a: 1}
let p = onWatch(
  obj,
  (v, property) => {console.log(` 监听到属性 ${property}扭转为 ${v}`)
  },
  (target, property) => {console.log(`'${property}' = ${target[property]}`)
  }
)
p.a = 2 // 监听到属性 a 扭转
p.a // 'a' = 2

在上述代码中,通过自定义 setget 函数的形式,在本来的逻辑中插入了咱们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。

当然这是简略版的响应式实现,如果须要实现一个 Vue 中的响应式,须要在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要应用 Proxy 替换本来的 API 起因在于 Proxy 无需一层层递归为每个属性增加代理,一次即可实现以上操作,性能上更好,并且本来的实现有一些数据更新不能监听到,然而 Proxy 能够完满监听到任何形式的数据扭转,惟一缺点就是浏览器的兼容性不好。

call() 和 apply() 的区别?

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

  • apply 承受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带下标的汇合,这个汇合能够为数组,也能够为类数组,apply 办法把这个汇合中的元素作为参数传递给被调用的函数。
  • call 传入的参数数量不固定,跟 apply 雷同的是,第一个参数也是代表函数体内的 this 指向,从第二个参数开始往后,每个参数被顺次传入函数。

说一下 data 为什么是一个函数而不是一个对象?

JavaScript 中的对象是援用类型的数据,当多个实例援用同一个对象时,只有一个实例对这个对象进行操作,其余实例中的数据也会发生变化。而在 Vue 中,咱们更多的是想要复用组件,那就须要每个组件都有本人的数据,这样组件之间才不会互相烦扰。所以组件的数据不能写成对象的模式,而是要写成函数的模式。数据以函数返回值的模式定义,这样当咱们每次复用组件的时候,就会返回一个新的 data,也就是说每个组件都有本人的公有数据空间,它们各自保护本人的数据,不会烦扰其余组件的失常运行。

写代码:实现函数可能深度克隆根本类型

浅克隆:

function shallowClone(obj) {let cloneObj = {};

  for (let i in obj) {cloneObj[i] = obj[i];
  }

  return cloneObj;
}

深克隆:

  • 思考根底类型
  • 援用类型

    • RegExp、Date、函数 不是 JSON 平安的
    • 会失落 constructor,所有的构造函数都指向 Object
    • 破解循环援用
function deepCopy(obj) {if (typeof obj === 'object') {var result = obj.constructor === Array ? [] : {};

    for (var i in obj) {result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i];
    }
  } else {var result = obj;}

  return result;
}

Vue 的生命周期是什么 每个钩子外面具体做了什么事件

Vue 实例有⼀个残缺的⽣命周期,也就是从开始创立、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是 Vue 的⽣命周期。1、beforeCreate(创立前):数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能拜访到 data、computed、watch、methods 上的办法和数据。2、created(创立后):实例创立实现,实例上配置的 options 包含 data、computed、watch、methods 等都配置实现,然而此时渲染得节点还未挂载到 DOM,所以不能拜访到 `$el` 属性。3、beforeMount(挂载前):在挂载开始之前被调用,相干的 render 函数首次被调用。实例已实现以下的配置:编译模板,把 data 外面的数据和模板生成 html。此时还没有挂载 html 到页面上。4、mounted(挂载后):在 el 被新创建的 vm.$el 替换,并挂载到实例下来之后调用。实例已实现以下的配置:用下面编译好的 html 内容替换 el 属性指向的 DOM 对象。实现模板中的 html 渲染到 html 页面中。此过程中进行 ajax 交互。5、beforeUpdate(更新前):响应式数据更新时调用,此时尽管响应式数据更新了,然而对应的实在 DOM 还没有被渲染。6、updated(更新后):在因为数据更改导致的虚构 DOM 从新渲染和打补丁之后调用。此时 DOM 曾经依据响应式数据的变动更新了。调用时,组件 DOM 曾经更新,所以能够执行依赖于 DOM 的操作。然而在大多数状况下,应该防止在此期间更改状态,因为这可能会导致更新有限循环。该钩子在服务器端渲染期间不被调用。7、beforeDestroy(销毁前):实例销毁之前调用。这一步,实例依然齐全可用,`this` 仍能获取到实例。8、destroyed(销毁后):实例销毁后调用,调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。另外还有 `keep-alive` 独有的生命周期,别离为 `activated` 和 `deactivated`。用 `keep-alive` 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 `deactivated` 钩子函数,命中缓存渲染后会执行 `activated` 钩子函数。
正文完
 0