2020年前端面试题·三

    技术2023-09-06  104

    2020年前端面试题·三

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

    闭包

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

    闭包的作用

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

    例如:

    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('吃肉..') } 原型链继承 function Dog() {} Dog.prototype = new Animal() let dog = new Dog() console.log(dog.name) // undefined dog.eat() // 吃肉..

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

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

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

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

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

    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 方法   优点: 可以继承父类中的属性和方法,代码简单

    事件代理

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

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

    this 指向问题

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

    new 操作符生成实例的过程

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

    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地址叫做同源,非同源的地址间交互会产生跨域。 解决跨域:

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

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

    get和post的区别

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

    模块化开发

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

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

    异步加载 JS 的方法

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

    defer 和 async 的异同点

    共同点

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

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

    元素视图属性

    offsetWidth :元素 content 宽度 + 左右 border + 左右 paddingoffsetHeight :元素 content 高度 + 上下 border + 上下 paddingclientWidth :元素 content 宽度 + 左右 paddingclientHeight :元素 content 高度 + 上下 paddingscrollWidth :元素内容真实宽度,内容不超出盒子宽度时为 clientWidthscrollHeight :元素内容真实高度,内容不超出盒子宽度时为 clientHeight

    window 视图属性

    innerWidth :浏览器窗口可视区宽度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))

    补充:Promise 有三个状态:进行中、成功后、失败后,且状态一旦从进行中变为成功或失败后,状态就不能再发生变化。所以每次在调用 then() 或 catch() 后,都会返回一个新的实例,以保证 Promise 的链式操作。

    JS 数据类型

    基本数据类型: Number 、String 、Boolean 、Null 、Undefined ----- 不能拥有属性和方法 引用数据类型: Function 、Array 、Object ----- 拥有属性和方法(对象) 基本包装类型: Number 、String 、Boolean 、Null 、Undefined ----- 属于特殊的引用类型,与基本类型对应

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

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

    例:

    // 声明基本类型,代码 // 后台对应代码 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 基本规范

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

    null 和 undefined 的区别

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

    严格模式

    在 js 脚本开头使用 use strict

    严格模式的限制:

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

    attribute 和 property 的区别

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

    let 、var 、const 的区别

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

    vue 双向数据绑定原理

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

    快速让数组乱序

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

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

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

    <!DOCTYPE html> <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>Document</title> </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 节点

    创建新节点

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

    添加、移除、替换、插入

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

    查找

    getElementById()getElementsByClssName()getElementsByTagName()getElementsByName()querySelector()querySelectorAll()

    Processed: 0.009, SQL: 9