前端常见面试题(一)

373 阅读6分钟

1. css3新特性

  1. 颜色:新增 RGBA 、HSLA 模式
  2. 文字阴影( text-shadow )
  3. 边框:圆角(border-radios) 边框阴影: box-shadow
  4. 盒子模型: box-sizing
  5. 背景: background-size \ background-origin \ background-clip
  6. 渐变: linear-gradient \ radial-gradient
  7. 过渡: transition 可实现属性的渐变
  8. 自定义动画 animate@keyfrom
  9. 媒体查询多栏布局: @media screen and (width:800px) {...}
  10. border-image 图片边框
  11. 2D转换/3D转换: transform:translate(x,y) rotate(x,y) skew(s,y) scale(x,y)
  12. 字体图标 iconfont \ icomoon
  13. 弹性布局 flex

2. css优先级

!important > 行内样式 > ID选择器 > 类选择器 > 标签选择器 > 通配符 > 继承 > 浏览器默认属性

权重           由4组数字组成,但是不会有进位

CSS权重选择器优先级计算表格:

选择器选择器权重
继承或者 *0,0,0,0
元素选择器0,0,0,1
类选择器,伪类选择器                                                         0,0,1,0
ID选择器0,1,0,0
行内样式1,0,0,0
!important无穷大

3. px,vw/vh,rem的区别

  1. px像素(Pixel)。绝对单位。像素 px 是相对于显示器屏幕分辨率而言的,是一个虚拟的长度单位,是计算机系统的数字化图像长度单位。
  2. vw 和 vh:视口单位中的“视口”,桌面端指的是浏览器的可视区域,不包含任务栏标题栏以及底部工具栏的浏览器区域大小。1vw等于视口宽度的1%。1vh等于视口高度的1%。
  3. rem 是CSS3新增的一个相对单位(rootem, 根em),使用 rem 为元素设定字体大小时,仍然是相对大小,相对的是 HTMl 的根元素

4. es6新特性

1.在ES6新特性中,增加的两个声明变量和常量的方法,let用于声明变量,const用于声明常量。

2.箭头函数 注意: 箭头函数没有绑定 this

3.数据类型 新增了两个基本数据类型 Bigint(ES10)、Symbol。 还有 set 、map 等引用数据类型

4.模板字符串 ${} 可以在里面写入变量

5.扩展运算符 ... 可以用来展开数组或对象

6.默认参数 定义函数时可以设置默认值

7.解构赋值 解构一个对象可以获取到需要的值

8.数组和字符串 的新方法

5. 如何理解响应式网站?

响应式网站设计(Responsive Web design)是一种网络页面设计布局,页面的设计与开发应当根据用户行为以及设备环境(系统平台、屏幕尺寸、屏幕定向等)进行相应的响应和调整

描述响应式界面最著名的一句话就是“Content is like water”

大白话便是“如果将屏幕看作容器,那么内容就像水一样”

响应式网站常见特点:

  • 同时适配 PC + 平板 + 手机等
  • 标签导航在接近手持终端设备时改变为经典的抽屉式导航
  • 网站的布局会根据视口来调整模块的大小和位置

ae68be30-9dba-11eb-85f6-6fac77c0c9b3.png

实现方式

响应式设计的基本原理是通过媒体查询检测不同的设备屏幕尺寸做处理,为了处理移动端,页面头部必须有meta声明viewport

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no”>

属性对应如下:

width=device-width: 是自适应手机屏幕的尺寸宽度

maximum-scale:是缩放比例的最大值

inital-scale:是缩放的初始化

user-scalable:是用户的可以缩放的操作

实现响应式布局的方式有如下:

  • 媒体查询
  • 百分比
  • vw/vh
  • rem

总结

响应式布局优点可以看到:

  • 面对不同分辨率设备灵活性强
  • 能够快捷解决多设备显示适应问题

缺点:

  • 仅适用布局、信息、框架并不复杂的部门类型网站
  • 兼容各种设备工作量大,效率低下
  • 代码累赘,会出现隐藏无用的元素,加载时间加长
  • 其实这是一种折中性质的设计解决方案,多方面因素影响而达不到最佳效果
  • 一定程度上改变了网站原有的布局结构,会出现用户混淆的情况

6. 闭包

