前端面试题(ES6框架)

简介: 本文深入讲解ES6核心知识,涵盖Webpack环境搭建、模块化演进、class与构造函数对比、Promise原理实现及Proxy、箭头函数等新特性,结合代码示例解析,助力掌握现代JavaScript开发要点.

1.ES6框架

1.如何搭建ES6的webpack开发环境?

  1. 安装Node环境 node -v // 10.14.1
  2. 安装NPM环境 npm -v // 6.4.1
  3. 安装babel npm install @babel/core babel-preset-es2015 babel-preset-latest
  4. 创建.babelrc文件 npm install babel-cli -g babel --version
    babel ./src/index.js // 测试babel的编译结构
  5. Webpack---模块化工具安装 npm install webpack babel-loader --save-dev // 安装webpack,babel-loader
  6. 配置webpack.config.js
  7. 配置package.json中的scripts
  8. 运行n npm start

1.1 常见错误

0x01产生原因
 babel-loader和babel-core版本不对应所产生的,
 babel-loader 8.x对应babel-core 7.x
 babel-loader 7.x对应babel-core 6.x

1.2 安装环境注意事项

  1. 这里的安装的版本必须是@babel/core,否则会出现版本不兼容的错误
  2. 建议先安装webpack,然后安装loader

2.谈一下JS的模块化?

  1. 最开始的时候JS是没有模块化的
  2. AMD(Async module define)成为标准,require.js(CMD--common-module-define)
  3. 前端打包工具,使得nodejs模块化可以被使用 (Grunt, Gulp, Webpack……)
  4. ES6的出现,想统一现在所有的模块化标准
  5. nodejs(服务器端)积极支持,浏览器(需要打包为ES5/4)尚未统一
  6. 可以自造库lib,但是不要自造标准

2.1 rollup的使用

  1. 功能单一,可集成,可扩展
  2. rollup + gulp的结合
  • 安装 npm install rollup rollup-plugin-node-resolve roll-up-plugin-babel babel-plugin-external-helpers babel-preset-latest --save-dev
  • 配置 .babelrc
  • 配置 rollup.config.js
  • 修改 package.json scripts
  • 运行命令:npm start

2.2 rollup特点

  • rollup功能单一,webpack功能强大
  • 参考设计原则和《Linux/Unix设计思想》
  • 工具要尽量功能单一,可以继集成,可扩展
  • 可以使用gulp + rollup(主流框架VUE和React使用的就是rollup进行模块化的)

3.class和JS普通构造函数的区别?

console.log(typeof MathHandle, typeof  MathHandle.prototype.constructor);   // function function
// 1. 构造函数的原型里面默认会有一个constructor属性,这个属性的值等于这个构造函数本身
console.log(MathHandle === MathHandle.prototype.constructor)        // true
console.log(MathHandle.prototype);
// 2. 所有实例的隐式原型__proto__和这个构造函数的显示原型prototype是相同的
console.log(m.__proto__ === MathHandle.prototype);      // true

console.log('-------------------------------------------------------------')
console.log(typeof MathHandler, typeof  MathHandler.prototype.constructor)         // 'function'
console.log(MathHandler === MathHandler.prototype.constructor)  // true
console.log(MathHandler.prototype);
console.log(mm.__proto__=== MathHandler.prototype);

Note

  1. class 在语法上更加贴合面向对象的写法
  1. class 实现继承更加易读,易理解
  2. 更加易于些java等后端语言的使用
  3. class【本质】还是语法糖,实际上还是使用的是prototype

4. 写实现一个Promise?

先看看Promise是怎么用的

new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 0)
}).then(value => {
  console.log(value)
})

在Promise的构造器中传入一个函数,这个函数有两个参数 resolve和reject,这两个参数都是Promise的回调函数,不需要自己写,在需要的时候调用就可以了,他们分别是成功的回调resolve和失败的回调reject。

4.1 简易版 Promise

第一步,先来搭建构建函数的大体框架

const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

function MyPromise(fn){
  const that = this
  that.state = PENDING
  that.value = null
  that.resolvedCallbacks = []
  that.rejectedCallbacks = []
  // 待完善 resolve 和 reject 函数
  // 待完善执行 fn 函数
}
  • 首先创建三个常量用于表示状态,对于经常使用的一些值应该通过常量来管理,便于开发及后期维护
  • 在函数体内部首先创建常量that,因为代码可能会异步执行,用于获取正确的this对象
  • 一开始Promise的状态是 pending
  • value 变量用于保存resolve或者reject中传入的值
  • resolvedCallbacks和rejectedCallback用于保存then中的回调,因为当执行完Promise时状态可能还是等待中,这时候应该把then中的回调保存起来用于状态改变时使用

