组件之间如何通信?
组件间通信可以通过props、传递回调函数、context、redux等形式进行组件之间通讯
react/vue中的key有什么作用?(key的内部原理是什么?)
1)简单来说,key是虚拟DOM对象的标识,在更新显示时,key起着极其重要的作用。
2)详细来说,当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
? a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
? (1)若虚拟DOM中内容没变,直接使用之前的真实DOM
? (2)若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
? b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
? 根据数据创建新的真实DOM,随后渲染到页面
使用index作为key可能会引发的问题
-
若对数据进行逆序添加,逆序删除等破坏顺序性的操作: 会产生没有必要的真实DOM更新 ==> 界面效果没有问题,但效率低 -
若结构中还包含输入类的DOM(如输入框,选择框等): 会产生错误DOM更新 ==> 界面有问题 -
注意:如果不存在对数据的逆序添加,逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
选择key
- key应该是唯一的
- key不要使用随机值(随机数在下一次render时,会重新生成一个数字)
- 避免使用index作为key
refs的理解?应用场景?
Refs允许我们访问DOM节点或在render方法中创建的React元素
应用场景:
- 对DOM元素的焦点控制、内容选择、控制
- 对DOM元素的内容设置及媒体播放
- 对DOM元素的操作和对组件实例的操作
- 集成第三方DOM库
Hooks的理解?解决了什么问题?
Hook是React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React特性
解决问题:
- 难以重用和共享组件中的与状态相关的逻辑
- 逻辑复杂的组件难以开发与维护,当我们的组件需要处理多个互不相关的 local state时,每个生命周期函数可能会包含着各种互不相关的逻辑在里面
- 类组件中的this增加学习成本,类组价在基于现有工具的优化上存在些许问题
- 由于业务变动,函数组件不得不改为类组件等
redux工作原理?
redux 要求我们把数据都放在 store 公共存储空间
一个组件改变了 store 里的数据内容,其他组件就能感知到 store的变化,再来取数据,从而间接的实现了这些数据传递的功能
工作流程如下所示:
render触发时机?
在 React 中,类组件只要执行了 setState 方法,就一定会触发 render 函数执行
函数组件 useState 会判断当前值有无发生改变确定是否执行render方法,一旦父组件发生渲染,子组件也会渲染
如何减少render?
父组件渲染导致子组件渲染,子组件并没有发生任何改变,这时候就可以从避免无谓的渲染,具体实现的方式如下:
- shouldComponentUpdate
- PureComponent
- React.memo
jsx转化DOM过程?
jsx首先会转化成 React.createElement这种形式,React.createElement作用是生成一个虚拟DOM对象,然后会通过ReactDOM.render进行渲染成真实DOM
性能优化手段有哪些?
除了减少render的渲染之外,还可以通过以下手段进行优化:
- 避免使用内联函数
- 使用React Fragments避免额外标记
- 使用Immutable
- 懒加载组件
- 事件绑定方式
- 服务端渲染
什么是防抖和节流?有什么区别?如何实现?
防抖:触发高频事件后n秒内 函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
function debounce(fn) {
let timeout = null;
return function() {
clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(this, arguments);
},500);
}
}
function sayHi() {
console.log("防抖成功");
}
var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi));
节流:高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率
function throttle(fn) {
let canRun = true;
return function() {
if (!canRun) return;
canRun = false;
setTimeout(() => {
fn.apply(this, arguments);
canRun = true;
},500);
}
}
function sayHi(e) {
console.log(e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi));
npm模块安装机制,为什么输入 npm install 就可以自动安装对应模块?
- npm 模块安装机制:
- 发出npm install 命令
- 查询node_modules目录之中是否已经存在指定模块
- 若存在,不再重新安装
- 若不存在
- npm 向 registry 查询模块压缩包的网址
- 下载压缩包,存放在根目录下的 .npm目录里
- 解压压缩包到当前项目的node_modules目录
- npm 实现原理:
输入 npm install 命令并敲下回车后,会经历如下几个阶段(以npm 5.5.1为例):
-
执行工程自身的 preinstall 当前 npm 工程如果定义了preinstall 钩子此时会被执行 -
确定首层依赖模块
- 首先需要做的是确定工程中的首层依赖,也就是 dependencies 和 devDependencies 属性中直接指定的模块(假设此时没有添加 npm install 参数)
- 工程本身是整颗依赖树的根节点,每个首层依赖模块都是根节点下面的一颗子树,npm 会开启多进程从每个首层依赖模块开始逐步寻找更深层级的节点。
-
获取模块 获取模块是一个递归的过程,分为以下几步:
- 获取模块信息。在下载一个模块之前,首先要确定其版本,这是因为package.json中往往是 semantic version(semver,语义化版本)。此时如果版本描述文件(npm-shrinkwrap.json 或 package-lock.json)中该模块信息直接拿即可,如果没有则从仓库获取。如 package.json 中某个包的版本是^1.1.0,npm就会去仓库中获取符合 1.x.x形式的最新版本。
- 获取模块实体。上一步会获取到模块的压缩包地址(resolved字段),npm 会用此地址检查本地缓存,缓存中有就直接拿,如果没有则从仓库下载。
- 查找该模块依赖,如果有依赖则回到第1步,如果没有则停止。
-
模块扁平化(dedupe) 上一步获取到的是一颗完整的依赖树,其中可能包含大量重复模块。比如A模块于loadsh,B模块同样依赖于lodash。在npm3以前会严格按照依赖树的结构进行安装,因此会造成模块冗余。 从npm3开始默认加入了一个dedupe的过程。它会遍历所有节点,逐个将模块放在根节点下面,也就是 node-modules 的第一层。当发现有重复模块,则将其丢弃。 这里需要对重复模块进行一个定义,它指的是模块名相同且 semver 兼容。每个 semver 都对应一段版本允许范围,如果两个模块的版本允许范围存在交集,那么就可以得到一个兼容版本,而不必版本号完全一致,这可以使更多冗余模块在dedupe过程中被去掉。 比如 node-modules 下 foo 模块依赖 lodash@^ 1.0.0,bar模块依赖lodash@^ 1.1.0,则^ 1.1.0 为兼容版本。 而当 foo 依赖 lodash@^ 2.0.0 ,bar依赖 lodash@^ 1.1.0,则依据 semver 的规则,二者不存在兼容版本。会将一个版本放在 node_modules 中,另一个仍然保留在依赖树里。 举个例子,假设一个依赖树原本是这样: node_modules – foo ---- lodash@version1 – bar ---- lodash@version2 假设 version1 和 version2 是兼容版本,则经过 dedupe 会成为下面的形式: node_modules – foo – bar – lodash(保留的版本为兼容版本) 假设 version1 和 version2 为非兼容版本,则后面的版本保留在依赖树中: node_modules – foo – lodash@version1 – bar ---- lodash@version2 -
安装模块 这一步将会更新工程中的 node_modules,并执行模块中的生命周期函数(按照 preinstall、install、postinstall的顺序)。 -
执行工程自身生命周期 当前 npm 工程如果定义了钩子此时会被执行(按照install、postinstall、prepublish、prepare 的顺序)。 最后一步是生成或更新版本描述文件,npm install 过程完成。
ES5的继承和ES6的继承有什么区别?
ES5的继承时通过prototype或构造函数机制来实现。ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))
ES6的继承机制完全不同,实质上是先创建父类的实例对象this(所以必须先调用父类的super() 方法,然后再用子类的构造函数修改this)
具体的:ES6通过clas s关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其进行加工。如果不调用super方法,子类得不到this对象。 ps:super关键字指代父类的实例,即父类的this对象。在子类构造函数中,调用super后,才可使用this关键字,否则报错。
为什么虚拟dom会提高性能?
虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff 算法避免了没有必要的dom操作,从而提高性能。 具体实现步骤如下:
- 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
- 当状态变更时,重新构造一颗新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
把步骤2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了。
分析比较 opacity:0、visibility:hidden、display:none优劣和适用场景
结构: display:none 会让元素完全从渲染树中消失,渲染的时候不占据任何空间,不能点击 visibility:hidden 不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见,不能点击 opacity:0 不会让元素从渲染树消失,渲染元素继续占据空间,只是不可见,可以点击
继承: display:none 和 opacity:0 是非继承属性,子孙节点消失由于元素从渲染树消失造成,通过修改子孙节点属性无法显示。 visibility:hidden 是继承属性,子孙节点消失由于继承了hidden,通过设置visibility:visible 可以让子孙节点显示。
性能: display:none 修改元素会造成文档回流,读屏器不会读取display:none 元素内容,性能消耗较大 visibility:hidden 修改元素只会造成本元素的重绘,性能消耗较少,读屏器读取visibility:hidden元素内容 opacity:0 修改元素会造成重绘,性能消耗较少
css sprite 是什么,有什么优缺点?
概念:将多个小图片拼接到一个图片中。通过 background-position 和元素尺寸调节需要显示的背景图案。 优点:
- 减少 HTTP 请求数,极大的提高页面加载速度
- 增加图片信息重复度,提高压缩比,减少图片大小
- 更换风格方便,只需要在一张或几张图片上修改颜色或样式即可实现
缺点:
- 图片合并麻烦
- 维护麻烦,修改一个图片可能需要重新布局整个图片、样式
link和@import的区别
- link 是 HTML 方式,@import 是 CSS 方式
- link 最大限度支持并行下载,@import 过多嵌套导致串行下载,出现FOUC
- link 可以通过 rel=“alternate stylesheet” 指定候选样式
- 浏览器对 link 支持早于 @import,可以使用@import 对老浏览器隐藏样式
- @import 必须在样式规则之前,可以在css文件中引用其他文件
- 总体来说:link 优于 @import
display:block 和 display:inline的区别
block 元素特点:
- 处于常规流中时,如果 width 没有设置,会自动填充满父容器
- 可以应用margin/padding
- 在没有设置高度的情况下会扩展高度以包含常规流中的子元素
- 处于常规流中时布局是在前后元素位置之间(独占一个水平空间)
- 忽略 vertical-aligh
inline 元素特点:
- 水平方向上会根据 direction 依次布局
- 不会在元素前后进行换行
- 受 white-space 控制
- margin/padding 在竖直方向上无效,水平方向上有效
- width/height 属性对非替换行内元素无效,宽度由元素内容决定
- 非替换行内元素的行框高由 line-height 确定,替换行内元素的行框高 由height,margin,padding,border决定
- 浮动或决定定位时会转换为 block
- vertical-align 属性生效
PNG、GIF、JPG的区别及如何选
GIF:
- 8位像素,256色
- 无损压缩
- 支持简单动画
- 支持boolean透明
- 适合简单动画
JPEG:
- 颜色限于 256
- 有损压缩
- 可控制压缩质量
- 不支持透明
- 适合照片
PNG:
- 有PNG8 和 truecolor PNG
- PNG8 类似 GIF 颜色上限为 256,文件小,支持alpha 透明度,无动画
- 适合图标、背景、按钮
js有几种数据类型,其中基本数据类型有哪些?
七种数据类型 Boolean、Null、Undefined、Number、String、Object、Symbol(ES6) Symbol也是原始数据类型,表示独一无二的值 Object 为引用类型(范围挺大),也包括数组、函数
Promise构造函数是同步执行还是异步执行,那么 then 方法呢?
promise构造函数是同步执行的,then方法是异步执行的 Promise new 的时候会立即执行里面的代码, then是微任务 会在本次任务执行完的时候执行 setTimeout是 宏任务,会在下次任务执行的时候执行
列举出几种创建实例的方法
- 字面量
let obj = {'name':'张三'};
- Object构造函数创建
let Obj = new Object();
Obj.name = "张三";
- 使用工厂模式创建对象
function createPerson(name) {
var o = new Object();
o.name = name;
};
return o;
- 使用构造函数创建对象
function Person(name) {
this.name = name;
}
var person1 = new Person('张三');
简述一下前端事件流
HTML中与JavaScript交互是通过事件驱动来实现的,例如鼠标点击事件onclick、页面的滚动事件onscroll等等,可以向文档或者文档中的元素添加事件侦听器来预定事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下“事件流"的概念。 什么是事件流:事件流描述的是从页面中接收事件的顺序,DOM2级事件流包括下面几个阶段。 事件捕获阶段 处于目标阶段 事件冒泡阶段 addEventListener是DOM2级事件新增的指定事件处理程序的操作,这个方法接收3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。
Function.proto_(getPrototypeof)是什么?
获取一个对象的原型,在Chrome中可以通过__proto__的形式,或者在ES6中可以通过Object.getPrototypeOf的实行 那么Function.proto是什么?也就是说Function由什么对象继承而来?
Function.__proto__==Object.prototype
Function.__proto__==Function.prototype
我们发现Function的原型也是Function
简述一下原型/构造函数/实例
原型(prototype):一个简单的对象,用于实现对象的 属性继承。可以简单理解为对象的爹。在FireFox和Chrome中,每个JavaScript 对象中都包含一个 protp(非标准)的属性指向它爹(该对象的原型),可obj.__protp__记性访问
构造函数:可以通过 new 来 创建一个对象 的函数 实例:通过构造函数和 new 创建出来的对象,我们常用的Object 便是一个构造函数,因此我们可以通过它构建实例。
const instance = new Object();
则此时,实例为instance,构造函数为Object,我们知道,构造函数拥有一个 prototype 的属性指向原型,因此原型为:
const prototype = Object.prototype
这里我们可以来看出三者的关系:
实例.__proto__ === 原型
原型.constructor === 构造函数
构造函数.prototype === 原型
如何实现一个bind函数
Function.prototype.myBind = function(context) {
if(typeof this !== 'function') {
throw new TypeError('Error');
}
var _this = this;
var args = [...arguments].slice(1)
return function F() {
if (this instanceof F) {
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}
如何实现一个call函数
Function.prototype.myCall = function(context) {
var context = context || window;
context.fn = this
var args = [...arguments].slice(1);
var result = context.fn(...args)
delete context.fn
return result
}
如何实现一个apply函数
Function.prototype.myApply = function(context) {
var context = context || window
context.fn = this
var result;
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
参考链接:https://mp.weixin.qq.com/s/A2RtdHVhRq3ZTRchktPrrg
|