简答: 闭包是一个可以访问其他函数内部变量的函数,主要作用是解决变量污染问题,也可以用来延长局部变量的生命周期。闭包在 js 中使用比较多,几乎是无处不在的。一般大多数情况下,在回调函数中闭包用的是最多的。

什么是闭包

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)

也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域

在 JavaScript中,每当创建一个函数,闭包就会在函数创建的同时被创建出来,作为函数内部与外部连接起来的一座桥梁

image.png

使用场景

任何闭包的使用场景都离不开这两点:

  • 创建私有变量
  • 延长变量的生命周期

一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但创建时所在词法环境依然存在,以达到延长变量的生命周期的目的

7. 原型和原型链

简答:

  • 原型是我们创建函数的时候,系统帮我们自动生成的一个对象。 主要作用是解决构造函数内部方法内存资源浪费问题。在开发中我们一般把实例对象一些通用的方法放入原型中,在 vue 里面有时候也会给 vue 的原型添加一些公共类方法来实现所有的组件中可以共享成员。像一些常见的routerrouter和store 都是挂载到 vue 的原型上的。
  • 原型链是 js 对象一种查找机制,遵循就近原则。当我们访问一个对象中的成员的时候,会优先访问自己的,如果自己没有就访问原型的,如果原型也没有就会访问原型的原型,直到原型链的终点 null. 如果还没有,此时属性就会获取 undefined,方法就会报错 xxx is not a function。一般原型链主要是用来实现面向对象继承的。

8. js继承

继承是什么

继承(inheritance)是面向对象软件技术当中的一个概念。

如果一个类别 B“继承自”另一个类别 A,就把这个 B 称为“A 的子类”,而把 A 称为“B 的父类别”也可以称“A 是 B 的超类”

继承的优点: 不劳而获, 继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码

在子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能

二、实现方式

下面给出JavaScripy常见的继承方式:

  • 原型链继承
  • 构造函数继承(借助 call)
  • 组合继承
  • 原型式继承
  • 寄生式继承
  • 寄生组合式继承

原型链继承

原型链继承是比较常见的继承方式之一,其中涉及的构造函数、原型和实例,三者之间存在着一定的关系,即每一个构造函数都有一个原型对象,原型对象又包含一个指向构造函数的指针,而实例则包含一个原型对象的指针

举例

image.png

上面代码看似没问题,实际存在潜在问题

image.png

改变s1play属性,会发现s2也跟着发生变化了,这是因为两个实例使用的是同一个原型对象,内存空间是共享的

构造函数继承

借助 call调用Parent函数

image.png

可以看到,父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法

相比第一种原型链继承方式,父类的引用属性不会被共享,优化了第一种继承方式的弊端,但是只能继承父类的实例属性和方法,不能继承原型属性或者方法

9. 如何css画一个三角形

要点: 一个 div 宽高设为 0 ,设 4 条边的宽度和颜色, 再给其中 3 条设置透明色

    div {
      width: 0px;
      height: 0px;
      border-top: 20px solid transparent;
      border-bottom: 20px solid transparent;
      border-left: 20px solid green;
      border-right: 20px solid transparent;
    }

image.png

11. 数组的常见方法?

  • 遍历方法

    • map : 映射数组,得到一个映射之后的新数组
    • filter:筛选数组
    • forEach:遍历数组
    • some:判断数组是否有元素满足条件(相当于逻辑或:一真则真,全假为假)
    • every:判断数组是否所有满足都满足条件(相当于逻辑与:一假则假,全真为真)
    • findIndex:查找元素下标,一般用于元素是引用类型
    • reduce:给数组每一个元素执行一次回调,一般用于数组元素求和(也可以求最大值、最小值)
  • 增删改查方法

    • push() : 末尾新增元素,返回值是新数组长度
    • unshift():开头新增元素,返回值是新数组长度
    • pop() :末尾删除元素,返回值是删除的那个末尾元素
    • shift(): 开头删除元素,返回值是开头的那个末尾元素
    • splice() : 删除指定下标元素,第三个参数是一个剩余参数,可以在删除的元素后面插入元素
  • 其他方法

    • reverse:翻转数组,会修改数组自身
    • sort: 数组排序,会修改数组自身
    • json: 拼接数组元素,返回值是拼接之后的字符串
    • slice: 根据下标范围查询数组元素,返回值是查询后的新数组
    • indexOf: 查询元素下标,一般用于元素是值类型

