2020年前端面试题·三

2020年前端面试题·三

    • 闭包
    • 作用域链
    • 原型和原型链
    • 继承
    • 事件代理
    • this 指向问题
    • new 操作符生成实例的过程
    • Ajax 原理
    • 跨域问题的产生和解决
    • get和post的区别
    • 模块化开发
    • 异步加载 JS 的方法
    • defer 和 async 的异同点
    • offsetWidth/offsetHeight、clientWidth/clientHeight、scrollWidth/scrollHeight 及相关属性的区别
    • Promise
    • JS 数据类型
    • JavaScript 基本规范
    • null 和 undefined 的区别
    • 严格模式
    • attribute 和 property 的区别
    • let 、var 、const 的区别
    • vue 双向数据绑定原理
    • 快速让数组乱序
    • 如何渲染几万条数据并不卡主界面
    • 原生 JS 操作 DOM 节点

闭包

什么是闭包?
  闭包是指能在函数外部获取到声明在函数内部变量的函数

闭包的作用

  1. 闭包可以储存变量,一般在函数体内声明的变量,在使用完后会被释放,但闭包的外部函数中的变量一直是引用状态,不会被释放。
  2. 闭包可以用来操作私有变量。

例如:

function A() {
    let num = 10
    return function () {
        return num
    }
}

作用域链

作用域链保证了执行环境里有权访问的变量和函数时有序的,作用域链中的变量只能向上访问,直到访问到 window 对象

原型和原型链

每个对象中都有一个内部属性,就是 prototype(原型),每个对象声明的实例中也都含有一个属性,就是 __proto__ (原型)

关系: instance.__proto__ == instance.constructor.prototype

特点: 当我们访问一个对象的属性时,如果在对象上找不到,就会沿着原型链向上层寻找,直到 Object

继承

父类(构造函数):

function Animal(name) {
    this.name = name
}
Animal.prototype.eat = function () {
    console.log('吃肉..')
}
  1. 原型链继承
function Dog() {}
Dog.prototype = new Animal()
let dog = new Dog()
console.log(dog.name)   // undefined
dog.eat()               // 吃肉..

  重点: 让子构造函数的原型指向父构造函数的实例
  优点: 可以继承父构造函数原型中的属性和方法
  缺点: 新实例无法向父类构造函数传参,无法继承构造函数实例中的属性和方法

  1. 构造函数继承
function Cat() {
    Animal.call(this, '猫')
}
let cat = new Cat()
console.log(cat.name)   // 猫
cat.eat()               // cat.eat is not a function

  重点: 子构造函数中调用父构造函数并改变 this 指向
  优点: 可以继承父构造函数中的属性和方法
  缺点: 无法继承构造函数原型中的属性和方法

  1. 组合继承(常用)
function Cat(name) {
    Animal.call(this, name)
}
Cat.prototype = new Animal()
let cat = new Cat('猫')
console.log(cat.name)
cat.eat()

  重点: 结合构造函数继承和原型链继承的特点
  优点: 可以继承父构造函数及其原型中的属性和方法
  缺点: 代码复杂

  1. class 关键字继承

  父类:

class Animal {
    constructor(name) {
        this.name = name
    }

    eat() {
        console.log(this.name + '吃吃吃')
    }
}

  子类:

class Cat extends Animal {
    constructor(name) {
        super(name)
    }
}
let cat = new Cat('猫')
console.log(cat.name)     // 猫
cat.eat()                 // 猫吃吃吃

  重点: 子类的 constructor 方法中要调用 super 方法,表示调用父类的 constructor 方法
  优点: 可以继承父类中的属性和方法,代码简单

事件代理

事件代理又叫事件委托,是把原本要监听的事件绑定给父元素,原理是利用了事件冒泡,使用事件委托的优点是:

  1. 节省大量内存,减少时间注册
  2. 新增子节点时无需重新绑定事件

this 指向问题

  1. 普通函数调用时,this 指向 window
  2. 箭头函数中没有 this ,所以 this 会像未声明的变量一样,验证作用域链向上层寻找,即箭头函数中 this 的指向与箭头函数外部环境的 this 指向一致
  3. callapplybind 会改变 this 的指向,指向改为指向方法调用时传入的第一个参数
  4. 其他情况,this 指向函数调用者

new 操作符生成实例的过程

  1. new 关键字会生成一个新的空对象
  2. 将构造函数中的 this 指向这个空对象
  3. 将构造函数中的属性和方法赋给这个空对象
  4. 返回对象并将对象赋值给变量

