目录
- GIT 5
- git最基本的6个操作 5
- 说说git的分支,合并与冲突 6
- 说说git公司多人协作与开源项目多人协作 6
- 区别fork(复刻)与clone(克隆) 6
- 区别git的pull与fetch 6
- git的几个主要分支 7
- Git常用远程操作命令 7
- webpack 8
- 说说你对项目构建的理解 8
- webpack的核心概念 8
- webpack配置文件的整体结构 8
- webpack模块化打包的基本流程 8
- 比较loader与plugin 9
- JS原生 9
- 区别2种类型的容器 9
- 说说开发中常用的ES6新语法 9
- 说说你对事件处理机制的理解 10
- async/await? 10
- 用数组的相关方法实现以下业务需求 10
- 说说你对cookie的理解(分类, 创建, 保存, 使用) 11
- 内存结构图(原型结构图) 11
- 说说你对变量提升与函数提升的理解 12
- 说说原型链的理解 12
- 说说你对作用域链的理解 13
- 说说你对闭包的理解 13
- JS事件循环机制(图) 14
- 比较函数的call()/apply()/bind() 14
- debug调试 15
- 说说你对回调函数的理解 15
- 详细说明如何判断函数中的this 15
- 区别localStorage和sessionStorage, session与cookie 16
- 关于2个引用变量指向同一个对象的2个问题 16
- console.log(a.b)的查找流程 16
- React 16
- 说说React的理解 16
- 使用2种方式定义一个简单组件 17
- 区别类组件和函数式组件 17
- 说说组件对象的3大属性 17
- React组件化编码的3步与2个重要问题 18
- 组件的生命周期及勾子 18
- 关于设计state数据的3个问题 18
- 说说react应用中如何与后台通信? 19
- 比较react中组件间3种通信方式 19
- 说说对react-router的理解 19
- 区别UI组件与容器组件 20
- 说说你对redux的理解 20
- redux结构图 20
- redux的基本编码 20
- React组件的二种分类 21
- react性能优化是哪个周期函数? 21
- 为什么虚拟dom会提高性能? 21
- Dom Diff算法 22
- 调用 setState 之后发生了什么? 22
- React 中 keys 的作用是什么? 22
- 概述下 React 中的事件处理逻辑 22
- 传入 setState 函数的第二个参数的作用是什么? 23
- 何为受控组件(controlled component) 23
- 何为高阶组件(higher order component) 23
- 了解 redux么,说一下redux吧 23
- 详细说说redux 23
- Vue 24
- 说一下你对Vue的理解 24
- 说说MVVM设计模式 24
- 说说你对模板的理解 25
- vue的常用配置选项及其作用(5个) 25
- 说说你对计算属性的理解 25
- 说说vue的vm对象/组件对象的生命周期 25
- 写出7个指令及其作用 26
- 说说vue组件间通信的几种方式 26
- 说说vue项目中如何与后台通信 26
- vue-router提供了哪些语法? 27
- 说说你对vuex的理解 27
- 在vue项目中引入vuex的基本流程 27
- vuex结构图 28
- 说说vue数据代理 28
- 说说vue模板解析 28
- 说说数据绑定的理解和基本原理 29
- Vue实现数据双向绑定的原理 29
- Vue的MVVM实现结构图 29
- 模板中显示数据的来源 29
- 说说你项目中使用的better-scroll库 30
- 说说你vue项目开发中碰到的问题, 原因及解决办法 30
- 前台异步显示数据效果的编码的3个部分 31
- 区别v-if与v-show 31
- 区别 r o u t e 和 route和 route和router 31
- 说说你对路由导航卫士的理解 31
- 区别2种路由实现:hash模式 和 history模式 32
- vue与react的比较? 32
- 其它 32
- package.json的结构 32
- 说说你对json数据的理解 33
- 说说你对mock数据的理解 33
- 说说脚手架的理解 33
- 说说项目的开发环境运行与生产环境打包运行 33
- 描述一下你的React项目 34
- 什么是API接口 34
- 说说你对路由的理解 34
- 后台路由回调函数处理的3步 35
- GET请求的2种请求参数 35
- 说说你对commonjs模块化规范的理解 35
- 你对代理的理解 35
- chrome调试应用的常用功能(窗口) 36
一、GIT
1. git最基本的6个操作
-
创建本地仓库
创建.gitignore配置文件 git init git add * git commit -m "xxx"
-
创建远程仓库
New Repository 指定名称 创建
-
将本地仓库推送到远程仓库
git remote add origin https://github.com/zxfjd3g/xxx.git 关联远程仓库 git push origin master
-
如果本地有更新, 推送到远程
git add * git commit -m "xxx" git push origin master
-
如果远程有更新, 拉取到本地
git pull origin master git fetch origin master:tmp
-
克隆远程仓库到本地
git clone https://github.com/zxfjd3g/xxx.git git checkout -b dev origin/dev
2. 说说git的分支,合并与冲突
-
分支是在开发主线之外编写你的代码完成特定工作而不影响开发主线
-
分支的操作:
查看分支: git branch 创建分支: git branch dev 切换分支: git checkout dev 比较分支: git diff master dev 合并分支: git merge dev
-
冲突
-
产生冲突: 当对2个分支进行合并时, 如果同一个文件的同一行内容不一样, 就会产生冲突
-
解决冲突:
修正合并后产生冲突的代码 git add * git commit -m "resolve conflict"
-
3. 说说git公司多人协作与开源项目多人协作
- 公司多人协作: 先在github上创建组织(修改权限), 多个同事加入此组织, 在组织下创建项目, 成员都可以进行推送更新
- 开源多人协作: fork仓库到自己的账户下, 修改fork仓库的代码, 向原仓库发起一个pull request, 对方接收到请求后可以选择合并
4. 区别fork(复刻)与clone(克隆)
- fork: 将别人的远程仓库完全复制为自己的远程仓库
- clone: 将远程仓库(自己/别人)完全复制为本地仓库
5. 区别git的pull与fetch
- 简单来说,git pull 是 git fetch + git merge
- 执行pull: 将远程仓库的更新拉取到本地后, 与本地仓库当前分支进行自动合并(可能会遇到冲突)
- 执行fetch: 将远程仓库的更新拉取到一个暂时的新分支上, 后面需要手动进行merge操作(可以先进行diff操作)
6. git的几个主要分支
-
master分支
主分支,产品的功能全部实现后,最终在master分支对外发布。
-
develop分支
开发分支,基于master分支克隆,产品的编码工作在此分支进行。
-
release分支
测试分支,基于delevop分支克隆,产品编码工作完成后,发布到本分支测试, 测试过程中发现的小bug直接在本分支进行修复,修复完成后合并到develop分支。 本分支属于临时分支,目的实现后可删除分支。
-
bugfix/hotfix分支
Bug修复分支,基于master分支或发布的里程碑Tag克隆, 主要用于修复对外发布的分支,收到客户的Bug反馈后, 在此分支进行修复,修复完毕后分别合并到develop分支和master分支。 本分支属于临时分支,目的实现后可删除分支。
-
feature分支
功能特征分支,基于develop分支克隆, 主要用于多人协助开发场景或探索性功能验证场景,功能开发完毕后合并到develop分支。 feature分支可创建多个,属于临时分支,目的实现后可删除分支。
7. Git常用远程操作命令
git clone: 克隆远程仓库到本地
git remote: 添加/删除远程仓库关联
git push: 将本地仓库推送到远程仓库
git pull: 从远程仓库更新拉取到本地仓库并与当前分支合并
git fetch: 从远程仓库更新拉取到本地仓库的临时新分支
二、模块化与项目构建
1. 谈谈你对js模块化的理解
-
当工业级的项目开发的足够大的时候,如果将所有的js代码定义在一个js文件的话,使得复杂度提升,后期维护难度加大。
-
如果将一个大的js文件根据一定的规范拆分成几个小的文件的话将会便于管理,可以提高复用性。
-
模块化在项目中十分的重要,一个复杂的项目肯定有很多相似的功能模块,如果每次都需要重新编写模块肯定既费时又耗力。但是引用别人编写模块的前提是要有统一的“打开姿势”,如果每个人有各自的写法,那么肯定会乱套,所有会引出模块化规范的使用
-
常用的JavaScript模块化规范有四种:
Commonjs, AMD(require.js), CMD(sea.js), ES6模块化
2. 区别CommonJS和ES6模块化
-
CommonJS是服务器端模块的规范,Node.js采用了这个规范。但目前也可用于浏览器端,需要使用Browserify/webpack进行提前编译打包。
-
ES6模块化规范专门针对于浏览器端,但目前浏览器支持不是很好(只有Chrome浏览器支持), 也需要使用Browserify进行打包。
-
暴露的方式和暴露的本质:
①commonjs暴露的方式
- module.exports = value;
- exports.xxx = value;
- 暴露的本质是exports对象
②ES6中暴露的方式
- export xxx (常规暴露,暴露的本质是对象,接收的时候只能以对象的解构赋值的方式来接收值)
- export default (默认暴露,暴露任意数据类型,暴露什么数据类型,接收什么数据类型)
3. 说说你对项目构建的理解
-
构建项目到底做些什么
编译项目中的js, sass, less, stylus 合并js/css等资源文件 压缩js/css/html等资源文件 JS语法的检查
-
构建工具
作用: 简化项目构建, 实现自动化构建 常用: grunt/gulp/webpack
4. 区别webpack与gulp/grunt
- webpack是一种模块化打包工具,主要用于模块化方案,预编译模块的方案。
- Grunt/Gulp是非模块化打包工具, 是工具链,可以配合各种插件做js压缩,css压缩,less编译 替代手工实现自动化工作
5. webpack的核心概念
- Entry:入口,Webpack进行打包的起始点(文件)
- Output:出口,webpack编译打包生成的bundle(文件)
- Loader:模块加载(转换)器,将非js模块包装成webpack能理解的js模块
- Plugin:插件,在 Webpack 构建流程中的特定时机插入具有特定功能的代码
- Module:模块,在 Webpack眼里一切皆模块,默认只识别js文件, 如果是其它类型文件利用对应的loader转换为js模块
- Chunk/bundle:代码块/束,一个 Chunk 由多个模块组合而成, 最终浏览器执行的是webpack打包生成的chunk文件
6. webpack配置文件的整体结构
module.exports = {
entry: '',
output: {},
module: {rules: []},
plugins: []
}
7. webpack模块化打包的基本流程
(1) 连接: webpack从入口JS开始, 递归查找出所有相关联的模块, 并连接
起来形成一个图(网)的结构
(2) 编译: 将JS模块中的模块化语法编译
为浏览器可以直接运行的模块语法(当然其它类型资源也会处理)
(3) 合并: 将图中所有编译过的模块合并
成一个或少量几个bundle文件, 而浏览器运行是打包生成的bundle文件
8. 比较loader与plugin
- loader: 用于加载特定类型的资源文件, webpack本身只能打包js, 如果打包css就需要css-loader/style-loader, 如果打包图片就需要file-loader
- plugin: 用来扩展webpack其它方面的功能, 如生成页面需要html-webpack-plugin, 压缩js需要uglifyjs-webpack-plugin
9. webpack打包后文件体积过大怎么办
- 异步加载模块(代码分割);
- 提取第三方库(使用cdn或者vender);
- 代码压缩;
- 去除不必要的插件;
- 去除devtool选项等等
10. webpack的Tree-shaking是什么?
Webpack在打包js模块时, 会将模块中向外暴露但没有被使用的功能从打包文件中移除。
2个前提:
- 必须是使用ES6的export向外暴露的
- 必须进行JS的压缩处理
11. 区别live-reload与hot-realod/HMR
相同点: 代码修改后都会自动重新编译打包。
不同点:
- live-reload: 刷新整体页面, 从而查看到最新代码的效果, 页面状态全部都是新的。
- Hot-reload: 没有刷新整个页面, 只是加载了修改模块的打包文件并运行,
从而更新页面的局部界面, 整个界面的其它部分的状态还在。
三、JS原生
1. 区别2种类型的容器
- 数组容器: 用来保存有序的多个数据(如列表数据), 数据的标识是下标, 我们通过下标来操作对应的数据
- 对象容器: 用来保存无序的多个数据, 多个数据的标识是属性名, 我们通过属性名来操作属性值数据
2. 说说开发中常用的ES6新语法
-
定义变量/常量: const/let
-
解构赋值:
let {a, b} = this.props import {aa} from 'xxx' function f ({name}) {}
-
对象的简洁表达: {a, b, c () {}}
-
箭头函数:
- 组件的自定义方法: xxx = () => {}
- 匿名函数作为实参
- 优点:
- 简洁
- 没有自己的this,使用引用this查找的是外部this
-
扩展运算符: …
-
类: class/extends/constructor/super
-
ES6模块化: export/default/import
-
异步: promise, async/await
3. 说说你对事件处理机制的理解
DOM事件
- 绑定事件监听
- 事件名(类型): 只有有限的几个, 不能随便写
- 回调函数
- 用户操作触发事件(event)
- 事件名(类型)
- 数据
自定义事件
- 绑定事件监听
- 事件名(类型): 任意
- 回调函数: 通过形参接收数据, 在函数体处理事件
- **触发(emit)/分发(dispatch)**事件(编码)
- 事件名(类型): 与绑定的事件监听的事件名一致
- 数据: 会自动传递给回调函数
4. async/await?
作用?
- 简化pormise的使用(不用再使用then()来指定成功或失败的回调函数)
- 以同步编码的方式实现异步流程(没有回调函数)
哪里使用await?
- 在返回promise对象的表达式左侧, 为了直接得到异步返回的结果, 而不是promsie对象
哪里使用async?
- 使用了await的函数定义左侧
5. 用数组的相关方法实现以下业务需求
公司前后招聘了10个员工(性别,年龄, 月薪各不相同),有以下需求
1). 列表显示所有员工的所有信息 forEach
2). 对员工进行年薪降序列表显示 sort()
3). 得到男员工的总月薪: reduce()
4). 查找一个月薪只比12000高一点点的员工: find()
5). 查找出所有月薪高于12000的员工: filter()
6). 列表显示所有员工的姓名/性别和年薪: map()
const employees = [
{name: 'A', sex: '男', age: 21, salary: 10000},
{name: 'B', sex: '女', age: 25, salary: 12000},
{name: 'C', sex: '男', age: 24, salary: 13000},
{name: 'D', sex: '男', age: 24, salary: 12500},
{name: 'E', sex: '女', age: 21, salary: 14000},
{name: 'F', sex: '男', age: 24, salary: 16000},
{name: 'G', sex: '男', age: 23, salary: 9000},
{name: 'H', sex: '女', age: 21, salary: 11000},
{name: 'I', sex: '男', age: 23, salary: 13200},
{name: 'J', sex: '男', age: 23, salary: 15000}
]
employees.forEach(e => console.log(e))
employees.sort((e1, e2) => e2.salary-e1.salary)
employees.reduce((preTotal, e) => preTotal + (e.sex=='男'?e.salary:0), 0)
employees.find(e => e.salary>1200 && e.salary<1400 && e.sex==='男')
employees.filter(e => e.salary>1200)
employees.map(e => ({'姓名/性别': `${e.name}/${e.sex}`, '年薪': e.salary*12}))
6. 说说你对cookie的理解(分类, 创建, 保存, 使用)
-
概念:cookie是由key和value组成的文本小数据。
-
分类: 会话cookie和持久化cookie。
-
创建:由服务器端创建: res.cookie(key, value, {maxAge: 1000})
-
保存:由浏览器端保存,浏览器接收到新的cookie会自动保存(内存/文件)
-
使用: 浏览器发送请求时自动携带对应的cookie, 服务器端通过req读取: req.cookies.key
7. 内存结构图(原型结构图)
function Foo () {}
const fn1 = new Foo()
const fn2 = new Foo()
const o1 = {}
const o2 = new Object()
8. 说说你对变量提升与函数提升的理解
变量提升: 在变量语句前就可以读取到变量, 值为undefined。
函数提升: 在函数定义语句前就可以调用函数。
原因: JS引擎在运行全局代码或执行函数前有预处理/解析
9. 说说原型链的理解
-
作用: 原型链用于查找对象的属性。
-
是什么: 实例对象上都会有一个隐式原型属性(proto), 它指向的就是原型对象, 而原型对象也有__proto__属性指向它的原型对象,以此类推,形成构成了一个原型链。
-
为什么__proto__指向的是原型对象?
构造函数对象上有显式原型属性(prototype), 它指向的就是原型对象
实例对象的__proto__属性被赋值为构造函数的prototype属性值。
10. 说说你对作用域链的理解
-
作用: 作用链用来查找变量。
-
是什么: 多个由内向外作用域形成的链。
-
作用域: 一块代码区域。
-
分类:全局作用域和函数/局部作用域, ES6有了块作用域
11. 说说你对闭包的理解
- 如何产生闭包?
2个函数嵌套
内部函数引用了外部函数内的局部变量
执行外部函数 - 是什么?
包含了那个局部变量的容器
它被内部函数对象引用着 - 作用?
延长局部变量的生命周期
使函数外部可以多次间接操作到函数内部的数据 - 应用?
循环遍历加监听
IIFE定义模块
jQuery内部 - 代码演示:
function fn1 () {
var a = 2
function fn2 () {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f()
f()
f = null
12. JS事件循环机制(图)
13. 比较函数的call()/apply()/bind()
- call(obj, param1, param2)
apply(obj, [[param1, param2])
调用/执行函数
只是强制指定函数中的this为第一个参数指定的对象
如果函数执行需要传参数, call是依次传递, apply需要封装成数组传递 - bind()
返回一个新函数, 不会自动执行, 需要手动执行
强制指定函数中的this为第一个参数指定的对象
新函数内部会原样调用原函数
14. debug调试
-
调试的目的
1)查找bug: 不断缩小可疑代码的范围 2)查看程序的运行流程(用于熟悉新接手项目的代码)
-
如何开启调试模式
1)添加语debugger句: 程序运行前 此方式用打包后才运行的项目 2)添加(打)断点: 程序运行前或者过程中 此方式用运行源码js
-
如何进行调试操作
resume: 恢复程序执行(可能执行完或者进入下一个断点处) step ove: 单步跳转, 尝试执行完当前语句, 进入下一条(如果内部有断点, 自动进入内部断点处) step into: 跳入, 进入当前调用函数内部 step out: 跳出, 一次性执行完当前函数后面所有语句,并出去 deactivate breakpoints: 使所有断点暂时失效 call stack: 显示是程序函数调用的过程 scop: 当前执行环境对应的作用域中包含的变量数据 breakpoints: 断点列表
15. 说说你对回调函数的理解
-
什么函数才是回调函数
你定义的
你没有直接调用
它最终执行了 -
回调函数相关的3个问题
什么时候执行
用来做什么的
函数中的this是谁
16. 详细说明如何判断函数中的this
-
正常情况: 执行函数的方式决定了函数中的this
- 直接调用: fn() window
- new调用: new fn() 新创建的对象
- 对象调用: obj.fn() obj对象
- call/apply调用: fn.call(obj) 第一个参数指定的对象
-
特别情况:
- bind()返回的函数: fn2 = fn.bind(obj) fn2()第一个参数指定的对象
- 箭头函数: 使用的外部的this(内部没有自己的this) fn = () => {} fn()
- 回调函数
- 定时器回调/ajax回调/数组遍历相关方法回调: window
- dom事件监听回调: dom元素
- 组件生命周期回调: 组件对象
-
在开发我们经常会利用箭头函数/bind()来改变this的指向
17. 区别localStorage和sessionStorage, session与cookie
-
localStorage: 浏览器端持久化存储, 关闭浏览还存在, 最大5MB(基本没限制了)。
-
sessionStorage: 浏览器端内存存储, 关闭浏览器不存在
-
session: 服务器端创建, 服务器端保存, 依赖于cookie
-
cookie: 服务器端创建, 浏览器端保存, 请求携带对应cookie, 长度和数量有限制(4kb)
18. 关于2个引用变量指向同一个对象的2个问题
- 2个引用变量指向同个对象, 通过一个引用变量改变对象内部的数据, 另一个引用变量看到的新的
- 2个引用变量指向同个对象, 让一个引用变量指向一个新的对象, 另一个引用变量看到的还是原来的对象
19. console.log(a.b)的查找流程
先查找a, 沿着作用域链查找, 找不到报错(变量未定义)
找到后查找对象上的b属性, 查找原型链, 如果找不到返回undefined
20. 为什么要深拷贝、浅拷贝?
let a = {age: 1}
let b = a
a.age = 2 // 操作a也会影响b
console.log(b.age) // 2
从上述例子中我们可以发现,如果给一个变量赋值一个对象,那么两者的值会是同一个引用,其中一方改变,另一方也会相应改变。
21. 手写深度克隆
- 深度克隆:对象/数组
function deepClone(target) {
// 1. 初始化变量,获取目标数据的类型
let result, targetClass = getTargetClass(target);
// 2. 判断目标的类型
if(targetClass === 'Object'){ // 对象
result = {};
}else if(targetClass === 'Array'){ // 数组
result = [];
}else {
return target;
}
// 3. 遍历目标数据
for(let key in target){
// 获取每一项值
let item = target[key];
// 4. 判断每一项数据的类型
if(getTargetClass(item) === 'Object' || getTargetClass(item) === 'Array'){
// 无论是对象还是数组,都可以result[key]取值
// item = {name: 'kobe'} item[]
result[key] = deepClone(item);
}else {
result[key] = item;
}
}
return result;
}
//定义一个判断数据类型的函数
function getTargetClass(target) {
return Object.prototype.toString.call(target).slice(8, -1)
}
21. ES6中共有五种针对对象遍历的方法?
- for in
for in 循环遍历对象自身的和继承的可枚举的属性(不含Symbol属性) - Object.keys(obj)
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性) - Object.getOwnPropertyNames(obj)
返回一个数组,包含对象自身的所有属性(不含Symbol属性,但包括不可枚举属性) - Object.getOwnPropertySymbols(obj)
返回一个数组,包含对象自身所有的Symbol属性 - Reflect.ownKeys(obj)
返回一个数组,包含对象自身的所有属性,不论属性名是Symbol或字符串,也不管是否可枚举。
23. 什么是同源策略?
- 同源策略是由Netscape提出的一个著名的安全策略,现在所有支持JavaScript 的浏览器都会使用这个策略。
- Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
- 所谓同源是指:协议,域名(IP),端口必须要完全相同
即:协议、域名(IP)、端口都相同,才能算是在同一个域里。
24. 非同源受到了哪些限制?
- Cookie不能读取;
- DOM无法获得;
- Ajax请求不能发送
21. JSONP解决跨域
1.前端写法
<body>
<button id="btn">按钮</button>
<script type="text/javascript">
var btn = document.getElementById('btn');
btn.onclick = function () {
//1. 创建一个script标签
var script = document.createElement('script');
//2. 设置回调函数
window.getData = function (data) {
console.log(data);//拿到数据
}
//3. 设置script标签src属性,填写跨域请求的地址
script.src = 'http://localhost:3000/jsonp?callback=getData';
//4. 将script标签添加到body中生效
document.body.appendChild(script);
//5.不影响整体DOM结构,删除script标签
document.body.removeChild(script);
}
</script>
</body>
2.后端写法
app.get('/jsonp', (req, res) => {
//解构赋值获取请求参数
const {callback} = req.query
//去数据库查找对应数据
const data = [{name: 'tom', age: 18}, {name: 'jerry', age: 20}];
res.send(callback + '(' + JSON.stringify(data) + ')');
})
26. Cors解决跨域?
以Node为例:
res.set(‘Access-Control-Allow-Origin’, ‘http://localhost:63342’);//存在安全问题
// 以上两种凡是解决跨域, 第三种使用代理工具 反向代理工具
27. 函数防抖
// 防抖(一段时间会等,然后带着一起做了)
function debounce(fn, delay){
let timerId = null
return function(){
const context = this
if(timerId){window.clearTimeout(timerId)}
timerId = setTimeout(()=>{
fn.apply(context, arguments)
timerId = null
},delay)
}
}
28. 函数节流
// 节流(一段时间执行一次之后,就不执行第二次)
function throttle(fn, delay){
let canUse = true
return function(){
if(canUse){
fn.apply(this, arguments)
canUse = false
setTimeout(()=>canUse = true, delay)
}
}
}
29. 手写ajax
var request = new XMLHttpRequest()
request.open('GET', '/a/b/c?name=ff', true);
request.onreadystatechange = function () {
if(request.readyState === 4 && request.status === 200) {
console.log(request.responseText);
}};
request.send();
四、React
1. 说说你对React的基本理解
- 什么
动态构建用户界面的JS库 - React的特点
Declarative(声明式编码)
Component-Based(组件化编码)
Learn Once, Write Anywhere(支持客户端与服务器渲染/编写原跨平台生应用)
高效 - React高效的原因
虚拟(virtual)DOM, 不总是直接操作真实DOM(批量更新, 减少更新的次数)
高效的DOM Diff算法, 最小化页面重绘(减小页面更新的区域)
2. 使用2种方式定义一个简单组件
- 方式1: 工厂函数(简单/无状态组件)
function MyCom1(props) {
return <h1>自定义组件标题11111</h1>
}
- 方式2: ES6类(复杂/有状态组件)
class MyCom2 extends React.Component {
render () { // 回调函数, 为组件对象提供虚拟DOM
return <h1>自定义组件标题33333</h1>
}
}
ReactDOM.render(<MyComponent1/>, domContainer)
3. 区别类组件和函数式组件
- 类组件: 使用class定义的组件, 会产生组件对象, 可以有自身的状态和生命周期勾子
- 函数组件: 使用function定义的组件, 不会产生组件对象, 没有自身的状态和生命周期勾子
4. 区别组件对象的3大属性
- state: 值为容器对象, 保存的是组件内可变的数据,组件根据state中的数据显示, 要更新界面只要更新state即可
- props: 值为容器对象, 保存的是从组件外传递过来的数据, 当前组件只读, 父组件修改了自动显示新数据
- refs: 值为容器对象, 保存的是n个有ref属性的dom元素对象, 属性名是ref指定的标识名称, 值为对应的dom元素
5. React组件化编码的3步与2个重要问题
编码的3步
-
拆分组件
-
实现静态组件
-
实现动态组件
a. 动态显示初始化数据- 类型
- 名称
- 保存在哪个组件
b. 交互
2个重要问题
- 状态数据保存在哪个组件? 看是某个组件需要, 还是某些组件需要(给父组件)
- 更新状态数据的行为在哪个组件? 状态在哪个组件, 行为就定义在哪个组件
6. react组件的生命周期及勾子
一、初始化阶段:ReactDOM.render(, containDom)
- constructor(): 创建组件对象的初始化方法
- componentWillMount:组件即将被装载、渲染到页面上
- render:组件在这里生成虚拟的DOM节点
- componentDidMount:组件真正在被装载之后
二、更新阶段:this.setState({})
- componentWillReceiveProps:组件将要接收到属性的时候调用
- shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回false,接收数据后不更新,阻止render调用重绘)——优化性能
- componentWillUpdate:组件即将更新不能修改属性和状态
- render:组件重新描绘
- componentDidUpdate:组件已经更新
三、销毁阶段:ReactDOM.unmountComponentAtNode(div)
- componentWillUnmount:组件即将销毁
7. 关于设计state数据的3个问题
- 类型: 数组
- 名称: todos
- 保存在哪个组件? 看是哪个组件需要还是哪些组件需要(共同的父组件)
8. 说说react应用中如何与后台通信?
- 通过ajax请求与后台交互, 但react本身并不包含ajax语法封装, 需要使用第三方ajax库
- 可以使用axios/fetch来发送ajax请求
- 发请求的时机/位置:
a. 初始化请求: componentDidMount()
b. 用户操作后请求: 事件回调函数或相关位置
9. 比较react中组件间3种通信方式
-
方式一: 通过props传递
通过props可以传递一般数据和函数数据,
一般数据–>父组件向子组件
函数数据–>子组件向父组件通信
缺点: 只能一层一层传递/兄弟组件必须借助父组件 -
方式二: 使用消息订阅(subscribe)-发布(publish)机制
实现库: pubsub-js
组件A: 发布消息(相当于触发事件)Pubsub.publish("事件","数据")
组件B: 订阅消息, 接收消息, 处理消息(相当于绑定事件监听)Pubsub.subscribe("事件",(事件,数据)=>{})
优点: 对组件关系没有限制
写在componentDidMount
中 -
方式三: redux
通过redux可以实现任意组件间的通信
集中式管理多个组件共享的状态, 而pubsub-js并不是集中式的管理 -
跨组件传值——context
context上下文对象,无需为每一层徐建手动添加props,就能在组件数据之间进行数据传递的方法。
使用createContext()方法提供了两个对象:
Provider对象
——生产者,用来生产数据;
Consumer对象
——消费者,用来使用数据;
步骤:①创建一个上下问对象的文件,并且设置
{this.props.children}
②在根里面引用相关的上下文对象组件 并使用
③创建上下文对象
// 创建上下文对象数据
let context=createContext()
let {Provider,Consumer}=context
{/* 1.添加this.props.children */}
<Provider value={123}>
{this.props.children}
</Provider>
④修改根中的组件引用
⑤在组件中使用
我是爷爷组件
<Consumer>
{
(value)=>{
return (
<span>{value}</span>
)
}
}
</Consumer>
10. 说说对react-router的理解
-
下载: npm install --save react-router-dom
-
是什么: 用来实现SPA的react插件
-
相关API:
a. 组件<HashRouter> / <BrowserRouter> <Route> <Redirect> <NavLink> / <Link> <Switch>
b. 对象或函数
props.history对象 props.match对象 props.location对象 withRouter函数
11. 区别UI组件与容器组件
- react-redux提出的
- UI组件: 只负责界面的显示, 不会使用redux相关语法, 没有自身的状态
- 容器组件: 包装UI组件, 向UI组件提供状态数据和更新状态数据的行为(连接UI组件和redux), 通过connect产生
12. 说说你对redux的理解
-
redux是一个独立专门用于做状态管理的JS库(不是react插件库)
-
可以与任何前端库配合使用, 但最合适的是与react库配合
-
作用: 集中式管理react应用中多个组件共享的状态
-
提供的API:
createStore() applyMiddleWare() combineReducers() store.getState()/dispatch()/subscribe()
13. redux结构图
14. redux的基本编码
- redux
store.js
生成并暴露一个store管理对象
reducers.js
包含n个reducer函数
根据老state和指定action来产生返回一个新的state
actions.js
包含n个action creator函数
同步action: 返回一个action对象({type: ‘XXX’, data: xxx})
异步action: 返回一个函数: disptach => {执行异步代理, 结束时dispatch一个同步action}
action-types.js
包含n个同步action的type名称常量 - 入口JS
<Provider store={store}>
- 组件
export default connect(
state => ({xxx: state.xxx}),
{action1, action2}
)(UI组件)
15. React组件的二种分类
- 路由组件和非路由组件 —react-router-dom
- 容器组件和UI组件 ---->react-redux
16. react性能优化是哪个周期函数?
shouldComponentUpdate 这个方法用来判断是否需要调用render方法
17. 为什么虚拟dom会提高性能?
- 虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能。
- 具体实现步骤如下:
- 用 JavaScript 对象结构表示 DOM 树的结构
- 然后用这个树构建一个真正的 DOM 树,插到文档当中当状态变更的时候,重新构造一棵新的对象树。
- 然后用新的树和旧的树进行比较,记录两棵树差异把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了。
18. Dom Diff算法
- 把树形结构按照层级分解,只比较同级元素。给列表结构的每个单元添加唯一的key属性,方便比较。
- React 只会匹配相同类名的 component合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.
- 在每一个事件循环结束时, React 检查所有标记 dirty 的 component 重新绘制.选择性子树渲染。
- 开发人员可以重写shouldComponentUpdate提高diff的性能。
19. 调用 setState 之后发生了什么?
- React 会将传入的参数对象与组件当前的状态合并产生了新的state
- 生成新的虚拟DOM树
- 计算出新树与老树的节点差异,然后做真实DOM的差异更新
20. react/vue中的key的作用/内部原理是什么?
类似问题: 为什么列表的key尽量不要用index
- 虚拟DOM的
key
的作用?
- 简单说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用
- 详细说: 当列表数组中的数据发生变化生成新的虚拟DOM后, React进行新旧虚拟DOM的diff比较
- key没有变
item数据没变, 直接使用原来的真实DOM
item数据变了, 对原来的真实DOM进行数据更新 - key变了
销毁原来的真实DOM, 根据item数据创建新的真实DOM显示(即使item数据没有变)
- key没有变
- key为index的问题
1). 添加/删除/排序 => 产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低
2). 如果item界面还有输入框 => 产生错误的真实DOM更新 ==> 界面有问题
注意: 如果不存在添加/删除/排序操作, 用index没有问题 - 解决:
使用item数据的标识数据作为key, 比如id属性值
21. 区别React中的事件与原生的DOM事件
- 为了解决跨浏览器兼容性问题,React 会将浏览器原生事件封装为合成事件
- React 并没有直接将事件附着到子元素上, 而是将事件绑定在了组件的根元素上(使用事件委托)
22. 传入 setState 函数的第二个参数的作用是什么?
- 可以将一个函数作为setState()的第2个参数传递
this.setState(
{ username: ‘tylermcginnis33’ },
() => console.log(‘状态更新且界面也更新了’)
) - 函数会在界面更新后才调用, 此时可以得到最新的状态, 也可以访问到最新的界面
23. 何为受控组件
(controlled component)
组件中的表单项能在输入过程中自动收集输入数据到状态中
在react中必须通过onChange
监听才能实现
24. 何为高阶组件(higher order component)
高阶组件是一个以组件为参数并返回一个新组件的函数。
HOC 运行你重用代码、逻辑和引导抽象。
最常见的可能是Redux 的 connect 函数.
如果你发现你在不同的地方写了大量代码来做同一件事时,就应该考虑将代码重构为可重用的 HOC
25. 了解 redux么,说一下redux吧
redux
是用来对组件的状态数据进行集中式管理的框架。- 主要有三个核心部分,
action,store,reducer
- 工作流程是
组件中调用store的dispatch()
分发action,
触发reducer
执行, 返回新的state
view
通过store提供的getState()
获取最新的数据
26. 详细说说redux
-
何为
redux
redux 的基本思想是应用的state保存在一个单一的store对象中。而改变应用state的唯一方式是在应用中触发actions,然后为这些actions编写reducers来修改state。
整个 state 转化是在 reducers 中完成,并且不应该有任何副作用
-
在 Redux 中,何为
store
Store 是一个 javascript 对象,它保存了整个应用的 state。与此同时,Store 也承担以下职责:允许通过 getState() 访问 state 通过 dispatch(action) 改变 state 通过 subscribe(listener) 注册 listeners
-
何为
action
action是一个纯 js对象,必须有一个 type 属性表明正在执行的 action 的类型。
实质上,action 是将数据从应用程序发送到 store 的有效载荷。 -
何为 reducer
一个reducer是一个纯函数,该函数以先前的state和一个action作为参数,并返回新的state。 -
**
redux-thunk
**的作用是什么
redux-thunk是一个允许你编写返回一个函数而不是一个 action对象的中间件。
thunk可以用来延迟 action 的派发(dispatch),也就是处理异步 action 的派发(dispatch)。 -
何为纯函数
(pure function)
一个纯函数是一个不依赖于且不改变其作用域之外的变量状态的函数,
这也意味着一个纯函数对于同样的参数总是返回同样的结果。
五、Vue
1. 说一下你对Vue的理解
vue是一个前端JS库
MVVM模式的实现
作用: 动态构建用户界面
重要技术: 模板与数据绑定(angular), 组件与虚拟DOM(react)
2. 说说MVVM设计模式
M: Model(模型), vue中是data(为view提供数据)
V: View(视图), vue中是模板页面(显示data中的数据)
VM: ViewModel(视图模型), vue中是Vue实例对象(管理者: 数据绑定/DOM监听)
3. 说说你对模板的理解
html中嵌套js
指令: < p v-text='msg'>
大括号表达式: {{msg}}
4. vue的常用配置选项及其作用(5个)
el: 最外层元素选择器
props: 声明接收哪些属性
data: 状态数据
computed: 计算属性
methods: 事件回调函数
watch: 监视属性变化
directives: 注册局部指令
filters: 注册局部过滤器
components: 配置组件
5. 说说你对计算属性的理解
什么时候用计算属性?
模板显示要显示的数据是根据n个已有的相关数据进行计算来确定
- getter: get方法, 当读取属性值时/相关数据发生了改变自动调用, 根据相关数据进行计算并返回结果, this就是vm
- setter: set方法, 当修改了当前属性的值自动调用, 监视属性值的变化去更新相关数据, this就是vm
6. 说说vue的vm对象/组件对象的生命周期
- 初始化
beforeCreate()
created()
beforeMount()
mounted(): 异步任务(发ajax请求/启动定时器) - 更新
beforeUpdate()
updated() - 死亡
beforeDestroy(): 收尾工作(清除定时器)
destroyed()
7. 写出7个指令及其作用
v-text: 设置标签体文本
v-html: 设置标签体子标签
v-if/v-else/v-show: 显示/隐藏
v-for: 遍历显示列表
v-bind: 强制绑定表达式, 简写:
v-on: 绑定事件监听, 简写@
v-model: 双向数据绑定
8. 说说vue组件间通信的几种方式
- props
父子组件间通信的基本方式
属性值的2大类型:
一般: 父组件–>子组件
函数: 子组件–>父组件
隔层组件间传递: 必须逐层传递(麻烦)
兄弟组件间: 必须借助父组件(麻烦) - vue自定义事件
子组件与父组件的通信方式
用来取代function props
不适合隔层组件和兄弟组件间的通信 - 消息订阅和发布
适合于任何关系的组件间通信
缺点: 管理不够集中 - vuex
多组件共享状态(数据的管理)
组件间的关系也没有限制
功能比pubsub强大, 更适用于vue项目 - slot
通信是带数据的标签
注意: 标签是在父组件中解析
9. 说说vue项目中如何与后台通信
- 通过ajax请求与后台通信
- 常用的库有2个
vue-resource:
vue的插件, 用于vue1.x
axios:
独立的第三方库,用于vue2.x
- 执行请求代码的时机
初始化异步显示: mounted()
特定用户操作后异步显示: 事件回调函数或相关函数中
10. vue-router提供了哪些语法?
- 1个函数:
VueRouter:
路由构建函数, 用于创建路由器对象, 配置路由 - 2个对象
$route:
代表当前路由的对象, 包含当前路由相关信息(path, params参数, query参数)
$router:
代表路由器对象, 包含控制路由跳转的方法(push/replce/back()) - 3个标签
: 路由链接, 生成路由链接
: 路由视图, 显示当前路由组件
: 缓存路由组件对象
11. 说说你对vuex的理解
vue的插件
对vue应用中多个组件的共享状态进行集中式的管理(读/写)
12. 在vue项目中引入vuex的基本流程
下载vuex
创建store对象
export default new Vuex.Store({
state,
mutations,
actions,
gettters
})
配置store对象
new Vue({ store })
组件中与vuex通信
$store.state/mapState()
$store.getters/mapGetters()
$store.dispatch()/mapActions()
13. vuex结构图
14. 说说vue数据代理
-
是什么?
通过vm对象来代理data对象中所有属性的操作 -
作用:
简化操作vm中的data对象中的数据 -
实现基本原理
- 通过
Object.defineProperty()
给vm添加与data对象的属性对应的属性描述符 - 所有添加的属性都包含
getter/setter
- 在
getter/setter
内部去操作data中对应的属性数据
15. 说说vue模板解析
- 目的
实现初始化显示 - 整体流程
1). 将el的所有子节点取出, 添加到一个新建的文档fragment
对象中
2). 对fragment
中的所有层次子节点递归进行编译解析处理
3). 将解析后的fragment
添加到el中显示 - 编译/解析包含大括号表达式的文本节点:
textNode.textContent = value
- 编译事件指令:
elementNode.addEventListener('eventName', callback)
- 编译一般指令:
elementNode.xxx = value
16. 说说数据绑定的理解和基本原理
- 作用:
实现数据的更新显示 - 基本原理:
a.通过Object.defineProperterty()
给data中所有属性添加setter/getter
, 实现数据劫持
b.为每个data
中的属性创建一个对应的dep对象
, 一旦属性数据变化, 通知dep对象
c.为模板中的每个表达式创建对应的watcher
, 并关联到对应的dep上
d.一旦dep收到数据变化的通知, 会通知所有关联的watcher, watcher收到通知后就更新对应的节点
17. Vue实现数据双向绑定的原理
- vue实现数据绑定主要是:采用数据劫持 + 消息订阅发布模式的方式,
- 通过
Object.defineProperty()来劫持/监视data中的属性
,在数据变动时发布消息给所有订阅者,每个订阅者去更新对应的DOM节点。 - 双向数据绑定是单向数据绑定的基础上,
给元素绑定input监听
, 一旦输入改变了, 将最新的值保存到对应的属性上
18. Vue的MVVM实现结构图
19. 模板中显示数据的来源
data
props
computed: 根据data/props/其它compute属性/vuex的state或getters计算产生
20. 说说你项目中使用的better-scroll库
- 什么: 用来实现页面滑动相关效果的js库
- 原理: 当内容的高度超过了容器的高度(固定)时, 才会形成滚动
- 基本编码:
scroll = new BScroll(selector, {})
scroll.on(eventName, cb)
scroll.scrollTo()
scroll.scrollToElement()
scroll.refresh()
- 注意:
创建BScroll对象的时机
创建BScroll对象的个数
BScroll禁用的原生dom事件, 自动定义事件分发
21. 说说你vue项目开发中碰到的问题, 原因及解决办法
-
创建
Swiper/BScroll对象
后, 轮播/滑动没有效果?
原因: 创建对象太早, 得在数据显示后创建
解决:watch + $nextTick() 或 callback + $nextTick()
-
初始显示异常
- 情况1:
Cannot read property 'xxx' of undefined"
原因: 初始值是空对象, 内部没有数据, 而模块中直接显示3层表达式
解决: 使用v-if指令
- 情况2:
Cannot read property 'avatar' of null"
原因: 初始值是null, 而模块中直接显示2层表达式
解决:初始值为{}
- 情况1:
-
问题: 更新状态数据, 对应的界面不变化
原因: 一般方法给一个已有绑定的对象中添加一个新的属性, 这个属性没有数据绑定
解决:
Vue.set(obj, 'xxx', value)
才有数据绑定
this.$set(obj, 'xxx', value)
才有数据绑定 -
问题: 点击添加购物项, 会1次添加多个
原因: 创建了多个BScroll对象来管理同一个DOM元素
解决: 只创建一个BScroll对象
扩展: 单例对象:
创建前, 先判断是否已经存在, 只有不存在才创建
创建后, 保存创建的对象
22. 前台异步显示数据效果的编码的3个部分
ajax
ajax请求函数
接口请求函数
vuex
store
state
mutation-types
mutations
actions
getters
组件
dispatch(): 异步获取后台数据到vuex的state
mapState()/mapGetters(): 从vuex的state中读取对应的数据
模板中显示
23. 区别v-if与v-show
如果频繁切换使用v-show比较合适
一旦涉及到初始化模板显示3层表达式数据, 使用v-if可以解决问题
24. 区别 r o u t e 和 route和 route和router
$route
是“路由信息对象”: 包括path,params,hash,query,meta等路由信息参数。
$router
是“路由实例”对象: 包括了路由的跳转方法,注册全局导航守卫的方法。
25. 说说你对路由导航卫士的理解
- 是什么?
导航守卫是vue-router提供的下面2个方面的功能
a. 监视路由跳转
b. 控制路由跳转 - 应用
a. 在跳转到界面前, 进行用户权限检查限制(如是否已登陆)
b. 在界面离开前, 做收尾工作 - 常用的卫士回调函数:
a. 全局前置卫士: beforeEach()
b. 组件进入卫士: beforeRouteEnter() // 不能直接使用this
c. 组件离开卫士: beforeRoute
26. 区别2种路由实现:hash模式 和 history模式
- hash模式:
在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取;
注意:hash虽然在URL中,但不会提交给服务器端, 只在浏览器端使用
- history模式:
路由路径中没有#
, 利用HTML5的新特性: pushState()/replaceState()及popState事件监听
注意:路径会提交给后台
, 在生产环境下会出现404的问题
解决404问题: 后台服务器端配置404页面或通过自定义中间件指定404页面为index.html页面
27. vue与react的比较?
- 相似处
功能: 动态构建用户界面
虚拟DOM: 内部都通过虚拟DOM提高效率
组件化: 都使用组件化编程思想编写项目
Props: 组件间通信的基本方式
构建工具: 脚手架, webpack
Chrome开发工具: react/redux-dev-tool vue-dev-tool - 不同点
界面: JSX与模板
界面更新: 状态管理 与 对象属性监视
数据绑定: 单向与双向
六、其它
1. package.json的结构
{
"name": "react-demo", // 标识名称
"version": "1.0.0", // 版本号
"scripts": { // 打包运行相关的npm命令
"xxx": "具体命令" npm run xxx
},
dependencies: {}, // 运行时依赖
devDependencies: {} // 开发时依赖
2. 说说你对json数据的理解
- 什么: json是一种用来存储结构化数据的文本数据结构
- 优点: 小巧, 可以轻松的与js相互转换
- 整体类型:
json对象: {}, 与js对象对应
json数组: [] 与js数组对应 - 组成:
结构: 类型与名称
值 - 模拟json数据:
与真实json数据在结构上要相同, 值可以不同
3. 说说你对mock数据的理解
- 对于前后台分离的项目来说, 前后台可以独立开发, 当后台还没有写好时, 前台应用就可以编写了
- 前台应用需要自己mock数据接口动态为前台提供数据, 当真实接口完成后, 切换到真实接口即可
- 要求: mock的json数据与真实接口的数据在结构上要相同
- 工具包: mockjs
4. 说说脚手架的理解
- 用来创建基于某个特定库(react/vue)的模板项目的工具包
- 全局下载脚手架后, 就会多出一个命令, 通过命令就可以创建项目
- 创建出的项目已经是有完整配置, 依赖声明的一个模块化/组件化/工程化的项目
5. 说说项目的开发环境运行与生产环境打包运行
- 开发环境运行
a. 命令:
npm start/ npm run dev
b. 背后做了什么
在内存中生成打包文件(不生成本地打包文件)
启动服务器运行内存中的打包文件 - 生产环境打包运行
a. 命令
npm run build
serve build/dist
b. 背后做了什么
在内存中生成打包文件
生成本地打包文件
启动服务器加载运行本地打包文件
6. 描述一下你的React项目
- 此项目为一个
前后台分离
的招聘的SPA
, 包括前端应用和后端应用 - 包括用户
注册/登陆
, 大神/老板列表, 实时聊天等模块 - 前端应用: 使用
React全家桶+ES6+Webpack等技术
- 后端应用: 使用
Node + express + mongodb + socketIO等技术
- 采用
模块化
、组件化
、工程化
的模式开发
7. 什么是API接口
- url
- 请求方式
- 请求参数格式
- 响应数据格式
8. 说说你对路由的理解
- 路由是什么?
就是一个key:value的映射关系 - 路由的分类?
a. 后台路由: path—callback
b. 前台路由: path—component - 作用?
a. 后台路由: 当服务器接收到请求时, 根据请求的path找到对应的路由, 由路由的回调函数来处理请求, 返回响应
b. 前台路由: 当请求某个路由地址时, 根据请求的path找到对应的路由, 显示路由对应的组件界面
9. GET请求的2种请求参数
- query参数:
路由path: /register
请求path: /register?username=xxx&password=yyy
获取参数: req.query.username - param参数:
路由path: /register/:username/:password
请求path: /register/xxx/123
获取参数: req.params.username
10. 说说你对commonjs模块化规范的理解
- 暴露模块
向外暴露的永远是exports
, exports的默认值为{}
方式一:module.exports = value
方式二:exports.xxx = value1
exports.yyy = value2
- 引入模块
const module = require('模块名/模块路径')
11. 你对代理的理解
- 是什么?
具有特定功能的程序 - 运行在哪?
前台应用端、只能在开发时使用 - 作用?
解决开发时的ajax请求跨域问题
a. 监视并拦截请求(3000)
b. 转发请求(4000) - 配置代理
告诉代理一些信息: 转发的目标地址
12. chrome调试应用的常用功能(窗口)
Elements: 查看DOM标签和样式
Console: 查看打印和错误信息
NetWork: 查看请求(url, 请求方式, 请求参数)和响应
Application: 查看浏览器端存储(localStorage, sessionStorage, cookie)
Sources: debugger调试
react: 查看react组件(state, props)
redux: 查看redux管理的state
vue: 查看vue组件或vuex状态