12. 如何判断数据类型

Object.prototype.toString()

每个对象都有一个 toString() 方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。默认情况下,toString() 方法被每个 Object 对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 “[object type]”,其中 type 是对象的类型。

就比如

const obj = {}
obj.toString() //    '[object Object]'

要想要其他的对象使用 Object.prototype.toString() 就需要使用 Function.prototype.call() 改变 this 的指向

Object.prototype.toString.call([]) //    '[object Array]'

const blob = new Blob(['hello'], { type: 'text/html'})
Object.prototype.toString.call(blob) //    '[object Blob]'

const div = document.querySelectorAll('div')
Object.prototype.toString.call(div) // '[object NodeList]'

可以看出 blob 、 NodeList 、 Array 都可以判断出来

如果要封装成函数可以使用 String.prototype.slice(8,-2) 就可以截取到对象的类型

原文链接:juejin.cn/post/725365…

13. var、let和const的区别

  • var 声明变量可以重复声明, 而 let 不可以重复声明
  • var 是不受限于块级的,而 let 是受限于块级
  • var 会与 window 相映射(会挂一个属性), 而 let 不与 window 相映射
  • var 可以在声明的上面访问变量, 而 let 有暂存死区, 在声明的上面访问变量会报错
  • const 声明之后必须赋值,否则会报错
  • const 定义不可变的量,改变了就会报错
  • const 和 let 一样不会与 window 相映射、支持块级作用域、在声明的上面访问变量会报错

14. defineproperty 和 Proxy

Object.defineProperty()

定义:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象

为什么能实现响应式

通过defineProperty 两个属性,getset

get

属性的 getter 函数,当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的 this 并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值

set

属性的 setter 函数,当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。默认为 undefined

基本用法

image.png

添加渲染函数实现响应式效果

const person = {}

function render() {
  document.querySelector('div').innerHTML = person.name
}

function Reactive(obj,key,val) {
  Object.defineProperty(obj,key,{
    set(newVal) {
      if (newVal === val) return
      val = newVal
      console.log('set!');
      render()     // 当对象的属性发生改变时执行渲染函数完成响应式效果
},
    get() {
      console.log('get!', val);
      return val
    }
  })
}

Reactive(person,'name','Warwick')

render()

效果如图:

动画.gif

当对象有多个属性时就需要进行遍历

function observe(obj) {
  if (typeof obj !== 'object' || obj == null) {
    return
  }
  Object.keys(obj).forEach((key) => {
    Reactive(obj, key, obj[key])
  })
}

如果存在嵌套对象的情况,还需要在Reactive中进行递归

function Reactive(obj,key,val) {
  observe(val)
  Object.defineProperty(obj,key,{
    set(newVal) {
      if (newVal === val) return
      val = newVal
      console.log('set!');
      render()
},
    get() {
      console.log('get!', val);
      return val
    }
  })
}

当给key赋值为对象的时候,还需要在set属性中进行递归

set(newVal) {
      if (newVal === val) return
      observe(newVal)
      render()
}

上述例子能够实现对一个对象的基本响应式,但仍然存在诸多问题

现在对一个对象进行删除与添加属性操作,无法劫持到

const obj = {
  foo: 'foo',
  bar: 'bar',
}
observe(obj)
delete obj.foo // no ok
obj.jar = 'xxx' // no ok

当我们对一个数组进行监听的时候,并不那么好使了

const arrData = [1, 2, 3, 4, 5]
arrData.forEach((val, index) => {
  Reactive(arrData, index, val)
})
arrData.push() // no ok
arrData.pop() // no ok
arrDate[0] = 99 // ok

可以看到数据的api无法劫持到,从而无法实现数据响应式,

所以在Vue2中,增加了setdelete API,并且对数组api方法进行一个重写

还有一个问题则是,如果存在深层的嵌套对象关系,需要深层的进行监听,造成了性能的极大问题

小结

  • 检测不到对象属性的添加和删除
  • 数组API方法无法监听到
  • 需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题

proxy

Proxy的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作,这就完全可以代理所有属性了

定义一个响应式方法reactive