第二步,完善resolve和reject函数,添加在 MyPromise 函数体内部

function resolve(value) {
  if(that.state === PENDING) {
    that.state = RESOLVED
    that.value = value
    that.resolvedCallbacks.map(cb => cb(that.value))
  }
}

function reject(value) {
  if(that.state === PENDING){
    that.state = REJECTED
    that.value = value;
    that.rejectedCallbacks.map(cb => cb(that.value));
  }
}
  • 首先两个函数都得判断当前状态是否为等待中,因为规范规定只有等待态才可以改变状态
  • 将当前状态更改为对应状态,并且将传入的值赋值给 value
  • 遍历回调数组并执行

第四步,实现如何执行 Promise 中传入的函数了

try {
  fn(resolve, reject)
} catch (e) {
  reject(e)
}
  • 实现很简单,执行传入的参数并且将之前两个函数当做参数传进去
  • 要注意的是,可能执行函数过程中会遇到错误,需要捕获错误并且执行 reject 函数

第五步,实现较为复杂的 then 函数。

then函数是在Promise构造器中成功状态下调用的resolve方法的回调。

then函数是可以接收两个参数的,一个是用户自定义的成功处理,另一个是用户自定义的错误处理,第二个参数可不传

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  const that = this
  //对传入的两个参数做判断,如果不是函数将其转为函数
  onFulfilled =
    typeof onFulfilled === 'function'
    ? onFulfilled
    : v => v  // onFulfilled = v => v
  onRejected =
    typeof onRejected === 'function'
    ? onRejected
    : r => {
      throw r
    }

  if(that.state === PENDING) {
    that.resolvedCallbacks.push(onFulfilled)
    that.rejectedCallbacks.push(onRejected)
  }
  else if(that.state === RESOLVED) {
    onFulfilled(that.value)
  }
  else {
    onRejected(that.value)
  }
}
  • 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
  • 当参数不是函数类型时,需要创建一个函数赋值给对应的参数,同时也实现了透传,比如如下代码
// 该代码目前在简单版中会报错
// 只是作为一个透传的例子
Promise.resolve(4).then().then((value) => console.log(value))
  • 接下来就是一系列判断状态的逻辑,当状态不是等待态时,就去执行相对应的函数。如果状态是等待态的话,就往回调函数中 push 函数,比如如下代码就会进入等待态的逻辑

5.ES6中常用的功能有哪些?

  1. let/const
  2. 多行字符串/模板变量
  3. 解构赋值
  4. 块级作用域?【重点理解】
  5. 函数默认参数
  6. 箭头函数(this指向问题)

5.1 var、let 及 const 区别?

  • var声明的变量会挂载在window上,而let和const不会
  • var声明变量存在变量提升,let和const不会
  • let、const 的作用范围是块级作用域,而var的作用范围是函数作用域
  • 同一作用域下let和const不能声明同名变量,而var可以
  • 同一作用域下在let和const声明前使用会存在暂时性死区
  • const
  • 一旦声明必须赋值,不能使用null占位
  • 声明后不能再修改
  • 如果声明的是复合类型数据,可以修改其属性

5.2 Proxy

Note

Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。 Vue3.0 中将会通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式。

let p = new Proxy(target, handler)

target 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。

