Amadeus's blog Amadeus's blog
首页
  • 前端文章

    • JavaScript
    • Vue
    • TypeScript
    • 前端工程化
  • 学习笔记

    • 《JavaScript教程》笔记
    • 《ES6 教程》笔记
    • 《Vue》笔记
    • 《TypeScript 从零实现 axios》
    • 小程序笔记
  • HTML
  • CSS
  • stylus
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 口语
  • 音标
  • 语法
  • 简单
  • 中等
  • 困难
  • 20年10月
  • 20年11月
  • 20年12月
  • 21年01月
  • 21年02月
  • 21年03月
  • 21年04月
  • 21年05月
  • 21年06月
  • 21年07月
  • 21年08月
  • 21年09月
  • 21年10月
  • 21年11月
  • 21年12月
  • 22年01月
  • 22年02月
  • 22年03月
  • 22年04月
  • 22年05月
  • 22年06月
  • 22年07月
  • 22年08月
  • 22年09月
  • 21年3月
  • 知识笔记
  • 22年5月
  • 22年8月
  • 22年9月
  • 学习
  • 书法
  • 面试
  • 音乐
  • 驾照
  • 深度强化学习
  • 心情杂货
  • 友情链接
关于
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Amadeus

起风了,唯有努力生存!
首页
  • 前端文章

    • JavaScript
    • Vue
    • TypeScript
    • 前端工程化
  • 学习笔记

    • 《JavaScript教程》笔记
    • 《ES6 教程》笔记
    • 《Vue》笔记
    • 《TypeScript 从零实现 axios》
    • 小程序笔记
  • HTML
  • CSS
  • stylus
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 口语
  • 音标
  • 语法
  • 简单
  • 中等
  • 困难
  • 20年10月
  • 20年11月
  • 20年12月
  • 21年01月
  • 21年02月
  • 21年03月
  • 21年04月
  • 21年05月
  • 21年06月
  • 21年07月
  • 21年08月
  • 21年09月
  • 21年10月
  • 21年11月
  • 21年12月
  • 22年01月
  • 22年02月
  • 22年03月
  • 22年04月
  • 22年05月
  • 22年06月
  • 22年07月
  • 22年08月
  • 22年09月
  • 21年3月
  • 知识笔记
  • 22年5月
  • 22年8月
  • 22年9月
  • 学习
  • 书法
  • 面试
  • 音乐
  • 驾照
  • 深度强化学习
  • 心情杂货
  • 友情链接