function reactive(obj) {
  if (typeof obj !== 'object' && obj != null) {
    return obj
  }
  // Proxy相当于在对象外层加拦截
  const observed = new Proxy(obj, {
    get(target, key, receiver) {
      const res = Reflect.get(target, key, receiver)
      console.log(`获取${key}:${res}`)
      return res
    },
    set(target, key, value, receiver) {
      const res = Reflect.set(target, key, value, receiver)
      console.log(`设置${key}:${value}`)
      return res
    },
    deleteProperty(target, key) {
      const res = Reflect.deleteProperty(target, key)
      console.log(`删除${key}:${res}`)
      return res
    },
  })
  return observed
}

测试一下简单数据的操作,发现都能劫持

const state = reactive({
  foo: 'foo',
})
// 1.获取
state.foo // ok
// 2.设置已存在属性
state.foo = 'fooooooo' // ok
// 3.设置不存在属性
state.dong = 'dong' // ok
// 4.删除属性
delete state.dong // ok

再测试嵌套对象情况,这时候发现就不那么 OK 了,如果要解决,需要在get之上再进行一层代理

总结

Object.defineProperty只能遍历对象属性进行劫持

Proxy直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的

Proxy可以直接监听数组的变化(pushshiftsplice

Proxy有多达 13 种拦截方法,不限于applyownKeysdeletePropertyhas等等,这是Object.defineProperty不具备的

正因为defineProperty自身的缺陷,导致Vue2在实现响应式过程需要实现其他的方法辅助(如重写数组方法、增加额外setdelete方法)

Proxy 不兼容 IE,也没有 polyfilldefineProperty 能支持到 IE9

15. 防抖和节流

防抖:单位时间内,频繁触发事件,只执行最后一次。 防抖的主要应用场景:

  • 搜索框搜索输入。只需用户最后一次输入完,再发送请求
  • 手机号、邮箱验证输入检测

节流:单位时间内,频繁触发事件,只执行一次。 节流的主要应用场景:

  • 高频事件 例如 resize 事件、scroll 事件
  • 手机号、邮箱验证输入检测

相同点:

  • 都可以通过使用 setTimeout 来实现
  • 降低回调执行频率。节省计算资源

不同点:

  • 函数防抖,在一段连续操作结束后,处理回调,利用 clearTimeout 和 setTimeout 来实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能
  • 函数防抖关注一定时间连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次

16. 箭头函数

箭头函数是定义函数一种新的方式,他比传统函数 function 定义更加方便和简单,他没有绑定自己的 this 指向和伪数组 arguments,无法调用 super 方法生成实例化对象,因为他不是构造函数,一般用来取代匿名函数的写法,最主要的是箭头函数的 this 指向他的上一级作用域中的 this 也可以理解为他的 this 是固定的,而普通函数的 this 是可变的

17. 重绘和回流

重绘: 当元素的一部分属性发生改变, 如外观、背景、颜色等不会引起布局变化,只需要浏览器根据元素的新属性重新绘制,使元素呈现新的外观叫做重绘。

重排回流): 当 render 树中的一部分或者全部因为大小边距等问题发生改变而需要DOM树重新计算的过程

重绘不一定需要重排(比如颜色发生改变),重排必然导致重绘(比如改变网页位置)

18. 递归函数

在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数

其核心思想是把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解

一般来说,递归需要有边界条件、递归前进阶段和递归返回阶段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回

用递归写一个累加函数:

function count(num) {
  if (num == 1) return 1
  else {
    return count(num - 1) + num
  }
}

console.log(count(100))

19. 微任务和宏任务

微任务

一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前

常见的微任务有:

  • Promise.then
  • MutaionObserver
  • Object.observe(已废弃;Proxy 对象替代)
  • process.nextTick(Node.js)

宏任务

宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合

常见的宏任务有:

  • script (可以理解为外层同步代码)
  • setTimeout/setInterval
  • UI rendering/UI事件
  • postMessage、MessageChannel
  • setImmediate、I/O(Node.js)

image.png

按照这个流程,它的执行机制是:

  • 执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中
  • 当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完

20. eventloop*

JavaScript是一门单线程的语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环

JavaScript中,所有的任务都可以分为

  • 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
  • 异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等

同步任务与异步任务的运行流程图如下:

JavaScript事件循环.png