Ajax 原理

创建 Ajax 过程:

let xhr = new XMLHttpRequest()
xhr.open(method, url, asynchronous)
xhr.send()
xhr.onreadystatechange = function () {
	if (readyState == 4) {
		if (xhr.status == 200) {
			success(xhr.responseText)
		} else {
			fail(xhr.status)
		}
	}
}

readyState:

  0: 请求未初始化
  1: 服务器连接已建立
  2: 请求已接收
  3: 请求处理中
  4: 请求已完成,且响应已就绪

跨域问题的产生和解决

同源策略: 协议名、域名、端口号都相同的两个URL地址叫做同源,非同源的地址间交互会产生跨域。
解决跨域:

  1. 通过 JSONP 解决跨域
  2. 通过 CORS 解决跨域(跨域资源共享)
  3. node.js 中间件代理跨域
  4. WebSocket协议跨域

详细情况:https://segmentfault.com/a/1190000011145364

get和post的区别

传参方式: get 传参会拼接到URL地址后,形式是 ? 后面以 & 分割,post 传参是将参数放到请求体里
数据大小: get 传参有数据大小的限制,一般参照浏览器地址栏支持的最大字节长度,post 传参没有参数数据大小要求
安全性: post 的安全性相对较高。
使用: getpost 都可以用来获取或更新数据,只是一般用 get 来获取数据,post 来存储数据

模块化开发

立即执行函数,不暴露私有成员

let module = (() => {
    let num = 0
    let f1 = () => {}
    let f2 = () => {}
    return { f1, f2 }
})()

异步加载 JS 的方法

  1. defer(只支持 IE):并行加载 js 文件,会按照页面上 script 标签的位置执行脚本
  2. async :并行加载 js 文件,加载完成后立即执行脚本
  3. 动态创建 script

defer 和 async 的异同点

共同点

  1. 不会阻塞文档元素的加载
  2. 使用这两个属性的脚本中不能使用 document.write
  3. 允许不定义属性值,直接使用属性值
  4. 只适用于外部脚本

offsetWidth/offsetHeight、clientWidth/clientHeight、scrollWidth/scrollHeight 及相关属性的区别

元素视图属性

  1. offsetWidth :元素 content 宽度 + 左右 border + 左右 padding
  2. offsetHeight :元素 content 高度 + 上下 border + 上下 padding
  3. clientWidth :元素 content 宽度 + 左右 padding
  4. clientHeight :元素 content 高度 + 上下 padding
  5. scrollWidth :元素内容真实宽度,内容不超出盒子宽度时为 clientWidth
  6. scrollHeight :元素内容真实高度,内容不超出盒子宽度时为 clientHeight

window 视图属性

  1. innerWidth :浏览器窗口可视区宽度
  2. innerHeight :浏览器窗口可视区高度

Promise

Promise 主要用于异步操作,可以将异步操作队列化。新建实例时,传入一个无名函数并且具有两个参数;参数一是成功后调用的函数名,函数体当做参数传入 then() 中,参数二是失败后调用的函数名,函数体当做参数传入 catch()

let promise = new Promise((resolve, reject) => {
	if (true) {
		resolve('成功')
	} else {
		reject('失败')
	}
})

promise.then(res => console.log(res)).catch(err => console.log(err))

JS 数据类型

基本数据类型: NumberStringBooleanNullUndefined ----- 不能拥有属性和方法
引用数据类型: FunctionArrayObject ----- 拥有属性和方法(对象)
基本包装类型: NumberStringBooleanNullUndefined ----- 属于特殊的引用类型,与基本类型对应

每当声明一个基本数据类型(NumberStringBoolean)的变量,后台都会声明一个同名的与之对应的引用类型(基本包装类型)的变量,从而使基本类型的变量可以调用一些属性和方法

如果声明的是基本类型(NumberStringBoolean),此时在实例上新增的属性或方法只存在一瞬间(一行代码内);
如果声明的是引用类型(NumberStringBoolean),此时在实例上新增的属性或方法长久存在;

例:

// 声明基本类型,代码                       // 后台对应代码
const str1 = 'hello world'               // const str1 = new String('hello world')
str1.color = 'red'                       // str1.color = 'red'
                                         // str1.color = null      (属性消除)
console.log(str1.color)                  // undefined

// 声明引用类型,代码                       // 后台对应代码
const str2 = new String('hello world')   // const str2 = new String('hello world')
str2.color = 'red'                       // str2.color = 'red'
console.log(str2.color)                  // red