关于
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • TypeScript笔记

  • 前端工程化

  • 面试

    • 简历
    • 面试
    • 前端体系
    • 八股文
    • HTML5面试题
    • CSS3面试题
    • JavaScript基础面试题
    • JavaScript高级面试题
      • 前端工程化面试题
      • Vue2面试题
      • Vue全家桶
    • 小程序

    • Vue3源码解析

    • 设计模式

    • NestJS笔记

    • JavaScript文章

    • Vue文章

    • 学习笔记

    • 前端
    • 面试
    Amadeus
    2023-02-04
    目录

    JavaScript高级面试题

    # JavaScript高级面试题

    # 1.this绑定

    绑定规则

    1. 默认绑定:独⽴函数调⽤,函数没有被绑定到某个对象上进⾏调⽤,严格模式(设置了’use strict’)下指向undefined,默认指向window。
    2. 隐式绑定:通过某个对象发起的函数调⽤,在调⽤对象内部有⼀个对函数的引⽤。
    3. 显式绑定:明确this指向的对象,第⼀个参数相同并要求传⼊⼀个对象。
      • apply/call
      • bind
    4. new绑定:
      • 创建⼀个全新对象
      • 新对象被执⾏prototype链接
      • 新对象绑定到函数调⽤的this
      • 如果函数没有返回其他对象,表达式会返回这个对象

    # 2.作用域

    ​ 变量提升、函数提升

    ​ GO\AO\VO

    ​ 作用域和作用域链

    ​ 闭包

    # 3.数组

    # 3.1 常用的数组操作

    Array.shift() 删除第一个元素并返回

    pop() 删除最后一个元素并返回

    push() 在尾部插入元素

    unshift() 在头部插入元素

    join()

    reverse() 反转数组

    # 3.2 数组降维

    some()

    concat()

    flat()

    # 3.3 数组去重

    Set去重(ES6) Array.from(new Set([]))

    两层for加splice去重(ES5)

    indexOf去重,放入新数组,放入前判断是否已存在

    sort去重,排完序,与前面不相等的就是不重复的,放入新数组

    includes,与indexOf思路相同

    filter

    # 3.4 数组中for Each和map的区别

    它们之间的区别:

    • map速度⽐forEach快
    • map会返回⼀个新数组,不对原数组产⽣影响, foreach不会产⽣新数组,forEach返回undefined
    • map因为返回数组所以可以链式操作,forEach不能
    • map⾥可以⽤return(return的是什么,相当于把数组中的这⼀项变为什么(并不影响原来的数组,只是相当于把原数组克隆⼀份,把克隆的这⼀份的数组中的对应项改变了) 。
    • forEach⾥⽤return不起作⽤,forEach不能⽤break,会直接报错。

    # 3.5 for in和for of的区别?

    它们的区别:

    • for...in可以遍历对象和数组,for...of不能遍历对象
    • for...in 循环不仅遍历对象的键名,还会遍历⼿动添加的其它键,甚⾄包括原型链上的键
    • for...in遍历的索引为字符串类型
    • for..of适⽤遍历数/数组对象/字符串/map/set等拥有迭代器对象的集合,但是不能遍历对
    • for...of与forEach()不同的是,它可以正确响应break、continue和return语句
    • 具有迭代器对象才可以使⽤for...of

    for in 遍历出的对象是键,for of 遍历出的对象是值

    for in 用于可枚举数据,如对象、数组、字符串。

    for of 用于可迭代数据,如数组、字符串、Map、Set。

    迭代器:function* iter(){yield 10}

    # 3.6 for await ... of

    用于Promise的遍历

    # 4.函数

    # 4.1 apply、call、bind函数的⽤法和区别?

    • call、apply和bind都可以改变函数的this指向
    • call、apply和bind第⼀个参数的是this要指向的对象
    • call、apply和bind都可以后续为函数传参,apply是将参数并成⼀个数组,call和bind是将参数依次列出
    • call、apply都是直接调⽤,bind⽣成的this指向改变函数需要⼿动调⽤。

    # 4.2 什么是纯函数?如何编写纯函数?

    纯函数:纯函数⼀般具有以下的特点:

    • 确定的输⼊⼀定会有确定的输出(外部环境的任何变化不会影响函数内部的操作产⽣的结果)
    • 纯函数的执⾏不会产⽣副作⽤。(函数内部的操作也不会对函数外部产⽣任何影响)

    纯函数在react和redux中应⽤⽐较多。

    # 4.3 什么是函数柯里化?柯里化有什么作用

    # 4.4 组合函数及组合函数的作用

    # 4.5 说说你对严格模式的理解

    严格模式下的语法限制:

    • 不允许意外创建全局变量(不写var、let、const这种声明变量的关键字)
    • 会对静默失败的赋值操作抛出异常
    • 试图删除不可删除的属性
    • 不允许函数参数有相同的名称
    • 不允许只有0开头的⼋进制语法
    • 不允许使⽤with
    • ⽆法获取eval中定义的变量
    • this绑定不会默认转成对象

    # 4.6 箭头函数不适用的场景

    箭头函数跟普通函数相比,没有arguments对象,以及this指向外层函数中的this,而不是指向当前调用对象。

    场景一: 因为this指向对象中的方法不能用箭头函数。

    场景二:Vue中因为this问题,不能再methods中使用,生命周期等

    Vue组件本质是JS对象,React组件本质是ES6 Class对象。在Class中,箭头函数可以指向对象实例。因此箭头函数可以在React中使用而不能再Vue中使用

    # 5.浏览器和v8引擎

    # 5.1 浏览器内核是什么?有哪些常⻅的浏览器内核?

    内核是浏览器渲染引擎,是浏览器最核心部分。负责解析网页语法并渲染。

    常见浏览器内核

    • trident(三叉戟)---- IE浏览器、360安全浏览器、UC浏览器、搜狗⾼速浏览器、百度浏览器
    • gecko(壁⻁) ---- Mozilla、Firefox
    • pestro -> Blink ---- Opera
    • Webkit ---- Safari、360极速浏览器、搜狗⾼速浏览器、移动端浏览器
    • Webkit -> Blink ----Chrome、Edge

    # 5.2 说出浏览器输⼊⼀个URL到⻚⾯显示的过程?

    URL->DNS->TCP->HTTP->服务器响应->浏览器解析并渲染->HTTP请求结束,TCP断开连接

    # 5.3 说说你对JS引擎的理解

    浏览器内核中有两种引擎,其中⼀种就是 JS 引擎

    • 排版引擎:负责 HTML 和 CSS 解析和排版
    • JS 引擎:负责解析和运⾏ JavaScript 语句

    常见JS引擎有

    • SpiderMonkey -> 第⼀款 JavaScript 引擎,Brendan Eich 开发
    • Chakra -> 微软开发
    • WebKit -> JavaScriptCore -> APPLE 开发
    • Chrome -> V8 -> GOOGLE 开发
    • ⼩程序 -> JSCore -> 腾讯开发

    # 5.4 说说V8引擎的内存管理

    • JavaScript的内存管理是⾃动的
    • 关于原始数据类型 直接在栈内存中分配
    • 关于复杂数据类型 在堆内存中分配

    # 5.5 V8引擎的垃圾回收器

    常⻅的算法

    1. 引⽤计数(Reference Count)
      • 当⼀个对象有引⽤指向它时 对应的引⽤计数+1
      • 当没有对象指向它时 则为0 此时进⾏回
      • 但是有⼀个严重的问题 - 会产⽣循环引⽤
    2. 标记清除(Mark-Sweep)
      • 核⼼思路: 可达性
      • 有⼀个根对象 从该对象出发 开始引⽤到所⽤到的对象 对于根对象没有引⽤到的对象 认为是不可⽤的对象
      • 对于不可⽤的对象 则进⾏回收
      • 该算法有效的解决了循环引⽤的问题
      • ⽬前V8引擎采⽤的就是该算法

    V8引擎为了优化 在采⽤标记清除的过程中也引⽤了其他的算法

    • 标记整理
      • 和标记清除相似 不同的是回收时 会将保留下来的存储对象整合到连续的内存空间 避免内存碎⽚化
    • 分代收集(Generational Collection)
      • 将内存中的对象分为两组 新的空间 旧的空间
      • 对于⻓期存活的对象 会将该对象从新空间移到旧空间中 同时GC检查次数减少
      • 将新空间分为from和to 对象的GC查找之后从from移动到to空间中 然后to变为from from变为to 循环⼏次 对于依然存在的对象 移动到旧空间中
    • 增量收集(Increment Collection)
      • 如果存在许多对象 则GC试图⼀次性遍历所有的对象 可能会对性能造成⼀定的影响
      • 所以引擎试图将垃圾收集⼯作分成⼏部分 然后这⼏部分逐⼀处理 这样会造成微⼩的延迟⽽不是很⼤的延迟
    • 闲时收集(IdIe-time Collection)
      • GC只会在CPU空闲的时候运⾏ 减少可能对代码执⾏造成的影响

    # 5.6 v8引擎执⾏代码的⼤致流程

    • Parse模块:将JavaScript代码转成AST Tree
    • Ignition: 解释器 将ASTTree 转换为字节码(byte Code)
      • 同时收集TurboFan 优化需要的信息
    • TurboFan: 编译器 将字节码编译为CPU可以直接执⾏的机器码(machine code)
      • 如果某⼀个函数呗被多次调⽤ 则会被标记为热点函数 会经过TurBoFan转换的优化的机器码让CPU执⾏ 提⾼代码性能
      • 如果后续执⾏代码过程中 改函数调⽤时的参数类型发⽣了改变 则会逆向的转成字节码 让CPU执⾏

    v8引擎执⾏流程: ⾸先会编译JavaScript 编译过程分为三步 1 词法分析(scanner)

    • 会将对应的每⼀⾏的代码的字节流分解成有意义的代码块 代码块被称为词法单元(token 进⾏记号化)

    2 语法分析(parser)

    • 将对应的tokens分析成⼀个元素逐级嵌套的树 这个树称之为 抽象语法树(Abstract Syntax Tree AST)
    • 这⾥也有对应的 pre-parser

    3 将AST 通过Ignition解释器转换成对应的字节码(ByteCode) 交给CPU执行同时收集信息

    • 将可优化的信息 通过TurBoFan编译器 编译成更好使⽤的机器码交给CPU执⾏
    • 如果后续代码的参数类型发⽣改变 则会逆优化(Deoptimization)为字节码

    # 5.7 说说线程和进程的区别以及关系

    进程

    • 是cpu 分配资源的最⼩单位;(是能拥有资源和􁇿⽴运⾏的最⼩单位)
    • 计算机已经运⾏的程序,是操作系统管理程序的⼀种⽅式 (官⽅说法)
    • 可以认为启动⼀个应⽤程序,就会默认启动⼀个进程(也可能是多个进程)(个⼈解释)
    • 也可以说进程是线程的容器

    线程

    • 是cpu 调度的最⼩单位;(线程是建⽴在进程的基础上的⼀次程序运⾏单位,⼀个进程中可 以有多个线程)
    • 操作系统能够运⾏运算调度的最⼩单位,通常情况下它被包含在进程中 (官⽅说法)
    • 每⼀个进程中,都会启动⾄少⼀个线程⽤来执⾏程序中的代码,这个线程被称之为主线程

    操作系统的⼯作⽅式

    • 如何做到同时让多个进程同时⼯作?
      • 因为CPU 的运算速度⾮常快, 可以快速的在多个进程之间迅速的切换
      • 当进程中的线程获取到时间⽚时, 就可以快速执⾏我们编写的代码
      • 由于CPU 执⾏速度过于变态, 对于⽤户来说是感受不到这种快速切换的

    # 5.8 JavaScript为什么是单线程

    • 这主要和js的⽤途有关,js是作为浏览器的脚本语⾔,主要是实现⽤户与浏览器的交互,以及操作dom
    • 这决定了它只能是单线程,否则会带来很复杂的同步问题。
    • ⽐如js被设计了多线程,如果有⼀个线程要修改⼀个dom元素,另⼀个线程要删除这个dom元素,此时浏览器就会⼀脸茫然,不知所措。
    • 所以,为了避免复杂性,从⼀诞⽣,JavaScript就是单线程,这已经成了这⻔语⾔的核⼼特征,将来也不会改变。

    # 5.9 浏览器是多进程的

    • 在浏览器中,每打开⼀个tab⻚⾯,其实就是新开了⼀个进程,在这个进程中,还有ui渲染线程,js引擎线程,http请求线程等。
    • 因此浏览器是⼀个多进程的。为了利⽤多核CPU的计算能⼒,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是⼦线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

    # 5.10 什么是重排重绘,如何减少重排重绘

    重排(Reflow):

    • 元素的位置发⽣变动时发⽣重排,也叫回流。此时在关键渲染路径中的 Layout 阶段,计算每⼀个元素在设备视⼝内的确切位置和⼤⼩。当⼀个元素位置发⽣变化时,其⽗元素及其后边的元素位置都可能发⽣变化,代价极⾼。

    重绘(Repaint):

    • 元素的样式发⽣变动,但是位置没有改变。此时在关键渲染路径中的 Paint 阶段,将渲染树中的每个节点转换成屏幕上的实际像素,这⼀步通常称为绘制或栅格化。

    另外,重排必定会造成重绘。以下是避免过多重排重绘的⽅法:

    1. 使⽤ DocumentFragment 进⾏ DOM 操作,不过现在原⽣操作很少也基本上⽤不到
    2. CSS 样式尽量批量修改
    3. 避免使⽤ table 布局
    4. 为元素提前设置好⾼宽,不因多次渲染改变位置

    # 6.面向对象

    # 6.1 什么是原型、原型链?

    原型:

    在JavaScript中,每⼀个对象都会有⼀个属性[[prototype]],这个属性就是对象的原型,这个属性的值也是⼀个对象,是原对象的原型对象。访问对象中属性时,会先在对象⾃身进⾏查找,如果没有找到,那么会去对象的原型对象上查找。

    原型链:

    每个对象都有⾃⼰的原型对象,原型对象也有⾃⼰的原型对象。在访问对象的属性时,会沿着对象⾃身=>⾃身的原型对象=>原型对象的原型对象......这样的链条⼀路查找上去,这条链式结构就叫做原型链。原型链的尽头是Object的原型对象的[[prototype]]属性,值为null。

    # 6.2 如何通过原型链实现继承?

    原型链继承:重写⼦类的显式原型对象,让⼦类的显式原型对象的隐式原型指向⽗类的显式原型对象。

    # 6.3 继承的各个⽅案以及优缺点?

    ⽅案⼀:直接将⽗类的prototype赋值给⼦类的prototype,⽗类和⼦类共享原型对象 缺点:在⼦类原型对象上添加⽅法和属性会影响到⽗类

    ⽅案⼆:通过new操作符创建⼀个新的对象,将这个对象作为⼦类的原型对象(显式原型) 缺点:

    • ⼦类的实例对象继承过来的属性是在原型上的,⽆法打印
    • 没有完美的实现属性的继承(⼦类的实对象可以从⽗类继承属性,也可以拥有⾃⼰的属性)

    ⽅案三:通过new操作符创建⼀个新的对象,将这个对象作为⼦类的原型对象(显式原型),并且在⼦类的内部通过借⽤构造函数的⽅法实现属性的继承

    缺点:⽗类构造函数会被调⽤两次,并且⼦类的实例对象总是有两份相同的属性,⼀份在⾃身,⼀份在其原型对象上

    ⽅案四:让⼦类的原型对象(显式原型)的原型对象(隐式原型)指向⽗类的原型对象(显式原型)

    缺点:存在兼容性问题, __proto__ 属性只有部分浏览器⽀持

    ⽅案五:寄⽣组合式继承(ES5中实现继承的最终⽅案)

    function createObject(o) {
        function F() {}
        F.prototype = o
        return new F()
    }
    function inherit(Subtype, Supertype) {
        Subtype.prototype = createObject(Supertype.prototype)
        Object.defineProperty(Subtype.prototype, "constructor", {
            enumerable: false,
            configurable: true,
            writable: true,
            value: Subtype
        })
    }
    function Person() {}
    function Student() {
    	Person.call(this)
    }
    inherit(Student, Person)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    # 6.4 说说你对⾯向对象多态的理解

    • 当对不同的数据类型执⾏同⼀个操作时, 如果表现出来的⾏为(形态)不⼀样, 那么就是多态的体现
    • 继承也是多态的前提

    # 7 ES6-13

    # 7.1 说说let、const和var的区别?

    1. 作⽤域提升
      • var声明的变量是会进⾏作⽤域提升
      • let、const没有进⾏作⽤域提升,但是会在解析阶段被创建出来
      • let,const具有暂时性死区
    2. 块级作⽤域
      • var不存在块级作⽤域
      • let和const存在块级作⽤域
    3. 重复声明
      • var允许重复声明变量
      • let和const在同⼀作⽤域不允许重复声明变量
    4. 修改声明的变量
      • let,var 可以修改声明的变量
      • const它表示保存的数据⼀旦被赋值,就不能被修改,但是如果赋值的是引⽤类型,那么可以通过引⽤找到对应的对象,修改对象的内容

    # 7.2 ES6新增特性

    ES6 :

    • 使⽤class⽤来定义类
    • constructor构造器
    • extends实现继承
    • super关键字代表继承的⽗类
    • 对象字⾯量的增强
    • 属性的简写
    • ⽅法的简写
    • 计算属性名
    • 解构
    • let/const的使⽤
    • 不能重复声明变量
    • 不存在作⽤域提升
    • 存在暂时性死区
    • 不添加window
    • 存在块级作⽤域
    • 字符串模板
    • 在模板字符串中,我们可以通过 ${expression} 来嵌⼊动态的内容
    • 标签模板字符串
    • 函数的默认参数
    • 函数的剩余参数
    • 箭头函数
    • 没有显式原型prototype
    • 不绑定this、arguments、super参数
    • 展开语法
    • 在函数调⽤时使⽤;
    • 在数组构造时使⽤;
    • 展开运算符其实是⼀种浅拷⻉
    • 在构建对象字⾯量时,也可以使⽤展开运算符,这个是在ES2018(ES9)中添加的新特性;
    • 规范了⼆进制和⼋进制的写法
    • 新增Symbol
    • Set、WeakSet、Map、WeakMap

    # 8.异步

    # 8.1 说说Promise的作⽤和使⽤⽅法(各个回调的作⽤)

    编辑 (opens new window)
    JavaScript基础面试题
    前端工程化面试题

    ← JavaScript基础面试题 前端工程化面试题→

    最近更新
    01
    最长递增子序列
    04-21
    02
    非递减子序列
    04-21
    03
    全排列
    04-21
    更多文章>
    Theme by Vdoing | Copyright © 2020-2024 Amadeus | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式