let onWatch = (obj, setBind, getLogger) => {
  let handler = {
    set(target, property, value, receiver) {
      setBind(value, property)
      return Reflect.set(target, property, value)
    },
    get(target, property, receiver) {
      getLogger(target, property)
      return Reflect.get(target, property, receiver)
    }
  }
  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

Note

自定义 set 和 get 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。

当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要我们在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷可能就是浏览器的兼容性不好了。

5.3 数组方法

5.3.1 map

Note

map 作用是生成一个新数组,遍历原数组,将每个元素拿出来做一些变换然后返回一个新数组,原数组不发生改变。

map 的回调函数接受三个参数,分别是当前索引元素,索引,原数组

var arr = [1,2,3];
var arr2 = arr.map(item => item + 1)    
arr   //[ 1, 2, 3 ]
arr2  // [ 2, 3, 4 ]
['1','2','3'].map(parseInt)
// -> [ 1, NaN, NaN ]
  • 第一个 parseInt('1', 0) -> 1
  • 第二个 parseInt('2', 1) -> NaN
  • 第三个 parseInt('3', 2) -> NaN

5.3.2 filter

Note

filter 的作用也是生成一个新数组,在遍历数组的时候将返回值为 true 的元素放入新数组,我们可以利用这个函数删除一些不需要的元素

filter 的回调函数接受三个参数,分别是当前索引元素,索引,原数组

5.3.3 reduce

Note

reduce 可以将数组中的元素通过回调函数最终转换为一个值。

如果我们想实现一个功能将函数里的元素全部相加得到一个值,可能会这样写代码

const arr = [1, 2, 3]
let total = 0
for (let i = 0; i < arr.length; i++) {
  total += arr[i]
}
console.log(total) //6

但是如果我们使用 reduce 的话就可以将遍历部分的代码优化为一行代码

const arr = [1, 2, 3]
const sum = arr.reduce((acc, current) => acc + current, 0)
console.log(sum)

对于 reduce 来说,它接受两个参数,分别是回调函数和初始值,接下来我们来分解上述代码中 reduce 的过程

  • 首先初始值为 0,该值会在执行第一次回调函数时作为第一个参数传入
  • 回调函数接受四个参数,分别为累计值、当前元素、当前索引、原数组,后三者想必大家都可以明白作用,这里着重分析第一个参数
  • 在一次执行回调函数时,当前值和初始值相加得出结果 1,该结果会在第二次执行回调函数时当做第一个参数传入
  • 所以在第二次执行回调函数时,相加的值就分别是 1 和 2,以此类推,循环结束后得到结果 6。

5.4 Es6中箭头函数与普通函数的区别?

  • 普通function的声明在变量提升中是最高的,箭头函数没有函数提升
  • 箭头函数没有属于自己的this,arguments
  • 箭头函数不能作为构造函数,不能被new,没有property
  • call和apply方法只有参数,没有作用域

5.5 Promise

Promise 翻译过来就是承诺的意思,这个承诺会在未来有一个确切的答复,并且该承诺有三种状态,这个承诺一旦从等待状态变成为其他状态就永远不能更改状态了。

  • 等待中(pending)
  • 完成了(resolved)
  • 拒绝了(rejected)

当我们在构造 Promise 的时候,构造函数内部的代码是立即执行的。

new Promise((resolve, reject) => {
  console.log('new Promise')
  resolve('success')
})
console.log('finifsh')

// 先打印new Promise, 再打印 finifsh

Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,并且是一个全新的 Promise,原因也是因为状态不可变。如果你在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装。

Promise.resolve(1)
  .then(res => {
    console.log(res) // => 1
    return 2 // 包装成 Promise.resolve(2)
  })
  .then(res => {
    console.log(res) // => 2
  })

当然了,Promise 也很好地解决了回调地狱的问题

ajax(url)
  .then(res => {
      console.log(res)
      return ajax(url1)
  }).then(res => {
      console.log(res)
      return ajax(url2)
  }).then(res => console.log(res))

6. 箭头函数和普通函数的8点区别?

6.1 不能作为构造函数

箭头函数作为匿名函数,是不能作为构造函数的,不能使用new

6.2 无arguments

箭头函数不绑定arguments,取而代之用rest参数…解决

function A(a){
    console.log(arguments); // 1
}

var BBB = (aaa)=>{
    console.log(arguments); // 2
}

var CCC = (a, ...rest)=>{
    console.log(a, '-----', rest);         // 1, ---- [2 3 4]
}

A(1);
BBB(2);
CCC(1, 2, 3, 4);

6.3 没有自己的this

箭头函数会捕获其所在上下文的 this 值,作为自己的 this 值

var obj = {
    a: 10,
    b: function(){
        console.log(this.a); //10
    },
    c: function() {
        // 在c方法里面return的这个箭头函数捕获的是c:function(){}这个环境的this,而这个环境的this是obj【找出块作用域的外部一层就是他的环境】
        return ()=>{
            console.log(this.a); //10
        }
    }
}
obj.b();
obj.c()();

6.4 this环境

箭头函数当方法使用的时候没有定义this绑定

var obj = {
    a: 10,
    b: () => {
        // 因为箭头函数捕获的是obj{}这个对象的环境,然后这个环境的this指向的是window【找出块作用域的外部一层就是他的环境】
        console.log(this.a); //undefined
        console.log(this); //window
    },
    c: function() {
        console.log(this.a); //10
        console.log(this); //obj{...}
    }
}
obj.b();
obj.c();

6.5 this不可以被修改

使用call()和apply()调用, 通过 call() 或 apply() 方法调用一个函数时,只是传入了参数而已,对 this并没有什么影响

var obj = {
    a: 10,
    b: function(n){
        var f = (v) => v + this.a;
        /*function f(v) {
            return v+this.a;
        }*/
        return f(n);
    },
    c: function(n) {
        var f = (v) => v + this.a;
        var m = {a:20};
        return f.call(m,n);
    }
}

console.log(obj.b(1)); //11
console.log(obj.c(1)); //11
目录
相关文章
|
25天前
|
前端开发 JavaScript 算法
前端面试(React框架一)
React 是一个用于构建用户界面的 JavaScript 库,采用虚拟 DOM 提升渲染效率,支持组件化开发与服务端渲染,具有良好的性能优化机制和丰富的生命周期管理。
84 13
|
24天前
|
测试技术 开发者 Python
Python装饰器:让代码优雅复用的魔法
Python装饰器:让代码优雅复用的魔法
233 135
|
24天前
|
人工智能 监控 机器人
别再往一个智能体里塞功能了:6种多智能体模式技术解析与选型指南
单智能体在功能增多时易陷入“指令迷雾”与“工具过载”,导致失效。本文提出6种多智能体架构模式:顺序流水线、并行扇出、层级监督、路由分发、反思迭代、共识投票,类比团队协作,通过分工提升系统稳定性与扩展性,解决复杂任务下的性能衰减问题。
195 6
别再往一个智能体里塞功能了:6种多智能体模式技术解析与选型指南
|
13天前
|
人工智能 算法 架构师
开源算法引爆GEO行业洗牌:王耀恒预言的“信息营养师”时代正式到来
马斯克宣布开源推荐算法,引爆GEO行业巨变。郑州讲师王耀恒早前预言的“算法祛魅”时代提前到来。虚假排名、AI投毒等灰色手段难以为继,“信息营养师”崛起。企业需重构竞争力:体检GEO健康度、设立伦理委员会、构建知识本体、培养首席信息架构师。透明化时代,唯有真实价值与长期主义才能赢得未来。(238字)
|
8天前
|
机器学习/深度学习 计算机视觉 网络架构
YOLO26改进 - 注意力机制 |融合HCF-Net维度感知选择性整合模块DASI 增强小目标显著性
本文介绍将HCF-Net中的维度感知选择性融合(DASI)模块集成至YOLO26检测头,通过通道分区与Sigmoid自适应加权,融合高/低维及当前层特征,显著提升红外小目标检测精度,在SIRST数据集上超越主流方法。(239字)
|
8天前
|
人工智能 开发框架 IDE
AI 时代的量化革命:10分钟开发你的第一个交易策略
本文手把手教你用AI工具10分钟开发首个量化交易策略:从克隆SDK、启动AI IDE,到生成KDJ_RSI组合策略、配置运行环境并实盘验证。零基础也能快速上手,开启AI驱动的量化投资新范式!
162 17
|
8天前
|
Ubuntu Linux 算法框架/工具
超详细!OFA 视觉问答(VQA)模型部署教学(避坑完整版)
本文详解OFA视觉问答(VQA)模型在ModelScope平台的完整部署教程:涵盖Linux环境搭建、Miniconda虚拟环境配置、严格匹配依赖版本(transformers 4.48.3等)、禁用自动依赖覆盖、输入格式规范及避坑指南(含5类高频问题的现象+原因+解法),附可直接运行的Python脚本,新手友好,开箱即用。
153 15
|
存储 缓存 NoSQL
即将开源 | 阿里云 Tair KVCache Manager:企业级全局 KVCache 管理服务的架构设计与实现
阿里云 Tair 联合团队推出企业级全局 KVCache 管理服务 Tair KVCache Manager,通过中心化元数据管理与多后端存储池化,实现 KVCache 的跨实例共享与智能调度。该服务解耦算力与存储,支持弹性伸缩、多租户隔离及高可用保障,显著提升缓存命中率与资源利用率,重构大模型推理成本模型,支撑智能体时代的规模化推理需求。
|
13天前
|
人工智能 算法 测试技术
全网最完整 GEO 优化落地指南(万字实操版)
GEO是让AI在回答时主动推荐品牌的优化策略。品牌若不被推荐,常因内容未被AI抓取、缺乏信任或未切中用户问题。企业可通过诊断现状、优化结构化内容、精准分发与持续监测五步法系统实施。B2B企业借此能在采购全链路中高效触达客户,短期即可提升AI提及率与销售转化。这将是伴随AI搜索发展的长期趋势,值得尽早布局。