JavaScript 基本规范

  1. 不要在同一行声明多个变量
  2. 请使用 === / !== 来比较 true / false 或者数值
  3. 请使用字面量方式代替 new Array 这种形式
  4. 不要使用全局函数
  5. switch 语句必须要有 default 分支
  6. if 语句必须有大括号
  7. for-in 中的变量,应该用 let / var 限定作用域

null 和 undefined 的区别

undefined 是声明了变量但没有赋值,null 是声明了变量并且赋值为 null
判断 undefinednull 时,必须用 === / !== ,因为 ==!= 无法区分 undefinednull

严格模式

在 js 脚本开头使用 use strict

严格模式的限制:

  1. 变量必须先声明,再使用
  2. 函数的参数不能有同名属性,否则报错
  3. 不能使用 with
  4. 禁止 this 指向全局变量

attribute 和 property 的区别

attribute 是 DOM 元素在 html 文档中作为标签拥有的属性
property 是 DOM 元素在 JS 中作为对象拥有的属性
对于标准属性来说,attributeproperty 是同步的,但是自定义属性不同步

let 、var 、const 的区别

  1. var 声明的变量会挂载到 window 对象上,而 letconst 不会
  2. letconst 会生成块级作用域
  3. letconst 不允许变量提升,必须先声明,后使用
  4. letconst 不允许重复声明,同意作用域下,只能声明一次
  5. const 声明后必须赋值,且不能改变

vue 双向数据绑定原理

Vue 是采用数据劫持结合订阅者-发布者模式,通过 Object.defineProperty() 来劫持各个属性的 settergetter ,数据变动时发布消息给订阅者,触发响应的监听回调

快速让数组乱序

let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
arr.sort(() => Math.random() - 0.5)
console.log(arr)

如何渲染几万条数据并不卡主界面

不能一次性将几万条数据都渲染出来,可以分批渲染,每次规定渲染条数
利用 window.requestAnimationFrame 来进行页面刷新


<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Documenttitle>
head>

<body>
  <ol id="olObj">ol>
body>

<script>
  let total = 10000
  let each = 23
  let loopCount = Math.ceil(total / each)
  let count = 0
  const olObj = document.getElementById('olObj')

  function appendObj() {
    let fragment = document.createDocumentFragment()
    for (let i = 0; i < each; i++) {
      let totalObj = count * each + i + 1
      if (totalObj > total) {
        break
      }
      const liObj = document.createElement('li')
      liObj.innerHTML = 'item' + totalObj
      fragment.appendChild(liObj)
    }
    olObj.appendChild(fragment)
    count++
    loop()
  }

  function loop() {
    if (count < loopCount) {
      window.requestAnimationFrame(appendObj)
    }
  }

  loop()
script>

html>

window.requestAnimationFrame

   window.requestAnimationFrame() 告诉浏览器,希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

document.createDocumentFragment

   createDocumentFragment() 方法,是用来创建一个虚拟的节点对象,或者说,是用来创建文档碎片节点。它可以包含各种类型的节点,在创建之初是空的。
   DocumentFragment 节点不属于文档树,继承的 parentNode 属性总是 null 。它有一个很实用的特点,当请求把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 自身,而是它的所有子孙节点,即插入的是括号里的节点。这个特性使得 DocumentFragment 成了占位符,暂时存放那些一次插入文档的节点。它还有利于实现文档的剪切、复制和粘贴操作。 另外,当需要添加多个 dom 元素时,如果先将这些元素添加到 DocumentFragment 中,再统一将 DocumentFragment 添加到页面,会减少页面渲染 dom 的次数,效率会明显提升。
   如果使用 appendChid 方法将原 dom 树中的节点添加到 DocumentFragment 中时,会删除原来的节点。

原生 JS 操作 DOM 节点

创建新节点

  1. createElement() 传入标签名称,创建元素节点
  2. createTextNode() 创建文本节点
  3. createDocumentFragment() 创建一个空的 DOM 片段

添加、移除、替换、插入

  1. appendChild() 末尾添加节点
  2. removeChild() 移除节点,传入要被移除的节点,返回被移除的节点
  3. insertBefore() 插入节点,传入新插入的节点和在哪个节点之前插入
  4. replaceChild() 替换节点,传入要被替换的节点

查找

  1. getElementById()
  2. getElementsByClssName()
  3. getElementsByTagName()
  4. getElementsByName()
  5. querySelector()
  6. querySelectorAll()

你可能感兴趣的:(前端面试题)