面试题/经验积累

vue中识别文本中的’/n’并实现自动换行
在vue中,直接渲染带有\n的语句,渲染失败,用 style="white-space: pre-wrap;

Lodash是一个JavaScript库,可在underscore.js顶部使用。 Lodash帮助处理数组,集合,字符串,对象,数字等。

_.sortBy()方法创建一个元素数组,该数组按通过每个迭代器运行集合中每个元素的结果以升序排序。而且此方法执行稳定的排序,这意味着它保留了相等元素的原始排序顺序。

用法:

_.sortBy(collection, iteratees)

**参数:**该方法接受上述和以下所述的两个参数:

  • **collection:**此参数保留要迭代的集合。
  • **iteratees:**此参数保存要进行排序的迭代对象,并使用一个参数(值)进行调用。

**返回值:**此方法用于返回新的排序数组。

// Requiring the lodash library  
const _ = require("lodash");     
// Original array  
var object = [ 
  { 'obj':'moto', 'price':19999 }, 
  { 'obj':'oppo', 'price':18999 }, 
  { 'obj':'moto', 'price':17999 }, 
  { 'obj':'oppo', 'price':15999 } ]; 
// Use of _.sortBy() method 
let gfg = _.sortBy(object,  
    [function(o) { return o.obj; }]); 
// Printing the output  
console.log(gfg);
yyyy-MM-dd HH:mm:ss和yyyy-MM-dd hh:mm:ss的区别:

无论是在springboot 还是在其他中,
yyyy-MM-dd HH:mm:ss代表24小时制,
yyyy-MM-dd hh:mm:ss代表12小时制

0.0199
0.7

JS 数值不进制处理 保留N位小数

不进制处理,可以数值截取指定小数位

dealDecimal(val) {
		let num = val.toString() // 先转化为字符串 调用substring方法
		num = num.substring(0, num.indexOf('.') + 3) // 保留两位小数
		return num 
},
JS 小数四舍五入

四舍五入可以使用 toFixed() 方法,toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。

// toFixed() 方法
var num = 2.446242342;
num = num.toFixed(2);  // 输出结果为 2.45

另外像 round()floor()ceil() 等都不能真正的四舍五入,有精度问题。

正则校验 0 ~ 10000000000之间的数值,最多两位小数:

/^(?!0$)([1-9][0-9]{0,9}|0)(\.(?![0]{1,2}$)[0-9]{1,2})?$/

正则 不含特殊字符:

valids.pattern(/^[\u4E00-\u9FA5A-Za-z0-9]*$/, '权益名称只可输入中文、英文及数字'),

JavaScript 中 ?? 与 || 的区别:

相同点:用法相同,都是前后是值,中间用符号连接。根据前面的值来判断最终返回前面的值还是后面的值。

值1 ?? 值2
值1 || 值2

不同点:判断方式不同:

使用 ?? 时,只有当值1 为 null 或 undefined 时才返回 值2;
使用 || 时,值1会转换为布尔值判断,为true返回值1,false 返回值2

// ??
undefined ?? 2	// 2
null ?? 2		// 2
0 ?? 2			// 0
"" ?? 2			// ""
true ?? 2		// true
false ?? 2		// false

// ||`
undefined || 2	// 2
null || 2		// 2
0 || 2			// 2
"" || 2			// 2
true || 2		// true
false || 2		// 2

// 总的来说,??更加适合在不知道变量是否有值时使用。

微前端浏览器缓存问题

微信小程序前端开发踩坑——页面跳转层级限制

微信小程序发版审核不通过提示使用定位相关api:更新神策版本优化定位问题(由于埋点功能涉及的神策包有定位api)

小程序中使用在线图片时注意酌情加时间戳,避免强缓存导致图片不替换问题

微信小程序中不展示滚动条:

view ::-webkit-scrollbar {
			width: 0;
  		height: 0;
  		background-color: transparent;
  		display: none;
}

微信小程序订阅消息相关:

wx.getSetting({
  withSubscriptions: true,
  success: res => {
    // 是否开启订阅消息提醒
    const flag1 = res?.subscriptionsSetting?.mainSwitch
    const itemSettings = res?.subscriptionsSetting?.itemSettings || {}
    // 是否开启指定消息订阅模板id的“一直选中”消息提醒
    const flag2 = itemSettings['5692Ap1Vn2lZ-SUp0pddfscdFRIsnUgN0n77Mrsdxcj-yI']
    console.log('mainSwitch: ', res, flag1, flag2)
    // 开启指定消息订阅模板id的消息提醒但是未“一直”选中
    if (flag1 && !flag2) {
      	console.log('一些操作,例如动画引导用户勾选“总是”')
    } else { // 未开启消息提醒 + 开启并选中总是,直接调用即可
      	wx.requestSubscribeMessage({
        	tmplIds: ['kBcJv55qydGropxX-ofsdzsdvdsxcdsgM6Cwt8pgaWJ9ACM_GAztJXhA'],
          complete: () => {
            	foo()
          },
        })
    }
  },
  fail: () => {
    foo()
  }
})

微信小程序中跳转其他小程序:

wx.navigateToMiniProgram({
  	appId: 'wx1dbe5bddgxvs78247',
  	path: 'packages/goods/detail/index?alias=1y6v0g6wnc4ojvk&shopAutoEnter=1'
})

微信小程序跳转公众号或者其他链接:web-view
(https://mp.weixin.qq.com开头的公众号可以直接跳转,其他类型的地址都需要配置业务域等,配置业务域还要上传文件等…)

微信小程序强制更新:

// wx.getUpdateManager 在 1.9.90 才可用,请注意兼容
const updateManager = uni.getUpdateManager()
updateManager.onUpdateReady(() => {
  uni.showModal({
    title: '更新提示',
    content: '新版本已经准备好,是否马上重启小程序?',
    success: res => {
      if (res.confirm) {
        updateManager.applyUpdate()
      }
    }
  })
})

微信小程序分享相关:

配置在公共文件处,根据情况判断开启场景
// 开启分享、分享到朋友圈
wx.showShareMenu({
  	withShareTicket: true,
  	menus: ['shareAppMessage', 'shareTimeline'],
})
// 关闭分享、分享到朋友圈
wx.hideShareMenu({
  menus: ['shareAppMessage', 'shareTimeline']
})

微信小程序下载文件并打开:

wx.downloadFile({
  url: docUrl,
  success: function(res) {
    wx.openDocument({ filePath: res.tempFilePath })
  },
})

将canvas绘图转换成图片+保存到本地相册

<template>
    <canvas
      id="poster"
      canvas-id="poster"
      canvas-type="2d"
      class="poster-box"
    />
</template>
<script>
    wx.canvasToTempFilePath({
      canvasId: 'poster', // 绘制的canvas id
      success: (res) => {
        var tempFilePath = res.tempFilePath
        wx.saveImageToPhotosAlbum({
          filePath: tempFilePath,
          success(res) {
            wx.showToast({
              title: '已保存到相册',
              icon: 'success',
              duration: 2000
            })
          },
          fail(res) {
            that.$toast({ title: '您未开启保存到相册的权限!' })
          }
        })
      },
      fail: (res) => {
        console.log(1111, res)
      }
    })
  	// 获取在线图片的路径(先保存然后获取的地址
  	uni.getImageInfo({
      src: 'https://xxx.com/image/fitness/qrcode.png',
      success: (qrcode) => {
        // 可以根据图片地址绘制canvas
        ctx.drawImage(qrcode.path, 231, 333, 64, 64)
      }
</script>

微信复制文本:

wx.setClipboardData({
  data: val,
  success: () => {
    wx.getClipboardData({
      success: () => {
        wx.showToast({
          icon: 'none',
          title: '已复制',
        })
      }
    })
  }
})

微信唤起电话键盘:wx.makePhoneCall({ phoneNumber: contactNo })

css粘性定位position:sticky问题采坑

ES12 中有新特性

uni-app支持如下页面生命周期函数:

uni-app使用button的时候设置border的问题,去border边框问题

uni-app的button想要直接使用outline: none;border:0;去掉边框线是不行的,需要设置button::after{ border: none;} 这样就可以解决这个button自带的边框问题了

# npm升级到最新版本
npm install -g npm

# 6.14就是你想要降的版本
npm install npm@6.14 -g
  
# 安装homebrew
/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"

# mac安装指定的node版本:https://www.jianshu.com/p/9ef908190ca1
# Mac 打开新的控制台总是找不到npm:https://www.cnblogs.com/juliazhang/p/14592470.html

# MAC上Git安装与GitHub基本使用:https://www.jianshu.com/p/7edb6b838a2e
  

Mac切换到root用户(su命令行)以及退出:sudo su
退出的话,输入exit

vim模式保存退出 => :wq

npm指令清除npm缓存

npm cache clean --force

Git提交代码规范

用于说明 commit 的类别,只允许使用下面7个标识。

  • feat:新功能(feature)
  • fix:修补bug
  • docs:文档(documentation)
  • style: 格式(不影响代码运行的变动)
  • refactor:重构(即不是新增功能,也不是修改bug的代码变动)
  • test:增加测试
  • chore:构建过程或辅助工具的变动

来回自如的切换淘宝镜像与国外源

1.打开cmd,查看当前镜像地址:

npm get registry

**2.**切换为淘宝镜像:

npm config set registry http://registry.npm.taobao.org/

npm config set registry https://registry.npmmirror.com

3.切换为原本的npm镜像:

npm config set registry https://registry.npmjs.org/

4.同步模块

直接通过 sync 命令马上同步一个模块, 只有 cnpm 命令行才有此功能:

cnpm sync connect

面试题汇总:

一道常被人轻视的前端JS面试题:(https://www.cnblogs.com/xxcanghai/p/5189353.html)

function Foo() {
    getName = function () { alert (1); };
    return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}

//请写出以下输出结果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();




function Foo() { // Foo函数
    getName = function () { alert (1); };
    return this;
}
Foo.getName = function () { alert (2);}; // 为Foo创建了一个getName的静态属性存储了一个匿名函数
Foo.prototype.getName = function () { alert (3);}; // 为Foo的原型对象新创建了一个叫getName的匿名函数
var getName = function () { alert (4);}; // 通过函数变量表达式创建了一个getName的函数
function getName() { alert (5);} // 声明了一个叫getName函数

=>
①Foo.getName();					// 访问Foo函数上存储的静态属性
②getName();							// 变量提升+函数表达式 var getName 覆盖了 function getName(){}
③Foo().getName();				// 先在当前Foo函数作用域内寻找getName变量,没有。再向当前函数作用域上层,即外层作用域内寻找是否含有getName变量,找到了,也就是②中的alert(4)函数,将此变量的值赋值为 function(){alert(1)}。此处实际上是将外层作用域内的getName函数修改了
④getName();							// 直接调用getName函数,相当于 window.getName() ,因为这个变量已经被Foo函数执行时修改了,遂结果与第三问相同,为1new Foo.getName();			// js的运算符优先级问题:点(.)的优先级高于new操作,遂相当于是: new (Foo.getName)();new Foo().getName();		// 首先看运算符优先级括号高于new,实际执行为:(new Foo()).getName(),遂先执行Foo函数,而Foo此时作为构造函数却有返回值this,而this在构造函数中本来就代表当前实例化对象,遂最终Foo函数返回实例化对象。之后调用实例化对象的getName函数,因为在Foo构造函数中没有为实例化对象添加任何属性,遂到当前对象的原型对象(prototype)中寻找getName,找到了。new new Foo().getName();// 运算符优先级问题:最终实际执行为:new ((new Foo()).getName)(); 先初始化Foo的实例化对象,然后将其原型上的getName函数作为构造函数再次new。

// 2 - 4 - 1 - 1 - 2 - 3 - 3

react渲染原理分析:https://www.jianshu.com/p/8dc8e59844c9
H5、C3、es6、es7的新特性:https://www.jianshu.com/p/8f08b6938e4d
vuex常见面试题:https://zhuanlan.zhihu.com/p/163283018
详解 CSS 属性 - 伪类和伪元素的区别:https://segmentfault.com/a/1190000000484493、https://www.cnblogs.com/ihardcoder/p/5294927.html
从零开始搭建一个vue-ssr:https://segmentfault.com/a/1190000019618170
react服务器渲染(ssr):https://segmentfault.com/a/1190000018328145?utm_source=tag-newest、https://www.jianshu.com/p/2db3857272a4
堆和栈的概念和区别:https://blog.csdn.net/pt666/article/details/70876410/
CSS表达式:https://blog.csdn.net/andyyukun/article/details/1676963
V8 引擎:https://zhuanlan.zhihu.com/p/27628685
NodeJs 库:https://zhuanlan.zhihu.com/p/254660191、https://www.cnblogs.com/zhangycun/p/13570459.html
Webpack 构建流程:https://segmentfault.com/a/1190000022991056、https://www.jianshu.com/p/5ded519fc1e7、https://www.cnblogs.com/xjy20170907/p/12910280.html
webpack 持久化缓存:https://www.jianshu.com/p/f4959dab2969#comments
JavaScript异步编程:https://segmentfault.com/a/1190000015711829、https://blog.csdn.net/weixin_40851188/article/details/90648666、http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html

JavaScript 执行过程:https://blog.csdn.net/weixin_33701617/article/details/91461463、https://blog.csdn.net/wexin_37276427/article/details/105028116

JavaScript执行分为两个阶段,编译阶段执行阶段
编译阶段会经过词法分析、语法分析、代码生成步骤生成可执行代码;
执行阶段JS 引擎执行可执行性代码会创建执行上下文,包括绑定this、创建词法环境和变量环境;词法环境创建外部引用(作用域链)和 记录环境(变量对象,let, const, function, arguments), JS 引擎创建执行上下完成后开始单线程从上到下一行一行执行 JS 代码了。

Diff算法(React、Vue):https://zhuanlan.zhihu.com/p/149972619、区别(https://www.jianshu.com/p/fac3d2b112a6)
Redux数据流:https://www.jianshu.com/p/faee4de8e092
react-redux中的数据传递:https://www.jianshu.com/p/5726bb042bda
Nodejs 跨域解决方案:https://blog.csdn.net/boonyaxnn/article/details/91397965、https://www.cnblogs.com/zz009527/p/14741507.html?ivk_sa=1024320u

DNS查询:

DNS(Domain Name System): 负责将域名URL转化为服务器主机IP。
DNS查找流程:首先查看浏览器缓存是否存在,不存在则访问本机DNS缓存,再不存在则访问本地DNS服务器。所以DNS也是开销,通常浏览器查找一个给定URL的IP地址要花费20-120ms,在DNS查找完成前,浏览器不能从host那里下载任何东西。

域名TTL值(Time-To-Live):就是一条域名解析记录在DNS服务器中的存留时间。当各地的DNS服务器接受到解析请求时,就会向域名指定的NS服务器(权威域名服务器)发出解析请求从而获得解析记录;在获得这个记录之后,记录会在DNS服务器(各地的缓存服务器,也叫递归域名服务器)中保存一段时间,这段时间内如果再接到这个域名的解析请求,DNS服务器将不再向NS服务器发出请求,而是直接返回刚才获得的记录;而这个记录在DNS服务器上保留的时间,就是TTL值。

影响DNS缓存的因素
① 服务器可以设置TTL值表示DNS记录的存活时间。本机DNS缓存将根据这个TTL值判断DNS记录什么时候被抛弃,这个TTL值一般都不会设置很大,主要是考虑到快速故障转移的问题。
② 浏览器DNS缓存也有自己的过期时间,这个时间是独立于本机DNS缓存的,相对也比较短,例如chrome只有1分钟左右。
③ 浏览器DNS记录的数量也有限制,如果短时间内访问了大量不同域名的网站,则较早的DNS记录将被抛弃,必须重新查找。不过即使浏览器丢弃了DNS记录,操作系统的DNS缓存也有很大机率保留着该记录,这样可以避免通过网络查询而带来的延迟。

最佳实践:当客户端的DNS缓存为空时,DNS查找的数量与Web页面中唯一主机名的数量相等。所以减少唯一主机名的数量就可以减少DNS查找的数量。然而减少唯一主机名的数量会潜在地减少页面中并行下载的数量,避免DNS查找降低了响应时间,但减少并行下载可能会增加响应时间。当页面的组件量比较多的时候,可以考虑将组件分别放到至少2-4个主机名 以获得最大收益。

前端应该了解的dns解析那些事:https://blog.csdn.net/xiaoluodecai/article/details/112596978
网络协议基础介绍-TCP、UDP、HTTP:https://blog.csdn.net/qq_41838901/article/details/98748353、https://blog.csdn.net/qq_31332467/article/details/79217262
webpack怎么打包css:https://www.html.cn/qa/css3/12773.html、打包js、css、图片:https://www.jianshu.com/p/52982767189b
SocketIO、Websocket断开重连问题排查方案:https://blog.csdn.net/wk19920726/article/details/109023816

重排重绘:
闭包,平时工作中的应用场景:
深浅拷贝:
防抖节流,怎么实现:
跨域,本地开发有跨域怎么处理,webpack中proxy代理:
promise,缺点,用promise封装ajax:
MVVM:
ES6新特性在浏览器打包完以后不兼容怎么处理:
Vue的生命周期,哪一阶段实例创建,哪一阶段挂载到dom上:
vue-cli搭载过项目,static和assets区别:
Vuex和Redux区别:
Store是什么:
使用Redux异步处理怎么做的:
react生命周期:
React的constructor、里面可以直接用props吗,super()传参props与不传的区别:
setState是同步还是异步的,第二个参数
虚拟Dom和diff算法:
遍历循环中key为什么最好不要用index:
React组件通信,context:
React中的高阶组件:
纯函数:
受控组件与非受控组件:
父组件怎么调用子组件的方法:
React性能优化:
怎么实现路由懒加载:
React-Router路由模式:
Hooks,为什么要使用hooks:
useEffect依赖对象可以放对象吗:
webpack打包做过什么优化:
sass-loader、style-loader、css-loader:
less里面如何封装一块代码块:

笔试题记忆点:继承的方法、继承的具体例题

Vue与React的区别与优缺点(必问!!):

浅谈懒加载技术:https://www.cnblogs.com/jsjx-xtfh/p/9587911.html
一次搞定前端“四大手写”:https://zhuanlan.zhihu.com/p/160315811

• JS类型转换之 valueOf 和 toString:

Object.prototype.valueOf():用 MDN 的话来说,valueOf() 方法返回指定对象的原始值。
Object.prototype.toString():toString() 方法返回一个表示该对象的字符串。每个对象都有一个 toString() 方法,当对象被表示为文本值时或者当以期望字符串的方式引用对象时,该方法被自动调用。

转换规律
如果只改写 valueOf() 或是 toString() 其中一个,会优先调用被改写了的方法,而如果两个同时改写,则会像 Number 类型转换规则一样,优先查询 valueOf() 方法,在 valueOf() 方法返回的是非原始类型的情况下再查询 toString() 方法。

let x = {
  	toString: () => {
        console.log('TpString')
        return 20
    },
    valueOf: () => {
        console.log('ValueOf')
        return '30'
    }
}
console.log(x == '20')
console.log(x == 30)

// => valueOf false
// => valueOf true

• JS中几种常见的高阶函数:(https://www.cnblogs.com/goloving/p/8361705.html)

高阶函数:英文叫Higher-order function。JavaScript的函数其实都指向某个变量。

高阶函数是至少满足下列一个条件的函数

  • 接受一个或多个函数作为输入
  • 输出一个函数

编写高阶函数,就是让函数的参数能够接收别的函数。

一个最简单的高阶函数:

function add(x, y, f) {
    return f(x) + f(y);
}
// 当调用add(-5, 6, Math.abs)时,参数x,y和f分别接收-5,6和函数Math.abs,根据函数定义,可以推导计算过程为:
// x = -5;
// y = 6;
// f = Math.abs;
// f(x) + f(y) ==> Math.abs(-5) + Math.abs(6) ==> 11;
// return 11;

//用代码验证一下:
add(-5, 6, Math.abs); // 11
常见的高阶函数:

① map/reduce

function pow (x) {
    return x * x;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]

// map()传入的参数是pow,即函数对象本身。
// 不需要map(),写一个循环,也可以计算出结果:

var f = function (x) {
    return x * x;
};
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var result = [];
for (var i=0; i<arr.length; i++) {
    result.push(f(arr[i]));
}
// 的确可以,但是,从上面的循环代码,我们无法一眼看明白“把f(x)作用在Array的每一个元素并把结果生成一个新的Array”。

/* 所以,map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数,比如,把Array的所有数字转为字符串:*/
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']
// 只需要一行代码。
// 再看reduce的用法。
// Array的reduce()把一个函数作用在这个Array的[x1, x2, x3...]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算,其效果就是:
// [x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
// 比方说对一个Array求和,就可以用reduce实现:
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
    return x + y;
}); // 25

② filter

filter也是一个常用的操作,它用于把Array的某些元素过滤掉,然后返回剩下的元素。和map()类似,Array的filter()也接收一个函数,和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。用filter()这个高阶函数,关键在于正确实现一个“筛选”函数。

// 回调函数:filter()接收的回调函数,其实可以有多个参数。通常我们仅使用第一个参数,表示Array的某个元素。回调函数还可以接收另外两个参数,表示元素的位置和数组本身:
var arr = ['A', 'B', 'C'];
var r = arr.filter(function (element, index, self) {
    console.log(element); // 依次打印'A', 'B', 'C'
    console.log(index); 	// 依次打印0, 1, 2
    console.log(self); 		// self就是变量arr
    return true;
});

// 利用filter,可以巧妙地去除Array的重复元素:
'use strict';
var r, arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry'];
r = arr.filter(function (element, index, self) {
    return self.indexOf(element) === index;
});
alert(r.toString());
// 去除重复元素依靠的是indexOf总是返回第一个元素的位置,后续的重复元素位置与indexOf返回的位置不相等,因此被filter滤掉了。

③ sort排序算法:因为Array的sort()方法默认把所有元素先转换为String再排序,结果’10’排在了’2’的前面,因为字符’1’比字符’2’的ASCII码小。如果不知道sort()方法的默认排序规则,直接对数字排序,绝对栽进坑里!幸运的是,sort()方法也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序。

// 要按数字大小排序,我们可以这么写:
var arr = [10, 20, 1, 2];
arr.sort((x, y) => x - y); // [1, 2, 10, 20]

// 如果要倒序排序,我们可以把大的数放前面:
var arr = [10, 20, 1, 2];
arr.sort((x, y) => y - x)); // [20, 10, 2, 1]

// 默认情况下,对字符串排序,是按照ASCII的大小比较的,现在,排序应该忽略大小写,按照字母序排序。
// 要实现这个算法,不必对现有代码大加改动,只要我们能定义出忽略大小写的比较算法就可以:
var arr = ['Google', 'apple', 'Microsoft'];
arr.sort(function (s1, s2) {
    x1 = s1.toUpperCase();
    x2 = s2.toUpperCase();
    if (x1 < x2) {
        return -1;
    }
    if (x1 > x2) {
        return 1;
    }
    return 0;
}); // ['apple', 'Google', 'Microsoft']

// 忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),再比较。
// sort()方法会直接对Array进行修改,它返回的结果仍是当前Array:
var a1 = ['B', 'A', 'C'];
var a2 = a1.sort();
a1; 				// ['A', 'B', 'C']
a2; 				// ['A', 'B', 'C']
a1 === a2; 	// true, a1和a2是同一对象

项目相关:

react版本:16.12.0

webpack版本:4.41.4

redux版本:4.0.5

ant-design版本:4.8.4

echarts版本:4.5.0(echarts-for-react: 2.0.16)

eslint版本:6.8.0

less版本:3.10.3

less-loader:5.0.0

日常积累:

1. PV、UV、IP分别是什么意思?

PV(Page View)访问量, 即页面浏览量或点击量,衡量网站用户访问的网页数量;在一定统计周期内用户每打开或刷新一个页面就记录1次,多次打开或刷新同一页面则浏览量累计。

UV(Unique Visitor)独立访客,统计1天内访问某站点的用户数(以cookie为依据);访问网站的一台电脑客户端为一个访客。可以理解成访问某网站的电脑的数量。网站判断来访电脑的身份是通过来访电脑的cookies实现的。如果更换了IP后但不清除cookies,再访问相同网站,该网站的统计中UV数是不变的。如果用户不保存cookies访问、清除了cookies或者更换设备访问,计数会加1。00:00-24:00内相同的客户端多次访问只计为1个访客。

IP(Internet Protocol)独立IP数,是指1天内多少个独立的IP浏览了页面,即统计不同的IP浏览用户数量。同一IP不管访问了几个页面,独立IP数均为1;不同的IP浏览页面,计数会加1。 IP是基于用户广域网IP地址来区分不同的访问者的,所以,多个用户(多个局域网IP)在同一个路由器(同一个广域网IP)内上网,可能被记录为一个独立IP访问者。如果用户不断更换IP,则有可能被多次统计。

会话次数(网站访问量)Session
会话是指在指定的时间段内在您的网站上发生的一系列互动,所以会话次数是一段时间内用户向您的网站发起的会话(Session)总数量。一次会话会浏览一个或多个页面

一、ES6 常用功能总结

1. let / const

es6 以前,都是用 var 关键字来标识,这样有个变量提升的坑。在 es6 中,添加了 let 和 const 两个关键字,let 定义变量,const 定义常量,并且添加了块级作用域。

ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。const声明一个只读的常量。

1、let和const的相同点:

① 只在声明所在的块级作用域内有效。
② 不提升,同时存在暂时性死区,只能在声明的位置后面使用。
③ 不可重复声明。

变量提升:es6 以前,js 引擎将所有的变量都提到最前面,初始化为 undefined。

2、let和const的不同点:

① let声明的变量可以改变,值和类型都可以改变;const声明的常量不可以改变,这意味着,const一旦声明,就必须立即初始化,不能以后再赋值。
② 数组和对象等复合类型的变量,变量名不指向数据,而是指向数据所在的地址。const只保证变量名指向的地址不变,并不保证该地址的数据不变,所以将一个复合类型的变量声明为常量必须非常小心。
想让定义的对象或数组的数据也不能改变,可以使用object.freeze(arr|object)进行冻结。冻结指的是不能向这个对象或数组添加新的属性,不能修改已有属性的值,不能删除已有属性。

const arr = [];
Object.freeze(arr);
arr[0] = 1;							// 不报错,但数据改变无效
console.log(arr.length); // 输出:0
补充:变量提升

JavaScript 代码的执行分为两个阶段。第一个阶段在当前词法环境中注册所有的变量和函数声明,简单说就是 解析,解析完成之后,第二个阶段的 JavaScript 执行就开始了!
JS中创建函数有两种方式:函数声明式和函数字面量式。只有函数声明才存在函数提升。
JavaScript 仅提升声明,而不提升初始化。如果你先使用的变量,再声明并初始化它,变量的值将是 undefined。
1:所有的声明都会提升到作用域的最顶上去;
2:同一个变量只会声明一次,其他的会被忽略掉;
3:函数声明的优先级高于变量声明的优先级,并且函数声明和函数定义的部分一起被提升。
*4:优先级:已初始化的一般变量 > 函数 > 函数参数 > 未初始化的一般变量

// 例1:已初始化的一般变量 优先于 函数	
// 解析:其实就是 函数是整个函数提升,变量只提升声明部分,所以 var a = 1; => var a; a = 1;
//			所以当在 var a = 1 后面打印输出时 相当于 a = 1 覆盖了上面的函数a
//				   							前面打印输出时 相当于只做了变量提升处理,所以为函数a
function a () {
  	var b = 10;
}
console.log(a) // function a() { var b = 10; }
var a = 1;  
console.log(a); // 1

// 例2:函数 优先于 未初始化的一般变量
var a;  
function a () {
  	var b = 10;
}  
console.log(a); // function a() { var b = 10; }

2. 模版字符串(template string)

使用 ``(反引号作为标识),既可以当作普通字符串使用,又可以用来定义多行字符串,还可以在字符串中嵌入变量(使用模板变量${})。

如果遇到特殊的字符 比如`,则需要在前面加转义字符 \(反斜杠)。
这样使用起来很方便,避免字符串拼接中出现的不必要的错误,而且更简单简洁,最重要的是人易懂。需要注意的是 ${ } 要和 `` 一起使用不然不会被解析。

3. 解构赋值 + 拓展运算符

解构赋值属于浅拷贝!
顾名思义,就是先解构,再赋值!

// 比如先定义一个对象和数组:
var obj = {
    a: 1,
    b: 2,
    c: 3
}
var arr = [1,2,3]
// 在 es6 以前,这样获取属性的值:	obj.a;		obj.b;		arr[i];
// 在 es6 中:	
const { a, c } = obj;
const [x, y, z] = arr;
// 很简单就可以获取对象的属性和数组的值,看下编译得过程:
var a = obj.a, c = obj.c;
var arr = [1, 2, 3];
var x = arr[0], y = arr[1], z = arr[2];

4. 块级作用域

代码块(用{}表示)
块就是代码中我们用两个{ }包起来的内容

5. 函数默认参数

function test (a, b = 0) {
		// ...
}
// 编译一下:
function test(a) {
		var b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;  
		// ...  
}

6. 箭头函数() => {}

定义:ES6箭头函数结构为 X => X*X

等价:function(X) { return X*X }

箭头函数特点:
  • 更简洁的语法

  • 不绑定this,捕获其所在上下文的 this 值 作为自己的 this 值

  • (是匿名函数,不能作为构造函数)不能使用new

  • 不绑定arguments,用rest参数…解决

  • 使用call()和apply()调用,只是传入了参数而已,对 this 并没有什么影响

  • 箭头函数没有原型属性

  • 不能简单返回对象字面量(返回对象时需要用小括号包起来,因为大括号被占用解释为代码块了)

  • 箭头函数不能当做Generator函数,不能使用yield关键字

  • 箭头函数不能换行

    let a = ()
              =>1; //SyntaxError: Unexpected token =>
    
箭头函数与普通函数的区别?

• 箭头函数都是匿名函数
this的指向不同
1) 箭头函数的 this 永远指向其上下文的 this ,任何方法都改变不了其指向,如 call() , bind() , apply()
2) 普通函数的this指向调用它的那个对象

箭头函数可以作为构造函数吗?为什么?

箭头函数是匿名函数,不能作为构造函数,不能使用new
首先 => 构造函数的new做了什么?
简单来说,分为四步: 1 JS内部首先会先生成一个对象;2 再把函数中的this指向该对象;3 然后执行构造函数中的语句;4 最终返回该对象的实例。
所以 => 因为箭头函数没有自己的 this ,它的 this 其实是继承了外层执行环境中的 this ,且 this 指向永远不会随在哪里调用、被谁调用而改变,所以箭头函数不能作为构造函数使用,或者说构造函数不能定义成箭头函数,否则用 new 调用是会报错。

7.Set()

Set()是有序列表的结合,而且是没有重复的,因此Set()对象可以用于数组的去重。

数组去重

1)[...new Set([1,2,3,1,'a',1,'a'])]

2)Array.from(new Set([1,2,3,1,'a',1,'a'])

3)[1,2,3,1,‘a’,1,‘a’].filter((item, i, arr => arr.indexOf(item) === i))

Set中包含的方法:add()、has、delete()、clear()

Set也能用来保存NaN和undefined, 如果有重复的NaN, Set会认为就一个NaN(实际上NaN!=NaN);

实例Set以后的对象拥有这些属性和方法:

​ 属性:Set.prototype 、Set.prototype.size
​ 方法:Set.prototype.add()、Set.prototype.clear()、Set.prototype.delete()、Set.prototype.entries()
     Set.prototype.forEach()、Set.prototype.has()、Set.prototype.values()、Set.prototype[@@iterator] ()

8. 模块化

​ 在现在多个人开发同一个项目很常见,每个人负责不同的模块,还有可能会几个人使用同一个模块,在这种情况下,模块化就很重要!其实使用起来也很简单,比如说有模块A、B、C三个 js 文件,各自在其中定义好自己的代码,使用 export 关键字导出自己的东西,别人使用时用 import 关键字引用即可,模块化的处理工具有 webpack、rollup 等。一个模块就是一个实现特定功能的文件。

1. 模块化开发的4点好处:

1.避免变量污染,命名冲突
2.提高代码复用率
3.提高维护性
4.依赖关系的管理

2. 实现模块化

开始的模块化体现:函数 => 对象 => 立即执行函数

1)commonJS
  • 运行时加载,一个模块就是一个对象,加载一个模块就是加载这个模块的所有方法,然后读取其中需要的方法。
  • require 加载模块。
  • module.exports 对外暴露接口。

​ 根据commonJS规范,一个单独的文件是一个模块,每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块读取,除非为global对象的属性。模块只有一个出口,module.exports对象,我们需要把模块想要输出的内容放入该对象。加载模块用require方法,该方法读取一个文件并且执行,返回文件内部的module.exports对象。
CommonJS 是代码同步加载,因为服务端的模块都是放在硬盘,加载的时间就是硬盘的读取时间。所以适用于服务端,并不适用于浏览器。

2)AMD (Asynchronous Module Definition) 异步模块定义
  • 异步并行加载,不阻塞 DOM 渲染。
  • 提前执行(预执行),在模块使用之前就已经执行完毕。

​ 它是一个在浏览器端模块化开发的规范,由于不是js原生支持,使用AMD规范进行页面开发需要用到RequireJS函数库(实际上AMD是RequireJS在推广过程中对模块定义的规范化的产出)。
​ AMD推崇的是依赖前置,在定义模块的时候就有声明其依赖的模块。(被提前罗列出来并会被提前下载并执行,后来做了改进,可以不用罗列依赖模块,允许在回调函数中就近使用require引入并下载执行模块。)

requireJS主要解决两个问题:
1 多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器。
2 js加载的时候浏览器会停止页面渲染,加载文件愈多,页面失去响应的时间愈长。

3)CMD (common module definition)
  • 异步并行加载,也不会阻塞 DOM 渲染。
  • 按需执行(需要的时候才执行)

​ 就像AMD有个requireJS,CMD有个浏览器实现的sea.js,sj要解决的问题和rj一样,只不过在模块定义方式和模块加载时机上有所不同(cmd是sea.js在推广过程中的规范化产出),sea.js是另一种前端模块化工具,它的出现缓解了requireJS的几个痛点。CMD推崇依赖就近,只有在用到某模块的时候再去require。

4)ES6 Modules模块化实现
  • 编译时加载,在模块编译时就完成加载,引用的时候只加载需要的方法,其他方法不加载。
  • 默认严格模式(严格模式的顶层 this 指向 undefined,而不是 windows);
  • js后缀不可省略;
  • 变量只在本模块作用域内有效,也就是局部变量;
  • 同一个模块加载多次,只执行一次。

import引入, module.export导出

8.1、node中的模块化导入导出和ES6的区别?

输出的是引用还是拷贝
CommonJS :输出的是值的拷贝,不会受其内部影响。
ES6 :输出的是值的引用,会受其内部影响。

模块内顶层 this 对象
CommonJS :指向的是当前模块对象。
ES6 :指向的是 undefined。

node commonjs规范模块化
  1. module对象为模块运行时生成的标识对象,提供模块信息;
  2. exports为模块导出引用数据类型时,modulex.exports与exports指向的是同一对象,require导入的是module.exports导出的对象;
  3. 同一模块导入后存在模块缓存,后续多次导入从缓存中加载;
  4. 源模块的引用与导入该模块的引用是同一对象;
  5. 最好不要同时使用module.exports与exports,导出对象使用module.exports,导出变量使用exports。
es6规范模块化:
  1. es6通过 export 和 import 实现导出导入功能;
  2. es6 export支持导出多个变量,export default 是 export 形式的语法糖,表示导出default接口;
  3. import * as xx from ‘xx.js’ 导入的是Module对象,包含 default接口 和其他变量接口;
  4. 多个模块导入多次,只会执行一次;
  5. 导出引用数据类型时,导出对象与导入对象指向同一个变量,修改导出变量对象,源对象也会发生改变;
  6. 导出单个变量建议使用export default,导出多个变量使用export。

9. promise(https://es6.ruanyifeng.com/#docs/promise)

  • promise是一个对象,对象和函数的区别就是对象可以保存状态,函数不可以(闭包除外)
  • 并未剥夺函数return的能力,因此无需层层传递callback,进行回调获取数据
  • 代码风格,容易理解,便于维护
  • 多个异步等待合并便于解决
  • promise构造函数是同步执行的,then方法是异步执行
promise详解
new Promise(
  function (resolve, reject) {
    // 一段耗时的异步操作
    resolve('成功', 4) // 数据处理完成
    // reject('失败') // 数据处理出错
  }
).then(
  (res) => {console.log(res)},  // 成功,
  (err) => {console.log(err)} // 失败
)
  • resolve作用:将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
    reject作用:将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
  • promise有三个状态:
    1、pending[待定]初始状态
    2、fulfilled[实现]操作成功
    3、rejected[被否决]操作失败
    当promise状态发生改变,就会触发then()里的响应函数处理后续步骤;
    promise状态一经改变,不会再变。
  • Promise对象的状态改变,只有两种可能:
    从pending变为fulfilled
    从pending变为rejected。
    这两种情况只要发生,状态就凝固了,不会再变了。

常见用法
异步操作和定时器放在一起,如果定时器先触发,就认为超时,告知用户;
例如我们要从远程的服务器获取资源,如果5000ms还没有加载过来我们就告知用户加载失败

**Promise.all **

可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。

1.它接受一个数组作为参数。

2.数组可以是Promise对象,也可以是其它值,只有Promise会等待状态改变。

3.当所有的子Promise都完成,该Promise完成,返回值是全部值的数组,数组顺序不变

4.如果有任何一个失败,该Promise失败,返回值是第一个失败的子Promise的结果。

**Promise.race **

区别在于 它一旦有一个promise返回就算完成(但是进程不会立即停止),该promise对象 resolve 以后,立即把 resolve 的值作为 Promise.race() resolve 的结果;如果该对象 reject,Promise.race也会立即 reject。

1.它接受一个数组作为参数。

2.数组可以是Promise对象,也可以是其它值,只有Promise会等待状态改变。

3.当某一个Promise完成,该Promise完成,返回值是该Promise的结果。

async await 与 promise 哪个好用?

  1. Promise
    Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大,简单地说,Promise好比容器,里面存放着一些未来才会执行完毕(异步)的事件的结果,而这些结果一旦生成是无法改变的。
  2. async await
    async await也是异步编程的一种解决方案,他遵循的是Generator 函数的语法糖,他拥有内置执行器,不需要额外的调用直接会自动执行并输出结果,它返回的是一个Promise对象。
  • 两者的区别
  1. Promise的出现解决了传统callback函数导致的“地域回调”问题,但它的语法导致了它向纵向发展行成了一个回调链,遇到复杂的业务场景,这样的语法显然也是不美观的。而async await代码看起来会简洁些,使得异步代码看起来像同步代码,await的本质是可以提供等同于”同步效果“的等待异步返回能力的语法糖,只有这一句代码执行完,才会执行下一句。
  2. async await与Promise一样,是非阻塞的。
  3. ???async await是基于Promise实现的,可以说是改良版的Promise,它不能用于普通的回调函数。

Q1:假设同时发十个请求,要求十个请求都返回后再做其他操作?

function one () {
			console.log(1111)
			return 'i am one'
} 
function two () {
      return new Promise((res, rej) => {
						setTimeout(()=> {
            			console.log(22222)
                  res('i am two')
            }, 1000)
			})
}
function three () {
			console.log(333333)
			return 'i am three'
}
async function run () {
  		one()
      await two() // 受await阻断影响,必须要在fn()执行完后才能执行,同时如果fn()没有返回结果,就是说没有reslove()的话,那么下面的代码将不会执行
      three();
      console.log(44444)
      // 1111 22222 333333 44444
}
run()

Promise.all([promise1,promise2]).then(function(){})
Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p1 = req1();
const p2 = req2();
const p3 = req3();
// 要求三个异步请求返回值都是promise实例
const p = Promise.all([p1, p2, p3]);
p.then(res => {
    console.log("三个异步请求都执行完成");
})

Q2:如何使用Promise来同时请求多个数据?
需求:多个请求,不管成功或失败,都希望拿到这些请求的结果,比如,第一个请求失败了,后面的请求还是继续,请问怎么实现?
思路

  1. 将所有的异步请求的结果放入一个数组(加 .then 将成功或是失败的结果都 return 出去)

  2. 使用 Promise.all 来处理(失败的结果处理一下再return)

    getPromiseArray(groupIds) {
      let promiseArray = []
      for (let groupId of groupIds) {
        // 加 .then 将成功或是失败的结果都 return 出去   
      	let promise = this.$http.get(groupId).then(res => { return res }, err => { return err.toString() })
        promiseArray.push(promise)
      }
      return promiseArray
    }
    
    getAllResults(groupIds, callBack) {
      let allResults = []
      Promise.all(this.getPromiseArray(groupIds)).then(function (values) {
        console.log(values,'values')
        for (let i = 0; i < values.length; i++) {
          if (values[i].data) {
            allResults.push(values[i].data)
          } else { // 失败的结果处理一下再return
            allResults.push(values[i])
          }
        }
        callBack(allResults)
      })
    }
    

10.async await函数

awiat必须在使用async的情况下才能使用,它的作用是阻断主函数的执行,等待异步执行的结果返回后才会向下继续执行。

受await阻断影响,必须要在await fn()执行完后才能执行,同时如果fn()没有返回结果,就是说没有reslove()的话,那么下面的代码将不会执行。所以暗示我们不能在最外层代码中使用await,因为不在async函数内。

await函数不能单独使用,而且async函数返回的是一个Promise对象,可以使用then函数添加回调函数。当函数执行的时候,一旦遇到await函数就会先返回一个Promise对象,等到异步操作完成,再去执行后面的语句。如果 await 后面的异步操作出错,那么等同于 async 函数返回的 Promise 对象被 reject。

11. class!!!!!

实际上应该说是构造函数,通过原型实现的

12.keys,values,entries

用于遍历数组,它们都返回一个遍历器对象,可以用for…of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

for (let index of ['a', 'b'].keys()) {
		console.log(index);
}
// 0
// 1

for (let elem of ['a', 'b'].values()) {
		console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {
		console.log(index, elem);
}
// 0 "a"
// 1 "b"

13、Generator!!!!!

1.相关概念

Generator(生成器)是一类特殊的函数,跟普通函数声明时的区别是加了一个*号。

Iterator(迭代器):当我们实例化一个生成器函数之后,这个实例就是一个迭代器。可以通过next()方法去启动生成器以及控制生成器的是否往下执行。

yield/next:这是控制代码执行顺序的一对好基友。
通过yield语句可以在生成器函数内部暂停代码的执行使其挂起,此时生成器函数仍然是运行并且是活跃的,其内部资源都会保留下来,只不过是处在暂停状态。
在迭代器上调用next()方法可以使代码从暂停的位置开始继续往下执行。

// 首先声明一个生成器函数
function *main() {
    console.log('starting *main()');
    yiled; // 打住,不许往下走了
    console.log('continue yield 1');
    yield; // 打住,又不许往下走了
    console.log('continue yield 2');
}
// 构造处一个迭代器it
let it = main(); 

// 调用next()启动*main生成器,表示从当前位置开始运行,停在下一个yield处
it.next(); // 输出 starting *main()

// 继续往下走
it.next(); // 输出 continue yield 1

// 再继续往下走
it.next(); // 输出 continue yield 2

2.消息传递
3.Generator在流程控制中的应用
4.Generator+Promise实现完美异步
5.async和await
6.yield委托

二、综合

css伪类和伪元素

在这里插入图片描述

在这里插入图片描述

1、清除浮动的几种方式,及原理?

  • ::after / <br> / clear: both
  • 创建父级 BFC(overflow:hidden)
  • 父级设置高度

BFC (块级格式化上下文,是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。

触发条件:
  • 根元素,即HTML标签
  • 绝对定位的元素:position: absolute/fixed
  • 内联块、表格单元格、表格标题 :display: inline-block / table-cell / table-caption / flow-root
  • 浮动:元素的float!== none
  • 块元素的ovevflow !== visible
根据MDN的定义,根元素已经触发了BFC
BFC规则特性:
  • 属于同一个 BFC 的两个相邻 Box 垂直排列
  • 属于同一个 BFC 的两个相邻 Boxmargin 会发生重叠
  • BFC 的区域不会与 float\box 的元素区域重叠
  • 计算 BFC 的高度时,浮动子元素也参与计算
  • 文字层不会被浮动层覆盖,环绕于周围
BFC的用途:
  1. 自适应两栏布局
  2. 阻止元素被浮动覆盖
  3. 清除内部浮动
  4. 阻止margin重叠
  5. 解决高度塌陷问题

1.1高度塌陷问题

父元素没有设置大小(或不设置高度)子元素浮动,子元素会跳出父元素的边界(脱标),当父元素的高度为auto时,父元素的高度直接为0。简单的说,就是本应在父盒子内部的元素跑到了外部。

解决方案:
  1. 给父盒子设置高度

  2. 父盒子设置overflow属性

    overflow:auto; 		/* 有可能出现滚动条,影响美观。*/
    overflow:hidden;  /* 可能会带来内容不可见的问题。*/
    
  3. 给父盒子末尾添加一个空盒子,并设置成清除浮动。(不推荐,因为引入了不必要的冗余元素。)
    优点:通俗易懂,易于掌握;
    缺点:添加了无意义标签,不易于后期维护,违背了结构和表现分离的标准。

  4. 使用after伪元素清除浮动。(最常用,代表网站:百度、淘宝、网易等。)
    优点:这个方法是空盒子方案的改进,一种纯css的解决方案,没有在页面上添加无意义标签,使页面结构更完整。
    缺点:低版本IE不兼容。

    /* 父元素中加一个类名为clearfix */
    .clearfix:after {
          content: "."; 			/* 尽量不要为空,一般写一个. */
      		height: 0; 					/* 盒子高度为0,看不见 */
          display: block; 		/* 插入伪元素是行内元素,要转化为块级元素 */
      		visibility: hidden; /* content由内容,要将内容隐藏 */
          clear: both;
          overflow: hidden;
    }
    .clearfix {
      		*zoom: 1; 					/* *只有IE6/7识别 */
    }
    
  5. 使用before和after双伪元素清除浮动(推荐,代表网站:小米、腾讯等)

    .clearfix:before,
    .clearfix:after {
           content: "";
           display: table;
    }
    .clearfix:after {
           clear: both;
    }
    .clearfix {
    			*zoom: 1;
    }
    
  6. 给父盒子添加border、padding-top

2、说一下闭包?!!!!!

闭包的实质是因为函数嵌套而形成的作用域链
闭包的定义即:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,闭包就是能够读取其他函数内部变量的函数。所以在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的特点:

1.让外部访问函数内部变量成为可能;
2.可以避免使用全局变量,防止全局变量污染;
3.可以让局部变量常驻在内存中;
4.会造成内存泄漏(有一块内存空间被长期占用,而不被释放)。

应用场景:
• 函数防抖
• 采用函数引用方式的setTimeout调用(给setTimeout第一个参数函数传参)
// 原生的setTimeout传递的第一个函数不能带参数,通过闭包可以实现传参效果
function f1(a) {
    function f2() {
        console.log(a);
    }
    return f2;
}
var fun = f1(1);
setTimeout(fun,1000);	// 一秒之后打印出1
• 迭代器(执行一次函数往下取一个值)
var arr = ['aa','bb','cc'];
function incre(arr) {
  	var i = 0;
  	return function () {
      	// 这个函数每次被执行都返回数组arr中 i下标对应的元素
      	return arr[i++] || '数组值已经遍历完';
    }
}
var next = incre(arr);
console.log(next());	// aa
console.log(next());	// bb
console.log(next());	// cc
console.log(next());	// 数组值已经遍历完
• 埋点(缓存)
function count() {
    var num = 0;
    return function () {
        return ++num
    }
}
var getNum = count();
var getNewNum = count();
document.querySelectorAll('button')[0].onclick = function() {
		console.log('点击加入购物车次数: '+getNum());
}
document.querySelectorAll('button')[1].onclick = function() {
  	console.log('点击付款次数: '+getNewNum());
}
• 事件+循环(循环赋值)

按照以下方式添加事件,打印出来的i不是按照序号的。形成原因就是操作的是同一个词法环境,因为onclick后面的函数都是一个闭包,但是操作的是同一个词 法环境。

var lis = document.querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
  	lis[i].onclick = function () {
      alert(i)
    }
} 

解决办法:使用匿名函数之后,就形成一个闭包, 操作的就是不同的词法环境

var lis = document.querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
  	(function (j) {
    		lis[j].onclick = function () {
      		alert(j)
        }
    })(i)
}

javascript语言实现继承机制的核心

就是 (原型),而不是Java语言那样的类式继承。Javascript解析引擎在读取一个Object的属性的值时,会沿着 (原型链)向上寻找,如果最终没有找到,则该属性值为undefined;如果最终找到该属性的值,则返回结果。与这个过程不同的是,当javascript解析引擎执行“给一个Object的某个属性赋值”的时候,如果当前Object存在该属性,则改写该属性的值,如果当前的Object本身并不存在该属性,则赋值该属性的值。

原型链是什么?

当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。

  1. 除Object以外的所有对象的prototype__proto__(原型对象)都指向Object.prototype(Object.prototype.proto == null)
  2. 所有函数(Function也是函数)的__proto__都指向Function.prototype
  3. 所有对象(函数也是对象,只不过是具有特殊功能的对象而已)的prototypeconstructor都指向对象本身

Object

**Object.create()**方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

const person = {
  isHuman: false,
  printIntroduction: function() {
    console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
  }
};
const me = Object.create(person);
me.name = 'Matthew'; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // inherited properties can be overwritten

me.printIntroduction();
// expected output: "My name is Matthew. Am I human? true"
person.printIntroduction();
// expected output: "My name is undefined. Am I human? false"

3、说一下继承的几种方式及优缺点?

说比较经典的几种继承方式并比较优缺点就可以了

  1. 原型继承,将子对象的prototype指向父对象的一个实例
  2. 借用构造函数继承,使用call或apply方法更改子类函数的作用域,使this执行父类构造函数
  3. 组合继承,综合使用构造函数继承和原型链继承
  4. 原型式继承,类似Object.create 用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象,结果是将子对象的 __proto__ 指向父对象
  5. 寄生式继承,二次封装原型式继承 并拓展
  6. 寄生组合式继承:改进组合继承,利用寄生式继承的思想继承原型

原型链继承

  • 优点:可继承构造函数的属性,父类构造函数的属性,父类原型的属性
  • 缺点:无法向父类构造函数传参;且所有实例共享父类实例的属性,若父类共有属性为引用类型, 一个子类实例更改父类构造函数共有属性时会导致继承的共有属性发生变化。

借用构造函数(类式继承)

  • 优点:解决了原型链继承的两个问题(1.传参, 2.子类因此可以继承父类共有属性)
  • 缺点:不可继承父类的原型链方法,构造函数不可复用

组合式继承

  • 组合式继承是比较常用的一种继承方法,其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又保证每个实例都有它自己的属性。
  • 优点:可继承父类原型上的属性,且可传参;每个新实例引入的构造函数是私有的
  • 缺点:会执行两次父类的构造函数,消耗较大内存,子类的构造函数会代替原型上的那个父类构造函数

原型式继承

  • 缺点:共享引用类型

4.1、http缓存策略?

浏览器每次发起请求时,先在本地缓存中查找结果以及缓存标识,根据缓存标识来判断是否使用本 地缓存。如果缓存有效,则使用本地缓存;否则,则向服务器发起请求并携带缓存标识。根据是否 需向服务器发起HTTP请求,将缓存过程划分为两个部分: 强制缓存和协商缓存,强缓优先于协商缓存。

• 强缓存,服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行比较缓存策略。
• 协商缓存,让客户端与服务器之间能实现缓存文件是否更新的验证、提升缓存的复用率,将缓存信息中的Etag和Last-Modified 通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存。

HTTP缓存都是从第二次请求开始的:
第一次请求资源时,服务器返回资源,并在response header中回传资源的缓存策略;
第二次请求时,浏览器判断这些请求参数,击中强缓存就直接200,否则就把请求参数加到 request header头中传给服务器,看是否击中协商缓存,击中则返回304,否则服务器会返回新的资源。这是缓存运作的一个整体流程图:

4.2、http请求及状态码?

http请求:

HTTP(Hyper Text Transfer Protocol(超文本传输协议))是一个简单的请求-响应协议,它通常运行在 TCP 之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的头以 ASCII 码形式给出;而消息内容则具有一个类似 MIME 的格式。HTTP 是应用层协议,同其他应用层协议一样,是为了实现某一类具体应用的协议,并由某一运行在用户空间的应用程序来实现其功能。HTTP 是一种协议规范,这种规范记录在文档上,为真正通过 HTTP 协议进行通信的 HTTP 的实现程序。HTTP 协议是基于 C/S 架构进行通信的,而 HTTP 协议的服务器端实现程序有 httpd、nginx 等,其客户端的实现程序主要是 Web 浏览器。

  • HTTP 是一种无状态协议,即对于事务处理没有记忆能力,服务器不保留与客户交易时的任何状态,如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。
  • HTTP 是一种面向对象的协议。允许传送任意类型的数据对象。它通过数据类型和长度来标识所传送的数据内容和大小,并允许对数据进行压缩传送。
  • HTTP 规范定义了 9 种请求方法:get、post、delete、put、head、trace、options、patch、connect。
  • 默认端口 80

HTTP 请求消息包括以下格式:请求行(request line)、请求头部(header)、空行、请求数据
e.g.

xml.open("POST", "1.php", true); //请求行
xml.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); //请求头部
xml.send("name=123&age=27"); //请求数据

HTTP 响应状态行、消息报头、空行和响应正文
e.g.

HTTP/1.1 200 OK // 状态行
X-Powered-By: Express // 消息报头
date: Wed, 11 Sep 2019 02:37:31 GMT // 消息报头
content-type: application/json;charset=UTF-8 // 消息报头
transfer-encoding: chunked // 消息报头
connection: close

HTTP 响应头

• Allow 服务器支持哪些请求方法(如 GET、POST 等)。
• Content-Encoding 文档的编码(Encode)方法。只有在解码之后才可以得到 Content-Type 头指定的内容类型。利用 gzip 压缩文档能够显著地减少 HTML 文档的下载时间。
• Content-Length 表示内容长度。只有当浏览器使用持久 HTTP 连接时才需要这个数据。
• Content-Type 表示后面的文档属于什么 MIME 类型。Servlet 默认为 text/plain,但通常需要显式地指定为 text/html(content-type:text/xml; charset= utf-8)。
• Date 当前的 GMT 时间
• Expires 应该在什么时候认为文档已经过期,从而不再缓存它
• Last-Modified 文档的最后改动时间
• Location 表示客户应当到哪里去提取文档
• Refresh 表示浏览器应该在多少时间之后刷新文档,以秒计
• Server 服务器名字
• Set-Cookie 设置和页面关联的 Cookie
• WWW-Authenticate 客户应该在 Authorization 头中提供什么类型的授权信息

HTTP状态码列表

HTTP状态码分类
1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错

1xx(临时响应)

100(Continue):客户端应继续提出请求。
101(Switching Protocols 切换协议):服务器根据客户端的请求切换协议(只能切换到更高级的协议)。

2xx(成功):

200:请求成功(一般用于GET与POST请求)。
201(Created 已创建):成功请求并创建了新的资源。比如说,我们 POST 用户名、密码正确创建了一个用户就可以返回 201。
202(Accepted 已接受):已经接受请求,但是结果正在处理中,这时候客户端可以通过轮询等机制继续请求。
203(Non-Authoritative Information 非授权信息):请求成功,但返回的meta信息不在原始的服务器,而是一个副本。
204(No Content 无内容):服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档。
205(Reset Content 重置内容):服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域。206(Partial Content 部分内容):服务器成功处理了部分GET请求。

3xx(已重定向):

300(Multiple Choices 多种选择):请求成功,但结果有多种选择(返回一个资源特征与地址的列表用于用户终端选择)。
301(Moved Permanently 永久移动):请求成功,但是资源被永久转移。(请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI,今后任何新的请求都应使用新的URI代替。)
302(Found 临时移动):与301类似,但资源只是临时被移动,客户端应继续使用原有URI。
303(See Other 查看其它地址):使用 GET和POST来访问新的地址来获取资源。
304(Not Modified 未修改):请求的资源并没有被修改过。(所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源。)
305(Use Proxy 使用):所请求的资源必须通过***访问。
306 (Unused):已经被废弃的HTTP状态码。
307 (Temporary Redirect 临时重定向):与302类似,使用GET请求重定向。

4xx(请求错误):

400(Bad Request):请求出现错误,服务器无法理解。(比如请求的语法错误、请求头不对等。)
401(Unauthorized):没有提供认证信息,请求要求用户的身份认证。(比如请求的时候没有带上 Token 等。)
402(Payment Required):为以后需要所保留的状态码。
403(Forbidden):请求的资源不允许访问,就是说没有权限。
404(Not Found):请求的内容不存在。(服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面。)
405 (Method Not Allowed):客户端请求中的方法被禁止。
406 (Not Acceptable):服务器无法根据客户端请求的内容特性完成请求。
407 (Proxy Authentication Required):请求要求的身份认证,与401类似,但请求者应当使用进行授权。
408 (Request Time-out):服务器等待客户端发送的请求时间过长,超时。
409 (Conflict):服务器处理请求时发生了冲突(服务器完成客户端的PUT请求时可能返回此代码)。
410 (Gone):客户端请求的资源已经不存在。(不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置。)
411 (Length Required):服务器无法处理客户端发送的不带Content-Length的请求信息。
412 (Precondition Failed):客户端请求信息的先决条件错误。
413 (Request Entity Too Large):由于请求的实体过大,服务器无法处理,因此拒绝请求;如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息。(为防止客户端的连续请求,服务器可能会关闭连接。)
414 (Request-URI Too Large):请求的URI过长(URI通常为网址),服务器无法处理。
415 (Unsupported Media Type):服务器无法处理请求附带的媒体格式。
416 (Requested range not satisfiable):客户端请求的范围无效。
417 (Expectation Failed):服务器无法满足Expect的请求头信息。

5xx(服务器错误):

500(Internal Server Error):服务器内部错误,无法完成请求。
501(Not Implemented):请求还没有被实现|不支持。
502 (Bad Gateway):服务器暂时不可用,有时是为了防止发生系统过载。
503 (Service Unavailable):服务器过载或暂停维修,服务器暂时无法处理客户端请求。(延时的长度可包含在服务器的Retry-After头信息中。)
504 (Gateway Time-out):关口过载,服务器使用另一个关口或服务来响应用户,等待时间设定值较长。
505 (HTTP Version not supported):服务器不支持或拒绝支持请求头中指定的HTTP版本。

4.3、http1.x 与http2.x 区别?

两者主要有一下4个区别:

1、HTTP2使用的是二进制传送,HTTP1.x是文本(字符串)传送。

二进制传送的单位是帧和流。帧组成了流,同时流还有流ID标识

2、HTTP2支持多路复用

​ 因为有流ID,所以通过同一个http请求实现多个http请求传输变成了可能,可以通过流ID来标识究竟是哪个流从而定位到是哪个http请求。

3、HTTP2头部压缩

​ HTTP2通过gzip和compress压缩头部然后再发送,同时客户端和服务器端同时维护一张头信息表,所有字段都记录在这张表中,这样后面每次传输只需要传输表里面的索引Id就行,通过索引ID 查询表头的值。

4、HTTP2支持服务器推送

​ HTTP2支持在未经客户端许可的情况下,主动向客户端推送内容。

4.4、http 和 https ?

1.基本概念

HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准 (TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。

HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。HTTPS协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另 一种就是确认网站的真实性。

2.HTTP 与 HTTPS 有什么区别?

​ HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer 安全链路层)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。简单来说,HTTPS协议是由 SSL + HTTP协议 构建的可进行加密传输、身份认证的网络协议,要比http协议安全。

HTTPS和HTTP的区别主要如下:
1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用;
2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议;
3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443;
4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

5、Ajax原理

(1)创建XMLHttpRequest对象:即创建一个异步调用对象
var xhr = new XMLHttpRequest();
(2)打开请求:创建一个新的http请求,并指定该请求的方法、URL、验证信息
xhr.open(‘GET’, ‘example.txt’, true);
(3)发送请求
xhr.send(); 发送请求到服务器
(4)接收响应
xhr.onreadystatechange =function(){}
(1)当readystate值从一个值变为另一个值时,都会触发readystatechange事件。
(2)当readystate==4时,表示已经接收到全部响应数据。
(3)当status ==200时,表示服务器成功返回页面和数据。
(4)如果(2)和(3)内容同时满足,则可以通过xhr.responseText,获得服务器返回的内容。

5.1、Axios的封装和使用(https://blog.csdn.net/wlangmood/article/details/97242917)

6、盒模型

盒模型的组成,由里向外content、padding、border、margin
在这里插入图片描述

标准盒模型:盒模型的宽高只是内容(content)的宽高。

IE盒模型:盒模型的宽高是内容(content)+填充(padding)+边框(border)的总宽高。

这里用到了CSS3 的属性 box-sizing:
标准模型 => box-sizing: content-box;
IE模型 => box-sizing: border-box;

7、JavaScript的三个主要组成部分

ECMAScript(核心),DOM(文档对象模型),BOM(浏览器对象模型)。

8、Dom事件流:捕获+冒泡+事件委托

​ *DOM(文档对象模型)结构是一个树型结构,当一个HTML元素产生一个事件时,该事件会在元素结点与根结点之间的路径传播,路径所经过的结点都会收到该事件,这个传播过程可称为**DOM事件流。***简单来说就是事件执行的顺序流。

DOM支持两种事件模型:捕获型事件流和冒泡型事件流 capture | bubble

冒泡型事件流:事件的传播是从最特定的事件目标到最不特定的事件目标。即从DOM树的叶子到根。
顺序: 从下向上 即: 某个具体的元素 -> … -> body -> html -> document -> window

注:默认addEventListener()使用冒泡型事件流。

捕获型事件流(俗称挖洞):事件的传播是从最不特定的事件目标到最特定的事件目标。即从DOM树的根到叶子。

顺序: 从上向下 即: window -> document -> html -> body -> … -> 某个具体的元素

DOM事件流包括三个阶段:
  1. 事件捕获阶段
  2. 处于目标阶段
  3. 事件冒泡阶段

一个事件触发后,会在子元素和父元素之间传播,这个传播分三个阶段:
1.捕获阶段:从window对象依次向下传播,到达目标节点,即为捕获阶段。捕获阶段不会响应任何事件。
2.目标阶段:在目标节点触发事件,即为目标阶段。
3.冒泡阶段:从目标阶段依次向上传播,到达window对象,即为冒泡阶段。【事件代理就是利用事件冒泡的机制把里层元素需要响应的事件绑定到外层元素身上】

事件委托|代理

​ 将原本绑定在子元素身上的事件委托给父元素,让父元素去监听事件。原理:事件冒泡机制

**优点:**减少事件注册,节省内存占用,也可以实现当新增对象时,无需再次对其进行事件绑定。(对于ul上代理所有li的click事件、动态生成节点绑定事件就很不错。)
**缺点:**事件代理的常用应用应该仅限于上述需求,如果把所有事件都用事件代理,可能会出现事件误判。即本不该被触发的事件被绑定上了事件。

阻止|取消事件传播
1) DOM中的事件对象:(符合W3C标准)

preventDefault() 取消事件默认行为。(比如链接的跳转或者表单的提交,主要是用来阻止标签的默认行为。)

stopImmediatePropagation() 取消事件冒泡同时阻止当前节点上的事件处理程序被调用。

stopPropagation() 阻止事件冒泡和捕获,取消事件对当前节点无影响。(冒泡机制下,阻止事件的进一步往上冒泡。捕获机制下,阻止事件的进一步向下捕获。)

2) IE中的事件对象:

cancelBubble() 阻止事件冒泡和捕获。

returnValue() 取消事件默认行为。

9、移动端适配技术构成:

1 viewport视口
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0 />
2 响应式布局:媒体查询+流式布局。
3 rem计算(px2rem):W3C官网上是这样描述rem的:“font size of the root element”

9.1 四种网页布局?

静态、自适应、流式、响应式

1、静态布局:即传统Web设计,网页上的所有元素的尺寸一律使用px作为单位。不管浏览器尺寸具体是多少,网页布局始终按照最初写代码时的布局来显示。

2、流式布局:使用百分比定义宽度,高度大都是用px来固定,根据可视区域 (viewport) 和父元素的实时尺寸进行调整,页面元素的宽度按照屏幕分辨率进行适配调整,但整体布局不变。这就导致如果屏幕太大或者太小都会导致元素无法正常显示。

3、自适应布局:使用 @media 媒体查询给不同尺寸和介质的设备切换不同的样式,即创建多个静态布局,每个静态布局对应一个屏幕分辨率范围。

4、响应式布局:媒体查询+流式布局。分别为不同的屏幕分辨率定义布局,同时,在每个布局中,应用流式布局的理念,即页面元素宽度随着窗口调整而自动适配。

10、Flex 布局

解释Flex布局,为什么要确定主轴方向,缩小倍数有什么用?

弹性布局中元素的大小是高度依赖父容器的大小的。它所具有的“伸缩性”,目标就是为了撑满父元素。为了可以简便的从上到下写。从左到右换行。缩小倍数防止溢出父元素。

Flex属性:

以下6个属性设置在容器上。

  • flex-direction 决定主轴的方向
  • flex-wrap 不换行|换行|反向换行 nowrap | wrap | wrap-reverse;
  • flex-flow 是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap
  • justify-content 定义了项目在主轴上的对齐方式
  • align-items 定义项目在交叉轴上如何对齐
  • git原因: 出现这个效果的原因,主要是因为 Flex 居中对齐的特性造成的。当内容不换行后,无论是否设定 overflow: scroll/auto,左侧区域由于居中的原因就变成“溢出”区域了,又由于设定了 overflow: auto 的缘故,右侧就还是正常显示的。
    解决: 利用万能的 margin: auto,它可以让任何内容横向居中对齐。
    但问题来了,Flex 占用了 100% 的宽度,有没有什么宽度可以让它根据内容自动调整?width: max-content 这个属性就迎刃而解,它就可以实现这样的效果!再加上 max-width: 100% 约束最大宽度,出现滚动条而不会溢出了!
.navigation{
    margin: auto;
    display: flex;
    overflow: auto;
    max-width: 100%;
    width: max-content;
}

元素垂直居中问题

已知高度水平垂直居中:
  1. 绝对定位+margin负间距(margin-left\right:宽高一半)
未知高度水平垂直居中:
  1. 绝对定位 + css3 transform: translate(-50%, -50%)

  2. 绝对定位(上下左右均为0位置) + margin: auto

  3. css3的flex布局

    .parent {
      	display: flex;
       	height: 100%;
       	flex-flow: row wrap;
       	align-items: center;
       	justify-content: center;
    }
    
  4. table布局 父 display: table 子 display: table-cell

文字水平、垂直居中: text-align: center; 、 line-height:高度;

11、CSS以及项目小问题相关合集

1.CSS3四个自适应关键字

fill-available、max-content、min-content、fit-content](https://www.cnblogs.com/yimuzanghua/p/8464216.html)

​ 一般地,有两种自适应:撑满空闲空间与收缩到内容尺寸。CSS3将这两种情况分别定义为’fill-availabel’和’fit-content’。除此之外 ,还新增了更细粒度的’min-content’和’max-content’。这四个关键字可用于设置宽高属性。

[注意]IE浏览器不支持,webkit内核浏览器需添加-webkit-前缀

width|height:fill-available fill-available表示撑满可用空间

width|height:fit-content fit-content表示将元素宽度收缩为内容宽度

width:min-content min-content表示采用内部元素最小宽度值最大的那个元素的宽度作为最终容器的宽度

width:max-content max-content表示采用内部元素宽度值最大的那个元素的宽度作为最终容器的宽度。如果出现文本,则相当于文本不换行

首先,要明白这里的“最小宽度值”是什么意思。例如图片的最小宽度值就是图片呈现的宽度,对于文本元素,如果全部是中文,则最小宽度值就是一个中文的宽度值;如果包含英文,因为默认英文单词不换行,所以,最小宽度可能就是里面最长的英文单词的宽度

2.在react中渲染后台返回的html文本(富文本):
<div dangerouslySetInnerHTML={{ __html: '数据' }} />
3.通过css渲染后台返回的回车符:
.aa {
	whiteSpace: 'pre-line'
}
4.Echarts图,hover 时tooltip框完整展示不被裁剪(挡住):
tooltip: {
	confine: true
}
5.Echarts图,放大区域的效果:
toolbox: {
  // 可以在折线图上拉取选框来确定所选范围数据 从而改造出要展示的效果
  show: true,
  feature: {
    dataZoom: {
      yAxisIndex: 'none'
    }
  }
}
6.解决antd其select、日期组件 下拉框随页面滚动分离问题:
// parentNode 和 parentElement 可随意使用,生效即可
// select组件:
getPopupContainer={triggerNode => triggerNode.parentNode}
// 另外:有的表格里面有下拉框的可能会出现下拉框被遮挡问题
// 可能由于嵌套多次未取到正确的父级,可以通过向上多取几次父级解决
// 例如:
getPopupContainer={triggerNode => triggerNode.parentNode.parentNode.parentNode}
// 日期组件:
getCalendarContainer={triggerNode => triggerNode.parentElement}
7.空标签
import React, { Fragment } from 'react';
<Fragment></Fragment>
8.CSS 文本超出部分省略展示效果:
width: 160px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;

TCP三次握手,为什么需要三次?

两个目的:1.确保建立可靠连接 2. 避免资源浪费

三次握手的目的是“为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误”,这种情况是:一端(client)A发出去的第一个连接请求报文并没有丢失,而是因为某些未知的原因在某个网络节点上发生滞留,导致延迟到连接释放以后的某个时间才到达另一端(server)B。本来这是一个早已失效的报文段,但是B收到此失效的报文之后,会误认为是A再次发出的一个新的连接请求,于是B端就向A又发出确认报文,表示同意建立连接。如果不采用“三次握手”,那么只要B端发出确认报文就会认为新的连接已经建立了,但是A端并没有发出建立连接的请求,因此不会去向B端发送数据,B端没有收到数据就会一直等待,这样B端就会白白浪费掉很多资源。如果采用“三次握手”的话就不会出现这种情况,B端收到一个过时失效的报文段之后,向A端发出确认,此时A并没有要求建立连接,所以就不会向B端发送确认,这个时候B端也能够知道连接没有建立。

12、从浏览地地址栏输入地址按下回车做了什么?

可以看做是一次请求的发起,那么必然会经历以下几个步骤:

获取数据过程
  1. URL地址解析
  2. DNS查询
  3. TCP连接
  4. 处理请求:发送http请求 -> 服务器接收请求 -> 服务器响应
  5. TCP连接断开
  6. 浏览器解析资源
  7. 渲染页面
解析数据渲染过程
  1. 解析HTML生成DOM树。

  2. 解析CSS生成CSSOM规则树。

  3. 将DOM树与CSSOM规则树合并在一起生成渲染树(Render Tree),这一过程称为 Attachment。

  4. 遍历渲染树开始布局(flow),计算每个节点的位置大小信息。

  5. 将渲染树每个节点绘制(paint)到屏幕上,展示整个页面。
    (不同浏览器内核不同,所以渲染过程不太一样)

    img

WebKit 主流程

重绘、重排区别,如何避免?

重排(Reflow):当渲染树的一部分必须更新并且节点的尺寸发生了变化,浏览器会使渲染树中受到 影响的部分失效,并重新构造渲染树。
重绘(Repaint):是在一个元素的外观被改变所触发的浏览器行为,浏览器会根据元素的新属性重 新绘制,使元素呈现新的外观。比如改变某个元素的背景色、文字颜色、边框颜色等等。

区别:重绘不一定需要重排(比如颜色的改变),重排必然导致重绘(比如改变网页位置)
引发重排
  • 添加、删除可见的dom元素
  • 元素的位置改变
  • 元素本身的尺寸改变(外边距、内边距、边框厚度、宽高、等几何属性)
  • 页面渲染器初始化
  • 浏览器窗口尺寸改变
  • 获取某些属性。当获取一些几何属性时,浏览器为取得正确的值也会触发重排,它会导致队列刷新, 这些属性包括:
    offset系列(offsetTop:box距离浏览器顶部的距离;offsetWidth:box的width(300) + border(10) + padding(20))、
    scroll系列(scrollLeft:有横向滚动条时,为 内容向左滚出去的距离;scrollWidth:在没有滚动条的情况下,元素内容的总宽度)、
    client系列(clientTop:内容到边框的距离 相当于border的高度;clientWidth:盒子的width + 盒子的padding)、
    getComputedStyle() (currentStyle in IE),
    所以,在多次使用这些值时应进行缓存。
优化:
  1. 浏览器自己的优化:
    浏览器会维护1个队列,把所有会引起重排,重绘的操作放入这个队列,等队列中的操作到一定数 量或者到了一定时间间隔,浏览器就会flush队列,进行一批处理,这样多次重排,重绘变成一次 重排重绘。

  2. 减少 reflow / repaint:
    (1) 批量修改 DOM 的样式。例如:可以先定义好 css 的 class,然后修改 DOM 的 className;

    批量修改DOM元素的核心思想是:

    • 让该元素脱离文档流
    • 对其进行多重改变
    • 将元素带回文档中

    隐藏元素,进行修改后,然后再显示该元素

    使用文档片段创建一个子树,然后再拷贝到文档中

    (2) 不要把 DOM 结点的属性值放在一个循环里当成循环里的变量;
    (3) 为动画的 HTML 元素使用 fixed / absoult 的 position,那么修改他们的 CSS 是不会 reflow 的;
    (4) 千万不要使用 table 布局。因为可能很小的一个小改动会造成整个 table 的重新布局;(table 及其内部元素除外,它可能需要多次计算才能确定好其在渲染树中节点的属性,通常要花3倍于同 等元素的时间。这也是为什么我们要避免使用table做布局的一个原因。)(5)不要在布局信息改变的时候做查询(会导致渲染队列强制刷新)。

13、null 与undefined的区别:

JavaScript的最初版本是这样区分的:null是一个表示"无"的对象,转为数值时为0;undefined是一个表示"无"的原始值,转为数值时为NaN。

**null表示"没有对象",即该处不应该有值。**典型用法是:

(1) 作为函数的参数,表示该函数的参数不是对象。

(2) 作为对象原型链的终点。

Object.getPrototypeOf(Object.prototype)
// null

**undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。**典型用法是:

(1)变量被声明了,但没有赋值时,就等于undefined。

(2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。

(3)对象没有赋值的属性,该属性的值为undefined。

(4)函数没有返回值时,默认返回undefined。

14、link和@import引入外部样式的区别

1、link是html方式,@import是css方式

​ link属于HTML标签,而@import完全是CSS提供的一种方式。

2、加载顺序的不同:

​ 当页面被加载的时候,link引用的CSS会同时被加载,而@import引用的CSS 会等到页面全部被下载完再被加载。(所以有时候浏览@import加载CSS的页面时开始会没有样式,然后突然样式会出现,网速慢的时候还挺明显。)

3、兼容性上的差别

由于@import是CSS2.1提出的,@import只有在IE5以上的才能识别,而link标签无此问题。

4、使用DOM控制样式时的差别

当使用javascript控制DOM(document.styleSheets)去改变样式的时候,只能使用link标签,因为@import不是dom可以控制的。

5、@import次数:限制@import只能引入31次css文件。
6、什么是 FOUC(Flash of Unstyled Content)? 如何来避免 FOUC?

当使用 @import 导入 CSS 时,会导致某些页面在 IE 出现奇怪的现象: 没有样式的页面内容显示瞬间闪烁,这种现象称为“文档样式短暂失效”,简称为FOUC。

产生原因:当样式表晚于结构性html加载时,加载到此样式表时,页面将停止之前的渲染。

等待此样式表被下载和解析后,再重新渲染页面,期间导致短暂的花屏现象。

解决方法:使用 link 标签将样式表放在文档 head

15、this指向及改变this指向的方法

1.普通函数的this:指向它的调用者,如果没有调用者则默认指向window.
2.箭头函数的this: 指向箭头函数定义时所处的对象,而不是箭头函数使用时所在的对象,默认使用父级的this。
箭头函数中的this定义时就已经确定且永远不会改变!首先从它的父级作用域中找,如果父级作用域还是箭头函数,再往上找,如此直至找到this的指向。

一、函数的调用方式决定了 this 的指向不同:

1.普通函数调用,此时 this 指向 window

function fn() {
   console.log(this);   // window
 }
 fn();  //  window.fn(),此处默认省略window

2.构造函数调用, 此时 this 指向 实例对象

function Person(age, name) {
this.age = age;
this.name = name
console.log(this) // 此处 this 分别指向 Person 的实例对象 p1 p2
}
var p1 = new Person(18, ‘zs’)
var p2 = new Person(18, ‘ww’)

3.对象方法调用, 此时 this 指向 该方法所属的对象

var obj = {
fn: function () {
console.log(this); // obj
}
}
obj.fn();

4.通过事件绑定的方法, 此时 this 指向 绑定事件的对象

<body>
    <button id="btn">hh</button>
    <script>
        var oBtn = document.getElementById("btn");
        oBtn.onclick = function() {
            console.log(this); // btn
        }
    </script>
</body>

5.定时器函数, 此时 this 指向 window

setInterval(function () {
console.log(this); // window
}, 1000);

二、更改this指向的三个方法

1.call() 方法

var Person = {
name:“lixue”,
age:21
}
function fn(x,y){
console.log(x+“,”+y);
console.log(this);
console.log(this.name);
console.log(this.age);
}
fn.call(Person,“hh”,20); // 普通函数的this指向window,现在让我们更改this指向绑定事件的对象Person

2.apply() 方法

​ apply() 与call()非常相似,不同之处在于提供参数的方式, apply()使用参数数组,而不是参数列表.

3.bind()方法

​ bind()创建的是一个新的函数(称为绑定函数),与被调用函数有相同的函数体,当目标函数被调用时this的值绑定到 bind()的第一个参数上

16、深、浅拷贝

浅拷贝和深拷贝都只针对于引用数据类型,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存;但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改变原对象。

**区别:**浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行递归复制。

深拷贝:修改新变量的值不会影响原有变量的值。默认情况下基本数据类型都是深拷贝。
浅拷贝:修改新变量的值会影响原有的变量的值。默认情况下引用类型都是浅拷贝。

无论是使用扩展运算符(…)还是解构赋值,对于引用类型都是浅拷贝。所以在使用splice()、concat()、…对数组拷贝时,只有当数组内部属性值不是引用类型是,才能实现深拷贝。

实现浅拷贝方法:
Object.assign方法Object.assign({}, obj)
**for in 方法:**只复制第一层的浅拷贝
实现深拷贝方法:
• 简单需求,通过 JSON 反序列化来实现深拷贝
const B = JSON.parse(JSON.stringify(A))

缺点也是显而易见的,JSON value不支持的数据类型,都拷贝不了
1.不支持函数
2.不支持undefined(支持null)
3.不支持循环引用,比如 a = {name: ‘a’}; a.self = a; a2 = JSON.parse(JSON.stringify(a))
4.不支持Date,会变成 ISO8601 格式的字符串
5.不支持正则表达式
6.不支持Symbol

• 复杂需求,通过递归克隆 ,即浅拷贝 + 递归实现深拷贝

核心有三点:1.对象分类型讨论 2.递归 3.解决循环引用(环)
缺点:1.对象类型支持不够多(Buffer,Map,Set等都不支持) 2.存在递归爆栈的风险

function isObject(obj) {
    return typeof obj === 'object' && obj != null;
}
function cloneDeep2(source) {
    if (!isObject(source)) return source; // 非对象返回自身

    var target = Array.isArray(source) ? [] : {};
    for(var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (isObject(source[key])) {
                target[key] = cloneDeep2(source[key]); // 注意这里
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}
• 项目常用推荐:lodash的_.cloneDeep()

​ **lodash的_.cloneDeep()**支持循环对象,和大量的内置类型,对很多细节都处理的比较不错。是用栈记录了所有被拷贝的引用值,如果再次碰到同样的引用值的时候,不会再去拷贝一遍,而是利用之前已经拷贝好的值。

可以用**Object.prototype.toString.call()**判断数据类型:例:Object.prototype.toString.call({}) = “[object Object]”

js中遍历一个对象的属性的方法
  • Object.keys() 仅仅返回自身的可枚举属性,不包括继承来的,更不包括Symbol属性
  • Object.getOwnPropertyNames() 返回自身的可枚举和不可枚举属性。但是不包括Symbol属性
  • Object.getOwnPropertySymbols() 返回自身的Symol属性
  • for…in 可以遍历对象的自身的和继承的可枚举属性,不包含Symbol属性
  • Reflect.ownkeys() 返回对象自身的所有属性,不管是否可枚举,也不管是否是Symbol。注意不包括继承的属性

17、get请求和post请求区别与联系?

一:联系

get和post是http请求的两种方式,底层都是用TCP/IP协议进行通信的

get和post本质并无区别,只是被http规定了不同的行为和方式

HTTP给网络运输设定了好几个服务类别,有GET, POST, PUT, DELETE等等,HTTP规定,当执行GET请求的时候,要给请求包贴上GET的标签(设置method为GET),而且要求把传送的数据放在url中以方便记录。如果是POST请求,在请求数据包贴上POST的标签。

二:区别
  1. GET请求在浏览器后退时无害,不发送请求。POST在浏览器后退时会再次发送请求。
  2. GET更不安全,因为参数直接暴露在URL上,POST参数在HTTP消息主体中,而且不会被保存在浏览器历史或 web 服务器日志中。(但实际上,get也可以用body少量传值,post也可以在url中少量传值,这在技术上是完全行的通的,只是不符合http的规定。)
  3. GET请求会被浏览器主动cache,而POST不会,除非手动设置。
  4. POST是向服务器传送数据,GET是从服务器获取数据。
  5. GET请求只能进行url编码,而POST支持多种编码方式。
  6. 对参数的数据类型,GET只接受ASCII字符,而POST没有限制,允许二进制。
  7. 对于GET方式,服务器端用Request.QueryString获取变量的值;对于POST方式,服务器端用Request.Form获取提交的数据。
  8. 浏览器对URL的长度有限制,所以GET请求不能代替POST请求发送大量数据。POST传送的数据量较大,一般被默认为不受限制(但理论上IIS 4 中最大量为80KB,IIS 5 中为100KB);GET传送的数据量较小,一般不能大于2KB,不同的浏览器和服务器不同,一般限制在 2~8K 之间。
  9. GET执行效率比POST方法好,因为GET产生一个TCP数据包,POST产生两个TCP数据包。*
    对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。也就是post请求,第一次将header发送过去,确认服务器和网络没问题可以服务,才会将真正的data数据提交。 因为POST需要两步,时间上消耗的要多一点,看起来GET比POST更有效。
    在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。也就是网络好的话get和post请求效率基本一样,网络不好的时候post对验证请求数据完整性更有优势。
    并不是所有浏览器都会在POST中发送两次包,常用的Firefox就只发送一次

18、同源?跨域?如何实现?

跨域:

浏览器对于javascript的同源策略的限制,不同域之间相互请求资源,就算作“跨域”。

同源策略:

同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源也就是协议、域名、端口相同,即便两个不同的域名指向同一个ip地址,也非同源。

同源策略限制内容:
  • Cookie、LocalStorage、IndexedDB 等存储性内容
  • DOM 节点
  • AJAX 请求发送后,结果被浏览器拦截
但是有三个标签是允许跨域加载资源:
  • <img src=XXX>
  • <link href=XXX>
  • <script src=XXX>
注意

跨域请求都是get异步请求;
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。

跨域解决方案

解决方案有jsonp、cors、postMessage、websocket、Node中间件代理(两次跨域)、nginx反向 代理、window.name + iframe、location.hash + iframe、document.domain + iframe。

  • CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案
  • JSONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
  • 不管是Node中间件代理还是nginx反向代理,主要是通过同源策略对服务器不加限制。
  • 日常工作中,用得比较多的跨域方案是cors和nginx反向代理
1. jsonp:

概念:jsonp全称是JSON with Padding,是为了解决跨域请求资源而产生的解决方案,是一种依靠开发人员创造出的一种非官方跨域数据 交互协议。
原理:利用 <script>标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器 做支持才可以。
JSONP和AJAX对比:JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略, JSONP属于非同源策略(跨域请求)。
JSONP优缺点
优点:简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。
缺点:仅支持get方法具有局限性,不安全可能会遭受XSS攻击。
JSONP的实现流程

  • 声明一个回调函数,其函数名(如show)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)。
  • 创建一个<script>标签,把那个跨域的API数据接口地址,赋值给script的src,还要在这个地址中向服务器传递该函数名(可以通过问号传参:?callback=show)。
  • 服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串。例如:传递进去的函数名是show,它准备好的数据是show('我不爱你')
  • 最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数(show),对返回的数据进行操作。

jsonp的产生:

1.AJAX直接请求普通文件存在跨域无权限访问的问题,不管是静态页面也好;
2.不过我们在调用js文件的时候又不受跨域影响,比如引入jquery框架的,或者是调用相片的时候
3.凡是拥有src这个属性的标签都可以跨域例如

2. CORS

CORS 需要浏览器和后端同时支持,IE 8 和 9 需要通过 XDomainRequest 来实现。

浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。

服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。

3. Websocket

Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。

4. Node中间件代理(两次跨域)

实现原理同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。

代理服务器,需要做以下几个步骤:

  • 接受客户端请求 。
  • 将请求 转发给服务器。
  • 拿到服务器 响应 数据。
  • 将 响应 转发给客户端。
5. nginx反向代理

实现原理类似于Node中间件代理,需要你搭建一个中转nginx服务器,用于转发请求。
使用nginx反向代理实现跨域,是最简单的跨域方式。只需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。

实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。

6.postMessage

**postMessage()**方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、 多窗口、跨域消息传递。

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
页面和其打开的新窗口的数据传递
多窗口之间消息传递
页面与嵌套的iframe消息传递
上面三个场景的跨域数据传递

7.window.name + iframe

window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。
总结:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域,这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

8.location.hash + iframe

实现原理: a.html 欲与 c.html 跨域相互通信,通过中间页 b.html 来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。
具体实现步骤:一开始 a.html 给 c.html 传一个hash值,然后 c.html 收到hash值后,再把hash值传递给 b.html,最后 b.html 将结果放到a.html 的hash值中。 同样的 a.html 和 b.html 是同域的,都是 http://localhost:3000 而 c.html是 http://localhost:4000。

9.document.domain + iframe

该方法只能用于二级域名相同的情况下,比如 a.test.com 与 b.test.com 适用于该方式。 只需要给页面添加 document.domain ='test.com' 表示二级域名都相同就可以实现跨域。
实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。

19、事件循环Event loop?

event loop 的概念:

主线程从"任务队列"中读取执行事件,这个过程是循环不断的,这个机制被称为事件循环。
此机制具体如下:
主线程会不断从任务队列中按顺序取任务执行,每执行完一个任务都会检查microtask队列是否为空(执行完一个任务的具体标志是函数执行栈为空),如果不为空则会一次性执行完所有 microtask。然后再进入下一个循环 去任务队列中取下一个任务执行。

Javascript是单线程的,所有的同步任务都会在主线程中执行。
当主线程中的任务都执行完之后,系统会 “依次” 读取任务队列里的事件,与之相对应的异步任务进入主线程,开始执行。

macro task 与 micro task

异步任务的执行优先级并不相同,它们被分为两类:微任务( micro task ) 和 宏任务( macro task ) 。

根据异步事件的类型,这些事件实际上会被派发对应的宏任务和微任务中,当前主线程执行完毕后,会首先处理微任务队列中的事件,然后再去读取宏任务队列的事件。主线程会不断重复上面的过程,直到执行完所有任务。在同一次事件循环中,微任务永远在宏任务之前执行。

  1. 宏任务( macro-task ):整体 scriptsetTimeoutsetIntervalUI交互事件I/O
  2. 微任务( micro-task ):promise.then、process.nextTickPromiseMutaionObserver
    优先级: process.nextTick() > Promise.then() > setTimeout > setImmediate

整体script本身就是一次宏任务也是第一个宏任务

抛出问题?(promise和setTimeout执行顺序的问题?)

且看下面代码和问题:

setTimeout(function(){console.log(1)},0);
new Promise(function(resolve){
    console.log(2)
    for( var i=0 ; i<10000 ; i++ ){
        i==9999 && resolve()
    }
    console.log(3)
}).then(function(){
    console.log(4)
});
console.log(5);

// 这的问题是,为什么答案是 2 3 5 4 1,而不是 2 3 5 1 4

解析:

我们来捋一遍代码的执行过程,

1)所有的代码都写在script标签中,所以读取所有代码是第一个宏任务,我们开始执行第一个宏任务。

2)我们首先遇到setTimeout,他是第二个宏任务,将它扔进宏任务事件队列里先排队。

3)下面我们遇到promise,promise执行器里的代码会被同步调用,所以我们依次打印出2和3。

4)下面遇到promise的回调,他是一个微任务,将它扔进微任务事件队列中。

5)下面我们接着打印出5,然后执行微任务并且打印出4.

6)我们第一个宏任务执行完毕,执行下一个宏任务,打印出1

到此,所有任务都执行完毕,所以我们最后的结果为2 3 5 4 1。

20、Node 是单线程还是多线程?为什么能够开很多个异步请求去访问其他接口?

Nodejs所谓的单线程,只是主线程是单线程,所有的网络请求或者异步任务都交给了内部的线程池去实现,本身只负责不断的往返调度,由事件循环不断驱动事件执行。

1、nodejs内部揭秘:

图片描述

Node.js 的结构大致分为三个层次:

1、 Node.js 标准库,这部分是由 Javascript 编写的,即我们使用过程中直接能调用的 API。(在源码中的 lib 目录下可以看到。)

2、 Node bindings,这一层是 Javascript 与底层 C/C++ 能够沟通的关键,前者通过 bindings 调用后者,相互交换数据。

3、这一层是支撑 Node.js 运行的关键,由 C/C++ 实现。
V8:Google 推出的 Javascript VM,也是 Node.js 为什么使用的是 Javascript 的关键,它为 Javascript 提供了在非浏览器端运行的环境,它的高效是 Node.js 之所以高效的原因之一。
Libuv:它为 Node.js 提供了跨平台,线程池,事件池,异步 I/O 等能力,是 Node.js 如此强大的关键。
C-ares:提供了异步处理 DNS 相关的能力。
http_parser、OpenSSL、zlib 等:提供包括 http 解析、SSL、数据压缩等其他的能力。

2、为什么在浏览器中运行的 Javascript 能与操作系统进行如此底层的交互?

从JavaScript调用Node的核心模块,核心模块调用C++内建模块,内建模块通过 libuv进行系统调用,这是Node里经典的调用方式。总体来说,我们在 Javascript 中调用的方法,最终都会通过node-bindings 传递到 C/C++ 层面,最终由他们来执行真正的操作。Node.js 即这样与操作系统进行互动。

3、nodejs既然是单线程,如何实现异步、非阻塞I/O?

js执行线程是单线程,把需要做的I/O交给libuv,自己马上返回做别的事情,然后libuv在指定的时刻回调就行了。

细化一点:nodejs会先从js代码通过node-bindings调用到C/C++代码,然后通过C/C++代码封装一个叫 “请求对象” 的东西交给libuv,这个请求对象里面无非就是需要执行的功能+回调之类的东西,给libuv执行以及执行完实现回调。

总结来说,一个异步 I/O 的大致流程如下:

1、发起 I/O 调用
用户通过 Javascript 代码调用 Node 核心模块,将参数和回调函数传入到核心模块;
Node 核心模块会将传入的参数和回调函数封装成一个请求对象;
将这个请求对象推入到 I/O 线程池等待执行;
Javascript 发起的异步调用结束,Javascript 线程继续执行后续操作。

2、执行回调
I/O 操作完成后,会取出之前封装在请求对象中的回调函数,执行这个回调函数,以完成 Javascript 回调的目的。

图片描述

**总结:**node.js采用单线程异步非阻塞模式。它的单线程指的是自身Javascript运行环境的单线程,Node.js并没有给Javascript执行时创建新线程的能力,最终的实际操作,还是通过Libuv以及它的事件循环来执行实现异步的。

4、nodejs事件驱动是如何实现的?和浏览器的event loop是一回事吗?

event loop是一个执行模型,在不同的地方有不同的实现。浏览器和nodejs基于不同的技术实现了各自的event loop。

简单来说:nodejs的event是基于libuv,而浏览器的event loop则在html5的规范中明确定义。libuv已经对event loop作出了实现,而html5规范中只是定义了浏览器中event loop的模型,具体实现留给了浏览器厂商。

异步IO的流程:

图片描述

5、nodejs擅长什么?不擅长什么?

Node.js 通过 libuv 来处理与操作系统的交互,并且因此具备了异步、非阻塞、事件驱动的能力。因此,NodeJS能响应大量的并发请求。所以,NodeJS适合运用在高并发、I/O密集、少量业务逻辑的场景。

遇到 I/O 任务,Node.js 就把任务交给线程池来异步处理,高效简单,因此 Node.js 适合处理I/O密集型任务。但不是所有的任务都是 I/O 密集型任务,当碰到CPU密集型任务时,即只用CPU计算的操作,比如要对数据加解密(node.bcrypt.js),数据压缩和解压(node-tar),这时 Node.js 就会亲自处理,一个一个的计算,前面的任务没有执行完,后面的任务就只能干等着 。如果操作系统本身就是单核,那也就算了,但现在大部分服务器都是多 CPU 或多核的,而 Node.js 只有一个 EventLoop,也就是只占用一个 CPU 内核,当 Node.js 被CPU 密集型任务占用,导致其他任务被阻塞时,却还有 CPU 内核处于闲置状态,造成资源浪费。

其实虽然Node.js可以处理数以千记的并发,但是一个Node.js进程在某一时刻其实只是在处理一个请求。

因此,Node.js 并不适合 CPU 密集型任务。

21、cookiesession的区别和联系

cookie和session,可以与服务器交互通信的。

Cookie:

cookie保存在浏览器端,单个数据大小不超过4KB,是服务器发送到客户端的特殊信息,保存成字符串类型以文本的方式保存在客户端,会随着每次HTTP请求头request header发送到服务器端。如果不在浏览器中设置过期时间,cookie被保存在内存中,浏览器关闭就会删除这些cookie信息;如果设置了过期时间,cookie被保存在硬盘中,直到过期时间才会删除这些信息。

cookie应用场景:记录是否登录,上次登录时间,浏览的页面,浏览次数等

cookie的缺点:

1)存储空间很小,只有4kb,存储数量限制,一般一个站点只能有20个cookie

2)用户可以操作(禁用或者修改删除)cookie,使功能受限

3)安全性较低

4)每次访问都要传送cookie给服务器,浪费带宽,如果保存过多数据影响性能。

获取cookie:document.cookie;
// cookie是每个cookie键值对通过一个分号+空格的形式串联起来的,例:key1=value1; key2=value2; key3=value3;
设置/修改cookie:document.cookie = “key=value”

**删除cookie:**设置过期时间为现在以前的时间,如果不设置过期时间,浏览器关闭就会删除cookie
var exp = new Date();
exp.setDate(exp.getDate() - 1);
document.cookie = “key=; expires=” + exp.toGMTString();

cookie选项包括:

expires(http/1.0协议中的过期时间,GMT格式)、

max-age(http/1.1协议中的单位是秒的时间段,失效时间是创建时间 + max-age)、

domain(域名)、path(路径)、

secure(当请求是HTTPS或者其他安全协议时,包含 secure 选项的 cookie才能被发送至服务器,安全协议时才能在客户端设置secure参数)、

HttpOnly(在客户端是不能通过js代码去访问一个httpOnly类型的cookie的,也不能设置httpOnly参数,这种类型的cookie只能通过服务端来设置)

Session:

session保存在服务器端内存中,没有大小限制,通过类似与Hashtable的数据结构来保存,能支持任何类型的对象(session中可含有多个对象)。服务器端创建session对象时会检测客户端请求有没有包含sessionId,如果没有就创建一个并且返回给客户端,客户端一般记在cookie里,如果HTTP请求带着sessionId,就返回对应的session对象。如果用户禁用cookie,需要使用response.encodeURL(url)进行URL重写把sessionID拼在url后面。每次启动session_start后前一次的sessionID就失效或者session过期后sessionID也会失效。

session应用场景:保存用户登录信息,防止用户非法登录等。

session的缺点:

1)扩展性弊端,要求客户端代码和服务端代码在同一台服务器上,协议/域名/端口号一致。(PS:如果不在同一个服务器上,可以使用token进行身份验证)

2)Session保存的东西越多,就越占用服务器内存,对于用户在线人数较多的网站,服务器的内存压力会比较大。而且当用户离开网站后,这些session还会保存一段时间,造成资源浪费。

3)重启服务器,session数据会丢失。

4)依赖于cookie(sessionID保存在cookie),如果禁用cookie,则要使用URL重写,不安全

5)创建Session变量有很大的随意性,可随时调用,不需要开发者做精确地处理,所以,过度使用session变量将会导致代码不可读而且不好维护

**WebStorage:**localStorage 和 sessionStorage 属性允许在浏览器中存储 key/value 对的数据,不与服务器交互通信。存储大小5MB。只能存储字符串。存储的数据直接本地获取,比HTTP请求快。

sessionStorage:

sessionStorage 用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页之后将会删除这些数据。

应用场景:每次打开网页需要重新登录的登录信息。

保存数据语法:sessionStorage.setItem(“key”, “value”);
// 或者 sessionStorage.key = value;
// 或者 sessionStorage[“key”] = value;

读取数据语法: sessionStorage.getItem(“key”);
// 或者 sessionStorage.key;
// 或者 sessionStorage[“key”];

删除指定键的数据语法: sessionStorage.removeItem(“key”);
删除所有数据: sessionStorage.clear();

localStorage:

localStorage 用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去删除。

应用场景:长期登录的保存登录信息。

保存数据语法: localStorage.setItem(“key”, “value”);
// 或者 localStorage.key = value;
// 或者 localStorage[“key”] = value;

读取数据语法: localStorage.getItem(“key”);
// 或者 localStorage.key;
// 或者 localStorage[“key”];

删除数据语法: localStorage.removeItem(“key”);
删除所有数据语法: localStorage.clear();

23、防抖节流

​ 优化高频率执行js代码的一种手段,js中的一些事件如浏览器的resize、scroll, 鼠标的mousemove、mouseover,input输入框的keypress等事件在触发时,会不断地调用绑定在事 件上的回调函数,极大地浪费资源,降低前端性能。为了优化体验,需要对这类事件进行调用次数的限 制。
**防抖:**在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。(类似公交车问题
**节流:**每隔一段时间,只执行一次函数。(类似 眨眼问题

函数节流(throttle)
一种类似控制阀门一样定期开放的效果,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)。
function throtte(func, time) {
  	let activeTime = 0;
  	return () => {
      	const current = Date.now();
      	if (current - activeTime > time) {
          		func.apply(this, arguments);
          		activeTime = Date.now();
        }
    };
}
函数防抖(debounce)
  • 对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限(如1000毫秒)内,事件处理函数只执行一次。

每次 resize/scroll 触发、统计事件文本输入的验证,连续输入文字后发送 AJAX 请求进行验证,验证一次就好。

function debounce(fn,delay){
    let timer = null //借助闭包
    return function() {
        if(timer){
            clearTimeout(timer) 
        }
        timer = setTimeout(fn,delay) // 简化写法
    }
}
应用场景:

防抖:
1.输入框做远程搜索时,只需用户最后一次输入完,再发送请求,节约请求的资源;
2.window触发resize时,我们可以通过防抖来保证只在用户停止拖拽的时候触发相应的方法;
3.可以用来防止按钮的重复提交;
4.手机号、邮箱验证输入检测。

节流:
1.鼠标不断的点击触发click,保证单位时间内只触发一次;
2.滚动加载,加载更多或者滚到底部监听,判断是否滑动到底部自动加载更多;
3.高频点击按钮,表单重复提交;
4.谷歌搜索框,搜索联想功能。

异同比较:

相同点:
1.都可以通过使用 setTimeout 实现。
2.目的都是,降低回调执行频率。节省计算资源。
区别:
1.防抖是在用户频繁操作时,操作间隙大于等待时间才执行下一次;节流是用户频繁操作过程中,在单位时间内,周期性的执行函数;
2.防抖关注一定时间连续触发的事件只在最后执行一次,而节流侧重于一段时间内只执行一 次。

浏览器的垃圾回收机制?

浏览器的 Javascript 具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。
原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。但是这个过程不是实时的,因为其开销比较大并且GC时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。

不再使用的变量也就是生命周期结束的变量,当然只可能是局部变量,全局变量的生命周期直至浏览器卸载页面才会结束。
局部变量只在函数的执行过程中存在,而在这个过程中会为局部变量在栈或堆上分配相应的空间,以存储它们的值,然后在函数中使用这些变量,直至函数结束,而闭包中由于内部函数的原因,外部函数并不能算是结束。

上代码说明:

function fn1() {
    var obj = {name: 'hanzichi', age: 10};
}
function fn2() {
    var obj = {name:'hanzichi', age: 10};
		return obj;
}
var a = fn1();
var b = fn2(); 
/* 代码执行解析:
首先定义了两个function,分别叫做fn1和fn2,当fn1被调用时,进入fn1 的环境,会开辟一块内存存放对象{name: 'hanzichi', age: 10},而当调用结束后,出了fn1的环境,那么该块内存会被js引擎中的垃圾回收器自动释放;在fn2被调用的过程中,返回的对象被全局变量b所指 向,所以该块内存并不会被释放。
这里问题就出现了:到底哪个变量是没有用的?
所以垃圾收集器必须跟踪 到底哪个变量没用,对于不再有用的变量打上标记,以备将来收回其内存。用于标记的无用变量的策略可能因实现而有所区别,通常情况下有两种实现方式:标记清除和引用计数,引用计数不太常用。
*/
标记清除:

js中最常用的垃圾回收机制就是标记清除。当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。

function test() {
  		var a = 10 ;	// 被标记 ,进入环境
  		var b = 20 ;	// 被标记 ,进入环境
}
test(); 						// 执行完毕 之后 a、b又被标离开环境,被回收。 									 									 							

垃圾回收器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。 然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记(闭包)。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾回收器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。
到目前为止,IE9+、 Firefox、Opera、Chrome、Safari的js实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。

引用计数

引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0 时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。

function test() {
    var a = {};		// a的引用次数为0
    var b = a;		// a的引用次数加1,为1
    var c = a;		// a的引用次数再加1,为2
    var b = {};   // a的引用次数减1,为1
}
test();
存在严重问题:循环引用

循环引用:指的是对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。

function fn () {
  		var a = {};
  		var b = {};
  		a.pro = b;
  		b.pro = a;
}
fn()

以上代码a和b的引用次数都是2,fn()执行完毕后,两个对象都已经离开环境,在标记清除方式下是没有问题的,但是在引用计数策略下,因为a和b的引用次数不为0,所以不会被垃圾回收器回收内存, 如果fn函数被大量调用,就会造成内存泄露。在IE7与IE8上,内存直线上升。

我们知道,IE中有一部分对象并不是原生js对象。例如,其内存泄露DOM和BOM中的对象就是使用 C++以COM对象的形式实现的,而COM对象的垃圾回收机制采用的就是引用计数策略。因此,即使IE的 js引擎采用标记清除策略来实现,但js访问的COM****对象依然是基于引用计数策略的。换句话说,只要在 IE中涉及COM对象,就会存在循环引用的问题。

var element = document.getElementById("some_element");
var myObject = new Object();
myObject.e = element;
element.o = myObject;
/*
这个例子在一个DOM元素(element)与一个原生js对象(myObject)之间创建了循环引用。其中, 变量myObject有一个属性e指向element对象;而变量element也有一个属性o回指myObject。由于存 在这个循环引用,即使例子中的DOM从页面中移除,它也永远不会被回收。
*/
解决办法:手动解除循环引用
var element = document.getElementById("some_element");
var myObject = new Object();
myObject.e = element;
element.o = myObject;
myObject.e = null;	// 手动解除
element.o = null;		// 手动解除

将变量设置为null意味着切断变量与它此前引用的值之间的连接。当垃圾回收器下次运行时,就会删除这些值并回收它们占用的内存。
要注意的是,IE9+并不存在循环引用导致Dom内存泄露问题,可能是微软做了优化,或者Dom的回收方式已经改变。

24、什么是柯里化

定义:在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

// 支持多参数传递
function progressCurrying(fn, args) {
    var _this = this
    var len = fn.length;
    var args = args || [];
    return function() {
        var _args = Array.prototype.slice.call(arguments);
        Array.prototype.push.apply(args, _args);
        // 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
        if (_args.length < len) {
            return progressCurrying.call(_this, fn, _args);
        }
        // 参数收集完毕,则执行fn
        return fn.apply(this, _args);
    }
}

25、Git相关操作命令

一个查找产生 Bug 的提交记录的指令:git bisect 命令教程

查看操作记录:git reflog

通用分支关联,此操作仅关联,不提交:
git branch --set-upstream-to=origin/bf10 br10

通用分支关联,后续提交,只需要git push origin即可:
git push --set-upstream origin br10:bf10

npm install xxx
安装项目到项目目录下,不会将模块依赖写入devDependenciesdependencies

npm install -g xxx:
-g的意思是将模块安装到全局,具体安装到磁盘哪个位置,要看 npm cinfig prefix的位置

npm install -save xxx:
-save的意思是将模块安装到项目目录下,并在package文件的dependencies节点写入依赖。

npm install -save-dev xxx:
-save-dev的意思是将模块安装到项目目录下,并在package文件的devDependencies节点写入依赖。

git 合并分支:先更新需要合并分支(other)的代码,再切换到自己分支合并:git merge other
(最好保证自己分支的干净-避免冲突)

git 让每次合并产生新的提交记录:
git 采用的模式即称为 fast-forward 模式(快进模式)
–no-ff 即 no-fast-forward 禁用快进模式
git merge hotfix --no-ff

git pull origin xxx --no-edit
避免输入无用的merge信息,自动保存并上传了默认的merge信息

git 回退本地commit版本:git reset --soft 版本号
(代码不会丢失,会变成待提交状态)

git 修正最后一次commit但未push的操作(commit以后发现还需要修改部分,或者commit的描述信息需要修正):
git commit --amend 或者 git commit --amend “正确的描述信息”

git 撤销已推送到远程的操作:
1.git revert 版本号 // commit的所有改变都会被反转(最安全、最基本的撤销场景)
2.git push

git撤销已经提交到远程分支的代码:

1.首先通过git log查看提交记录
2.选中需要回退的版本。使用命令:git reset --soft 版本号
3.使用git log查看是否回退成功,确认回退成功
4.同步到远程分支。命令:git push --force
这个时候我们看远程分支上之前的提交已经撤销了。修改暂存到了本地。

git 远程分支更新后本地获取不到最新数据:
git remote // 列出所有远程主机 - 一般有一个origin
git remote update --prune // 更新远程主机origin 整理分支
拓展:
git branch -r // 列出远程分支
git branch -vv // 查看本地分支和远程分支对应关系

git 从已有的分支创建新的分支(如从master分支),创建一个dev分支
git checkout -b dev

git 提交该分支到远程仓库
git push origin dev

git 干净提交:git pull --rebase
如果rebase出现冲突,解决办法:
1.解决冲突的part
2.然后执行:git add
3.再执行:git rebase --continue
终止rebase的行动:git rebase --abort

git 重命名本地分支:
git branch -m

git 重命名远程分支:1、删除远程待修改分支 2、push本地新分支到远程服务器

git 删除分支:
git branch -d // 删除本地分支
git branch -d -r // 删除远程分支,删除后还需推送到服务器
git push origin: // 删除后推送至服务器

git 中一些选项解释:
远程: -r => --remote
所有: -a => --all
强制: -f => --force
删除: -d => --delete
-D => --delete --force
移动/重命名:
-m => --move
-M => --move --force

三、Vue相关

什么是Vue.nextTick() ??

定义:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

所以就衍生出了这个获取更新后的DOM的Vue方法。所以放在Vue.nextTick()回调函数中的执行的应该是会对DOM进行操作的 js代码。

理解:nextTick() 是将回调函数延迟在下一次dom更新数据后调用,简单的理解是:当数据更新了,在dom中渲染后,自动执行该函数。

1. 数据双向绑定原理

vue的双向绑定是由数据劫持结合发布者-订阅者模式实现的,那么什么是数据劫持?就是通过Object.defineProperty()来劫持对象属性的setter和getter操作,在数据变动时做你想要做的事情。

image-20210908184317674
数据劫持set和get函数起到什么作用?
回顾一下Object.defineProperty:
  • 语法:Object.defineProperty(obj, prop, descriptor)
  • 参数:
    obj:目标对象
    prop:需要定义的属性或方法的名称
    descriptor:目标属性所拥有的特性
  • 可供定义的特性列表
    value:属性的值
    writable:如果为false,属性的值就不能被重写。
    get:一旦目标属性被访问就会调用此方法,并将此方法的运算结果返回用户。
    set:一旦目标属性被赋值,就会调回此方法。
    configurable:如果为false,则任何尝试删除目标属性或修改属性以下特性(writable, configurable, enumerable)的行为将被无效化。
    enumerable:是否能在for…in循环中遍历出来或在Object.keys中列举出来。
拓展:js中遍历一个对象的属性的方法
  • Object.keys() 仅仅返回自身的可枚举属性,不包括继承来的,更不包括Symbol属性
  • Object.getOwnPropertyNames() 返回自身的可枚举和不可枚举属性。但是不包括Symbol属性
  • Object.getOwnPropertySymbols() 返回自身的Symol属性
  • for…in 可以遍历对象的自身的和继承的可枚举属性,不包含Symbol属性
  • Reflect.ownkeys() 返回对象自身的所有属性,不管是否可枚举,也不管是否是Symbol。注意不包括继承的属性

2. v-for 和 v-if 谁先执行?

当它们处于同一节点,v-for的优先级比v-if更高,这意味着 v-if将分别重复运行于每个 v-for循环中。

Vue 会先执行 v-for 得到 item 然后执行 v-if 判断是否展示。

官网并不推荐同一节点使用,原因是会影响性能增加渲染时长,我们可以在渲染之前把数据处理好。

3. v-html会导致哪些问题?

1.可能会导致 xss 攻击。用v-html一定要保证你的内容是可以依赖的,不要用在用户提交的内容上
// 因为用户输入的信息不可信,这样输入什么就会放入什么,v-html就相当于一个innerHTML
2.v-html 会替换掉标签内部的子元素

4. vue中的 watch、computed 和 methods之间的差别以及应用场景:

computed 和 watch,一个是计算,一个是观察,在语义上是有区别的。
计算是通过变量计算来得出数据。而观察是观察一个特定的值,根据被观察者的变动进行相应的变化,在特定的场景下不能相互混用,所以还是需要注意api运用的合理性和语义性。

computer

当页面中有某些数据依赖其他数据进行变动的时候,可以使用计算属性。
需要注意的是,就算在data中没有直接声明出要计算的变量,也可以直接在computed中写入。
computed比较适合对多个变量或者对象进行处理后返回一个结果值,也就是多个变量中的某一个值发生了变化则我们监控的这个值也就会发生变化。(举例:购物车里面的商品列表和总金额之间的关系,只要商品列表里面的商品数量发生变化,或减少或增多或删除商品,总金额都应该发生变化。这里的这个总金额使用computed属性来进行计算是最好的选择。)
计算属性默认只有getter,可以在需要的时候自己设定setter:

// ...
    computed: {
        fullName: {
            // getter
            get: function () {
                return this.firstName + ' ' + this.lastName
            },
            // setter
            set: function (newValue) {
                var names = newValue.split(' ')
                this.firstName = names[0]
                this.lastName = names[names.length - 1]
            }
        }
    }

适用场景:

img

watch

watch和computed很相似,watch用于观察和监听页面上的vue实例,当然在大部分情况下我们都会使用computed,但如果要在数据变化的同时进行异步操作或者是比较大的开销,那么watch为最佳选择。
watch一般用于监控路由、input输入框的值特殊处理等等,它比较适合的场景是一个数据影响多个数据。
watch为一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。直接引用文档例子:

var vm = new Vue({
        el: '#app',
        data: {
            firstName: 'Foo',
            lastName: 'Bar',
            fullName: 'Foo Bar'
        },
        watch: {
            firstName: function (val) {
                this.fullName = val + ' ' + this.lastName
            },
            lastName: function (val) {
                this.fullName = this.firstName + ' ' + val
            }
        }
    })

如果在data中没有相应的属性的话,是不能watch的,这点和computed不一样。
适用场景:

img

methods方法

跟前面的都不一样,我们通常在这里面写入方法,只要调用就会重新执行一次,相应的有一些触发条件,在某些时候methods和computed看不出来具体的差别,但是一旦在运算量比较复杂的页面中,就会体现出不一样。
需要注意的是,computed是具有缓存的,这就意味着只要计算属性的依赖没有进行相应的数据更新,那么computed会直接从缓存中获取值,多次访问都会返回之前的计算结果。

5. Vue-router相关:

5.1 vue-router的几种模式?JS是如何监听HistoryRouter的变化的?

通过浏览器的地址栏来改变切换页面,前端实现主要有两种方式:

			1. 通过hash改变,利用window.onhashchange 监听。
			2. 通过history的改变,进行js操作加载页面。然而history并不像hash那样简单, 因为history的改变,除了浏览器的几个前进后退(使用 history.back(),history.forward() 和 history.go() 方法来完成在用户历史记录中向后和向前的跳转)等操作会主动触发popstate 事件,pushState、replaceState 并不会触发popstate事件,要解决history监听的问题,方法是:
      首先完成一个订阅-发布模式,然后重写history.pushState、history.replaceState 并添加消息通知,这样一来只要history的无法实现监听函数就被我们加上了事件通知,只不过这里用的不是浏览器原生事件,而是通过我们创建的event-bus 来实现通知,然后触发事件订阅函数的执行。
5.2 HashRouter HistoryRouter:

vue-router是Vue官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。 vue-router 默认 hash 模式,还有一种是history模式。

原理:
  1. hash路由:hash模式的工作原理是hashchange事件,可以在window监听hash的变化。我们在 url后面随便添加一个#xx触发这个事件。vue-router默认的是hash模式—使用URL的hash来模拟一个完整的URL,于是当URL改变的时候页面不会重新加载,也就是单页应用了,当#后面的hash发生变化,不会导致浏览器向服务器发出请求,浏览器不发出请求就不会刷新页面,并且会触发hashChange 事件,通过监听hash值的变化来实现更新页面部分内容的操作。

    对于hash模式会创建hashHistory对象,在访问不同的路由的时候,会发生两件事: HashHistory.push()将新的路由添加到浏览器访问历史的栈顶,和HasHistory.replace()替换到当前栈顶的路由。

  2. history路由:

    主要使用HTML5的pushState()和replaceState()这两个api结合window.popstate事件(监听浏览器前进后退)实现的。pushState()可以改变url地址且不会发送请求,replaceState()可以读取历史记录栈,还可以对浏览器记录进行修改。

区别:
  1. hash模式较丑,history模式较优雅;

  2. pushState设置的新URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,故只可设置与当前同文档的URL

  3. pushState设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发记录添加到栈中;

  4. pushState通过stateObject可以添加任意类型的数据到记录中,而hash只可添加短字符串;

  5. pushState可额外设置title属性供后续使用;

  6. hash兼容IE8以上,history兼容IE10以上;

  7. history模式需要后端配合将所有访问都指向index.html,否则用户刷新页面,会导致404错误。

    // hash路由原理:
    // 监听hashchange方法
    window.addEventListener('hashchange', () => {
    			div.innerHTML = location.hash.slice(1)
    })
    
    // history路由原理:
    // 利用 html5 的 history 的 pushState方法结合 window.popstate事件(监听浏览器前进后退)
    function routerChange (pathname){
    			history.pushState(null, null, pathname)
          div.innerHTML = location.pathname
    }
    window.addEventListener('popstate', () => {
    			div.innerHTML = location.pathname
    })
    
5.3 Vue router 原理哪个模式不会请求服务器?

Vue router 的两种方法,hash模式不会请求服务器。

  1. url的hash,就是通常所说的锚点#,javascript通过hashChange事件来监听url的变化,IE7以下 需要轮询。比如这个 URL: http://www.abc.com/#/hello ,hash 的值为 #/hello 。它的特点 在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面
  2. HTML5的History模式,它使url看起来像普通网站那样,以“/”分割,没有#,单页面并没有跳转。 不过使用这种模式需要服务端支持,服务端在接收到所有请求后,都只想同一个html文件,不然 会出现404。因此单页面应用只有一个html,整个网站的内容都在这一个html里,通过js来处理。
5.4 Vue-router 实现懒加载:

懒加载:当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

实现:结合 Vue 的异步组件和 Webpack 的代码分割功能,实现路由组件的懒加载
  1. 首先,可以将异步组件定义为返回一个 Promise 的工厂函数 (该函数返回的 Promise 应该 resolve 组件本身):

    const Foo = () => Promise.resolve({ /* 组件定义对象 */ })
    
  2. 在 Webpack 2 中,我们可以使用动态 import 语法来定义代码分块点 (split point):

    import('./Foo.vue') // 返回 Promise
    

    结合这两者,这就是如何定义一个能够被 Webpack 自动代码分割的异步组件:

    const Foo = () => import('./Foo.vue')
    

    在路由配置中什么都不需要改变,只需要像往常一样使用 Foo :

    const router = new VueRouter({
      routes: [
        { path: '/foo', component: Foo }
      ]
    })
    

6. Vue-alive 干嘛的?

keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染。
prop:
include: 字符串或正则表达式。只有匹配的组件会被缓存。
exclude: 字符串或正则表达式。任何匹配的组件都不会被缓存。

7. Vue组件通信相关:

7.1 组件通信的8种方法:
1. props 和 $emit

这是最常用的父子组件通信方式,父组件通过props向子组件传递数据,子组件通过$emit触发事件传递数据给父组件。

2. $attrs 和 $listeners

第一种方式处理父子组件之间的数据传输有一个问题:如果多层嵌套,父组件A下面有子组件B,组件B下面有组件C,这时如果组件A想传递数据给组件C怎么办呢?如果采用第一种方法,我们必须让组件A通过props传递消息给组件B,组件B再通过props传递消息给组件C;要是组件A和组件C之间有更多的组件,那采用这种方式就很复杂了。从Vue 2.4开始,提供了$attrs 和 $listeners来解决这个问题,能够让组件A直接传递消息给组件C。

3. v-model

父组件通过v-model传递值给子组件时,会自动传递一个value的prop属性,在子组件中通过 this.$emit(‘input', val)自动修改v-model绑定的值.

4. provider 和 inject

父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量。不论子组件有多深, 只要调用了inject那么就可以注入provider中的数据。而不是局限于只能从当前父组件的props属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。

5. 中央事件总线

上面方式都是处理的父子组件之间的数据传递,那如果两个组件不是父子关系,是兄弟组件如何通信?这种情况下可以使用中央事件总线的方式。新建一个Vue事件bus对象,然后通过bus.$emit触发事件,通过bus.$on监听触发的事件。

image-20210907095757157
6. parent 和 children
7. boradcast 和 dispatch

vue1.0中提供了这种方式,但vue2.0中没有,但很多开源软件都自己封装了这种方式,比如min ui、element ui和iview等。 比如如下代码一般都作为一个mixins去使用,broadcast是向特定的父组件触发事件,dispatch是向特定的子组件触发事件,本质上这种方式还是on和emit的封装,但在一些基础组件中却很实用。

8. vuex处理组件之间的数据交互

如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候上面这一些方法可能不利于项目的维护,vuex的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。

7.2 组件间传值 => attrs 和 listeners

$attrs 和 $listeners的作用:解决多层嵌套情况下,父组件A下面有子组件B,组件B下面有组件C,组件A传递数据给组件C的问题,这个方法是在Vue 2.4提出的。

实现方式:
// C组件
Vue.component('C', {
     template:
  			`<div>
     				<input type="text" v-model="$attrs.messageC" @input="passCData($attrs.messageC)">
     		</div>`,
     methods: {
				passCData(val) {
          //触发父组件A中的事件
          this.$emit('getCData', val)
				}
     }
})

// B组件
Vue.component('B', {
 			data() {
     		return {
         		myMessage: this.message
				}
      },
			template:
  				`<div>
							<input type="text" v-model="myMessage" @input="passData(myMessage)">
							<C v-bind="$attrs" v-on="$listeners"></C>
					</div>`,
			//得到父组件传递过来的数据
  		props: ['message'],
			methods:	{
					passData(val){
							//触发父组件中的事件
            	this.$emit('getChildData', val)
					}
      }
})

// A组件
Vue.component('C', {
     template:
  				`<div>
     						<input type="text" v-model="$attrs.messageC" @input="passCData($attrs.messageC)">
     			</div>`,
     methods: {
					passCData(val) {
            //触发父组件A中的事件
            this.$emit('getCData', val)
					}
     }
})
Vue.component('A', {
 			template:
  				`<div>
 								<p>this is parent compoent!</p>
 								<B :messageC="messageC" :message="message" v-on:getCData="getCData"
									 v-on:getChildData="getChildData(message)"></B>
					</div>`,
 			data() {
     			return {
         			message:'Hello',
       				messageC:'Hello c'
     			}
			}, 
  		methods: {
        	getChildData(val) {
            	console.log('这是来自B组件的数据')
					},
          //执行C子组件触发的事件
          getCData(val) {
            console.log("这是来自C组件的数据:" + val)
          }
      }
})

var app = new Vue({
  el: '#app',
  template: `<div><A></A></div>`
})

C组件中能直接触发getCData的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性;

通过v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props 声明的)

7.3 组件传值 => 事件总线

中央事件总线:主要用来解决兄弟组件之间的通信问题。

实现方式:

新建一个Vue事件bus对象,然后通过bus. e m i t 触发事件, b u s . emit 触发事件,bus. emit触发事件,bus.on监听触发的事件。

// 组件A
Vue.component('brother1', {
 		data() {
     		return {
        		myMessage:'Hello brother1'
				}
		},
		template:
				`<div>
 						<p>this is brother1 compoent!</p>
 						<input type="text" v-model="myMessage" @input="passData(myMessage)">
				</div>`,
  	methods: {
				passData(val) {
          		// 触发全局事件globalEvent
          		bus.$emit('globalEvent',val)
				}
    }
})

// 组件B
Vue.component('brother2', {
		template:
  			`<div>
							<p>this is brother2 compoent!</p> <p>brother1传递过来的数据:{{brothermessage}}</p> 
 				</div>`,
		data() {
     		return {
         		myMessage:'Hello brother2',
         		brothermessage:''
				}
    },
		mounted() {
				//绑定全局事件globalEvent
      	bus.$on('globalEvent', (val) => {
        		this.brothermessage=val;
     		})
		}
})

//中央事件总线
var bus = new Vue();
var app = new Vue({
 		el:'#app',
 		template:
  			`<div>
 							<brother1></brother1>
 							<brother2></brother2>
 				</div>`
})

8. Vuex相关

8.1 存储刷新:刷新页面保持数据不被清空
  1. 监听beforeunload,在刷新之前把(this.$store.state)存入localStorage;
  2. 刷新完页面初始化时在created中取出重新存入vuex中;
// 在页面加载时读取localStorage状态,复制对象是解决 新vuex管理的状态中新添加的字段也可以存入
localStorage.getItem("publicTit") && this.$store.replaceState(Object.assign(this.$store.state,JSON.parse(localStorage.getItem("publicTit"))));

9. Vue 自定义指令:

一、注册自定义指令

​ Vue自定义指令和组件一样存在着全局注册和局部注册两种方式。

注册全局指令的方式,通过 Vue.directive( id, [definition] ) ,第一个参数为自定义指令名称(指令名称不需要加 v- 前缀,默认是自动加上前缀的,使用指令的时候一定要加上前缀),第二个参数可以是对象数据,也可以是一个指令函数。

<div id="app" class="demo">
    <!-- 全局注册 -->
    <input type="text" placeholder="我是全局自定义指令" v-focus>
</div>
<script>
    Vue.directive("focus", {
        inserted: function(el){
            el.focus();
        }
    })
    new Vue({
        el: "#app"
    })
</script>

注册局部自定义指令,通过在Vue实例中添加 directives 对象数据注册局部自定义指令。

<div id="app" class="demo">
    <!-- 局部注册 -->
    <input type="text" placeholder="我是局部自定义指令" v-focus2>
</div>
<script>
    new Vue({
        el: "#app",
        directives: {
            focus2: {
                inserted: function(el){
                    el.focus();
                }
            }
        }
    })
</script>

这个简单案例当中,我们通过注册一个 v-focus 指令,实现了在页面加载完成之后自动让输入框获取到焦点的小功能。

二、钩子函数

一个指令定义对象可以提供如下几个钩子函数 (均为可选):

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • update:所在组件的 VNode 更新时调用。
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:只调用一次,指令与元素解绑时调用。

指令钩子函数会被传入以下参数:

  • el: 指令所绑定的元素,可以用来直接操作 DOM,就是放置指令的那个元素。
  • binding: 一个对象,里面包含了几个属性,这里不多展开说明,官方文档上都有很详细的描述。
  • vnode:Vue 编译生成的虚拟节点。
  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
<script>
    Vue.directive('demo', function (el, binding) {
    console.log(binding.value.color) // "white"
    console.log(binding.value.text)  // "hello!"
    })
</script>

10. vue 其它相关:

1. Vue强制刷新组件的方法:

使用vue进行开发时,如果要刷新当前路由,则调用router.go(0)方法即可。但是某些情况下,我们可能要求仅仅刷新某个组件,而不是路由,那么我们应该怎么做呢?

① 使用this.$forceUpdate强制重新渲染

如果要在组件内部中进行强制刷新,则可以调用**this.$forceUpdate()**强制重新渲染组件,从而达到更新目的。

<template>
		<button @click="reload()">刷新当前组件</button>
</template>
<script>
export default {
    name: 'comp',
    methods: {
        reload() {
            this.$forceUpdate()
        }
    }
}
</script>
② 使用watch监测值的变化,v-if以及this.$nextTick配合使用

如果是刷新某个子组件,则可以通过v-if指令实现。我们知道,当v-if的值发生变化时,组件都会被重新渲染一遍。因此,利用v-if指令的特性,可以达到强制刷新组件的目的。

2.Vue 插槽:

在构建页面过程中一般会把用的比较多的公共的部分抽取出来作为一个单独的组件,但是在实际使用这个组件的时候却又不能完全的满足需求,可能希望在这个组件中添加一点东西,这时候我们就需要用到插槽来分发内容。

官方文档对于插槽的应用场景是这样描述的:我们经常需要向一个组件传递内容,Vue 自定义的 <slot> 元素让这变得非常简单,只要在需要的地方加入插槽即可。

插槽是子组件中提供给父组件使用的一个占位符,用<slot></slot>表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的<slot></slot>标签。

插槽的作用:让用户可以拓展组件,更好地复用组件和对其做定制化处理。
插槽的分类:
1.默认插槽
2.具名插槽
// <slot>元素有一个特殊的 attribute:name, 这个 attribute 可以用来定义额外的插槽
// 一个不带name的<slot>出口会带有隐含的名字"default"		<=  默认插槽
// 父组件在向具名插槽提供内容的时候,我们可以在一个<template>元素上使用 v-slot 指令,并以v-slot的参数的形式提供其名称: v-slot:footer
// 父组件中会向子组件中具名传递对应的模板内容,而没有指定名字的模板内容会传递给子组件中不带 name 的 <slot> 
// 注意: v-slot 只能添加在 <template> 上 具名插槽在书写的时候可以使用缩写, v-slot用 # 来代替
3.作用域插槽(主要解决的是父组件在向子组件插槽传递模板内容时存在访问子组件数据的问题)

11. vue生命周期、钩子函数

vue每个组件都是独立的,每个组件都有一个属于它的生命周期。

vue生命周期在项目中的执行顺序: 初始化、创建、挂载、更新、销毁

beforeCeate() => data() => created() => beforeMount() => mounted()
当更新会在created之后插入: beforeUpdate => updated
关闭页面组件最终都会销毁: beforeDestroy() => destroyed()

强调几点:

  1. beforeCeate在事件和生命周期钩子初始化前调用
  2. data的初始化是在created前完成数据观测(data observer)

vue中内置方法属性的运行顺序: (methods、computed、data、watch、props)

从源码可以知道: props => methods =>data => computed => watch

// https://github.com/vuejs/vue/blob/dev/src/core/instance/state.js
export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

Vue和React生命周期的比较:

相同点:

  1. react和vue异步请求都最好在挂载函数(componentDidMount和mounted)中执行
  2. 生命周期都包含:初始化、创建、挂载、销毁、更新
  3. 都是通过refs获取dom元素
  4. 都需要进行卸载和数据的销毁:setTimeout、setInterval、removeEventListener等

不同点:

  1. 更新过程挂载阶段生命周期不一样,react用新函数componentDidUpdate,vue还是用mounted
  2. 写法大不一样

四、React相关

React组件生命周期相关:

1. 生命周期三个不同的阶段:

初始渲染阶段:这是组件即将开始其生命之旅并进入 DOM 的阶段。
更新阶段:一旦组件被添加到 DOM,它只有在 prop 或状态发生变化时才可能更新和重新渲染。这些只发生在这个阶段。
卸载阶段:这是组件生命周期的最后阶段,组件被销毁并从 DOM 中删除。

2. useEffect相当于class里面的那几个生命周期?
componentDidMonut、componentDidUpdate
  1. React首次渲染和之后的每次渲染都会调用一遍useEffect函数,而之前我们要用两个生命周期函数分别表示首次渲染(componentDidMonut)和更新导致的重新渲染(componentDidUpdate)。
  2. useEffect中定义的函数的执行不会阻碍浏览器更新视图,也就是说这些函数时异步执行的,而componentDidMonutcomponentDidUpdate中的代码都是同步执行的。(个人认为这个有好处也有坏处吧,比如我们要根据页面的大小,然后绘制当前弹出窗口的大小,如果是异步的就不好操作了。)

React渲染原理分析:

JSX,是 React.createElement 方法的语法糖,使用 JSX 能够直观的展现 UI 及其交互,实现关注点分离。
每个 react 组件的顶部都要导入 React,因为 JSX 实际上依赖 Babel(@babel/preset-react)来对语法进行转换,最终生成React.createElemnt的嵌套语法。createElement()方法定义如下:

React.createElement(type, [props], [...children]);
// createElement()接收三个参数: 元素类型、属性值、子元素,它最终会生成 Virtual DOM。

jsx 的本质是什么?

  • JSX是React引入的,但不是React独有的
  • React已经将它作为一个独立标准开放,其他项目也可用
  • React.createElement是可以自定义修改的
  • 本身功能已经完备,和其他标准兼容和扩展没问题

jsx其实是react的语法糖,无法直接被浏览器解析,需要转换为js,是通过React.createElement函数来实现的即h函数,返回vnode,第一个参数可能是元素,也可能是组件,通过 组件的首字母一定是大写来区分(react规定),最终通过(vdom中的)patch(函数)来渲染。(我们在写组件的时候需要import React,但是代码可见的地方并没有直接用,就是提供React.createElement转换jsx)

React.createElement('div',null,child1,child2,child3)
React.createElement('div',null,[child1,child2,child3])
React.createElement('List',null,child1,child2,"文本节点")

context是什么, 有什么用途??????????

当你的应用控件层级很多,有时候需要把Props逐级传递,尤其是把一个Props从顶层传递到最底层,中间每个控件都得去帮忙传递Props,尽管他们可能用不到这个Props。

Context 可以共享对于一个组件树而言是“全局”的数据,这样就不必显式地通过组件树的逐层传递 props。

谨慎使用,因为这会使得组件的复用性变差。

<MyContext.Provider> // 顶级父组件中提供
<MyContext.Consumer> // 需要用到的子组件使用

setState是同步的还是异步的?

  • setState在生命周期函数和合成函数中都是异步更新。
  • setState在setTimeout、原生事件和async函数中都是同步更新。每次更新不代表都会触发render,如果render内容与newState有关联,则会触发,否则即便setState多次也不会render。
  • 如果newState内容与render有依赖关系,就不建议同步更新,因为每次render都会完整的执行一次批量更新流程(只是dirtyComponets长度为1,stateQueue也只有该组件的newState),调用一次diff算法,这样会影响React性能。
  • 如果没有必须同步渲染的理由,不建议使用同步,会影响react渲染性能
image-20210903153255627
isBatchingUpdates

决定setState是否异步的属性isBatchingUpdates, 表示是否正处于更新阶段。
isBatchingUpdates默认为false,也就是说,默认不会让setState异步执行。
但是有一个方法batchedUpdates,这个方法会去修改isBatchingUpdates的值为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,从而使isBatchingUpdates变为true

dirtyComponents

若正处于isBatchingUpdates: true阶段,state状态存储在dirtyComponents中,当isBatchingUpdates: false再批量执行。
那batchedUpdates方法是谁调用的呢?我们再往上追溯一层,原来是ReactMount.js中的_renderNewRootComponent方法。
也就是说,整个将React组件渲染到DOM的过程就处于一个大的事务中了。

结论

在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用 setState 不会同步更新 this.state,除此之外的setState调用会同步执行this.state。

  • 所谓“除此之外”,指的是绕过React通过 addEventListener 直接添加的事件处理函数,还有通过setTimeout || setInterval 产生的异步调用。
  • 简单一点说, 就是经过React 处理的事件是不会同步更新 this.state的,通过 addEventListener || setTimeout/setInterval 的方式处理的则会同步更新。

React Router相关:

Facebook对react进行持续的改进,路由作为其中最重要的一部分,在4.0版本对其进行了大量的优化,总的来说,简单易用!
之前使用react路由的时候,我们引入的是react-router包,现在改版之后,我们引入的是react-router-dom包。

改版之后的react-router-dom路由,我们要理解三个概念,Router、Route和Link
Router:我们可以把它看做是react路由的一个路由外层盒子,它里面的内容就是我们单页面应用的路由以及路由组件。(Router下面只能包含一个盒子标签)
Route:代表了你的路由界面,path代表路径,component代表路径所对应的界面。()
Link:代表一个链接,在html界面中会解析成a标签,点击会切换到链接组件的。作为一个链接,必须有一个to属性,代表链接地址。(首页)

1. 什么是React 路由?

React 路由是一个构建在 React 之上的强大的路由库,它有助于向应用程序添加新的屏幕和流。这使 URL 与网页上显示的数据保持同步。它负责维护标准化的结构和行为,并用于开发单页 Web 应用。 React 路由有一个简单的API。

<switch>
    <route exact="" path="’/’" component="{Home}/">
    		<route path="’/posts/:id’" component="{Newpost}/">
    				<route path="’/posts’" component="{Post}/">
						</route>
      	</route>
  	</route>
</switch>
2. 为什么需要 React 中的路由?

Router 用于定义多个路由,当用户定义特定的 URL 时,如果此 URL 与 Router 内定义的任何 “路由” 的路径匹配,则用户将重定向到该特定路由。所以基本上我们需要在自己的应用中添加一个 Router 库,允许创建多个路由,每个路由都会向我们提供一个独特的视图。

3. 为什么React Router v4中使用 switch 关键字 ?

虽然 <div> 用于封装 Router 中的多个路由,当你想要仅显示要在多个定义的路线中呈现的单个路线时,可以使用“switch”关键字。使用时,<switch> 标记会按顺序将已定义的URL与已定义的路由进行匹配。找到第一个匹配项后,它将渲染指定的路径,从而绕过其它路线。

4. React Router 的优点:
  • 就像 React 基于组件一样,在 React Router v4 中,API 是 ‘All About Components’。可以将Router 可视化为单个根组件,其中我们将特定的子路由()包起来。
  • 无需手动设置历史值:在 React Router v4 中,我们要做的就是将路由包装在组件中。
  • 包是分开的:共有三个包,分别用于 Web、Native 和 Core。这使我们应用更加紧凑,基于类似的编码风格很容易进行切换。

Redux相关:

Redux:Redux 是当今最热门的前端开发库之一。它是 JavaScript 程序的可预测状态容器,用于整个应用的状态管理。使用 Redux 开发的应用易于测试,可以在不同环境中运行,并显示一致的行为。

描述redux 单项数据流!!!!!!!
1. 数据如何通过 Redux 流动?
  1. 首先,用户(通过View)发出Action,发出方式就用到了dispatch方法;
  2. 然后,Store自动调用Reducer,并且传入两个参数:当前State 和 收到的Action,Reducer会返回新的State;
  3. State一旦有变化,Store就会调用监听函数,来更新。
2. Redux遵循的三个原则是什么?
  1. 单一事实来源:整个应用的状态存储在单个 store 中的对象/状态树里。单一状态树可以更容易地跟踪随时间的变化,并调试或检查应用程序。
  2. 状态是只读的:改变状态的唯一方法是去触发一个动作。动作是描述变化的普通 JS 对象。就像 state 是数据的最小表示一样,该操作是对数据更改的最小表示。
  3. 使用纯函数进行更改:为了指定状态树如何通过操作进行转换,你需要纯函数:一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数。
3. 你对单一事实来源有什么理解?

Redux 使用 “Store” 将程序的整个状态存储在同一个地方。因此所有组件的状态都存储在 Store 中,并且它们从 Store 本身接收更新。单一状态树可以更容易地跟踪随时间的变化,并调试或检查程序。

4 .Redux 由以下组件组成:
  1. Action – 这是一个用来描述发生了什么事情的对象。
  2. Reducer – 这是一个确定状态将如何变化的地方。
  3. Store – 整个程序的状态/对象树保存在Store中。
  4. View – 只显示 Store 提供的数据。
5. 如何在Redux中定义Action?

React 中的 Action 必须具有 type 属性,该属性指示正在执行的 ACTION 的类型。必须将它们定义为字符串常量,并且还可以向其添加更多的属性。在 Redux 中,action 被名为 Action Creators 的函数所创建。以下是 Action 和 Action Creator 的示例:

function addTodo (text) {
  			return {
          type: ADD_TODO,
          text
        }
}
6. 解释 Reducer 的作用

Reducers 是纯函数,它规定应用程序的状态怎样因响应 ACTION 而改变。Reducers 通过接受先前的状态和 action 来工作,然后它返回一个新的状态。它根据操作的类型确定需要执行哪种更新,然后返回新的值。如果不需要完成任务,它会返回原来的状态。

7. Store Redux 中的意义是什么?

Store 是一个 JavaScript 对象,它可以保存程序的状态,并提供一些方法来访问状态、调度操作和注册侦听器。应用程序的整个状态/对象树保存在单一存储中。因此,Redux 非常简单且是可预测的。我们可以将中间件传递到 store 来处理数据,并记录改变存储状态的各种操作。所有操作都通过 reducer 返回一个新状态。

8. Redux 有哪些优点?

• 结果的可预测性 - 由于总是存在一个真实来源,即 store ,因此不存在如何将当前状态与动作和应用的其他部分同步的问题。
• 可维护性 - 代码变得更容易维护,具有可预测的结果和严格的结构。
• 服务器端渲染 - 你只需将服务器上创建的 store 传到客户端即可。这对初始渲染非常有用,并且可以优化应用性能,从而提供更好的用户体验。
• 开发人员工具 - 从操作到状态更改,开发人员可以实时跟踪应用中发生的所有事情。
• 社区和生态系统 - Redux 背后有一个巨大的社区,这使得它更加迷人。一个由才华横溢的人组成的大型社区为库的改进做出了贡献,并开发了各种应用。
• 易于测试 - Redux 的代码主要是小巧、纯粹和独立的功能。这使代码可测试且独立。
• 组织 - Redux 准确地说明了代码的组织方式,这使得代码在团队使用时更加一致和简单。

hooks相关

1. hooks的优缺点:

优点:

  • 更容易复用代码

    这点应该是react hooks最大的优点,它通过自定义hooks来复用状态,从而解决了类组件有些时候难以复用逻辑的问题。
    类组件的逻辑复用方式是高阶组件和renderProps。
    hooks是怎么解决这个复用的问题呢,具体如下:
    1. 每调用useHook一次都会生成一份独立的状态,这个没有什么黑魔法,函数每次调用都会有一份独立的作用域。
    2. 虽然状态(from useState)和副作用(useEffect)的存在依赖于组件,但它们可以在组件外部进行定义。这点是class component做不到的,你无法在外部声明state和副作用(如 componentDidMount)。
    
  • 清爽的代码风格 函数式编程风格,函数式组件、状态保存在运行环境、每个功能都包裹在函数中,整体风格更清爽,更优雅。

  • 代码量更少

    1. 向props或状态取值更加方便,函数组件的取值都从父级作用域直接取,而类组件需要先访问实例引用this,再访问其属性state和props,多了一步。
    2. 更改状态也变得更加简单, this.setState({ count:xxx }) 变成 setCount(xxx)。
    
    • 更容易发现无用的状态和函数 对比类组件,函数组件里面的unused状态和函数更容易被发现。
    • 更容易拆分组件 写函数组件的时候,你会更愿意去拆分组件,因为函数组件写起小组件比类组件要省事。

缺点:

  • 部分代码从主动式变成响应式

    写函数组件时,你不得不改变一些写法习惯。你必须深入理解 useEffect 和 useCallback 这些api的第二个参数的作用。其次,还有下面几点:
    1. useEffect的依赖参数并不好写,你需要花时间去判断该把什么变量加入到依赖数组,幸运的是 eslint-plugin-react-hooks 很多场景可以帮你解决这点,但有时得靠你自己加以判断。
    2. useEffect很容易出错,它是响应式的,当某个依赖项变化时它才会被调用。有时,useEffect会发生比你预期更多的调用次数。你必须去理解它的调用时机、调用时的状态老旧问题,这不直观,也难以维护,这点在团队协作中很明显,你的队友有时会难以理解你useEffect的触发时机以及其作用。
    
  • 状态不同步

    不好用的useEffect,这绝对可以成为摒弃react hooks的理由。
    函数的运行是独立的,每个函数都有一份独立的作用域。当我们处理复杂逻辑的时候,经常会碰到“引用不是最新”的问题。
    

组件相关:

1. 组件通信
• 父组件向子组件通信:

父组件通过向子组件传递 props,子组件得到 props 后进行相应的处理。

拓展:父组件里怎么调用子组件里的方法?可以通过在父组件中给子组件绑定ref

• 子组件向父组件通信:

利用回调函数,可以实现子组件向父组件通信:父组件将一个函数作为 props 传递给子组件,子组件调用该回调函数,便可以向父组件通信。

• 跨级组件 间 通信:

① 中间组件层层传递 props
② 使用 context 对象:

context 相当于一个全局变量,是一个大容器,我们可以把要通信的内容放在这个容器中,这样一来,不管嵌套有多深,都可以随意取用。
使用 context 也很简单,需要满足两个条件:
• 上级组件要声明自己支持 context,并提供一个函数来返回相应的 context 对象
• 子组件要声明自己需要使用 context
• 非嵌套组件间通信:

① 利用二者共同父组件的 context 对象进行通信

② 使用自定义事件的方式

2. 函数组件 与 类组件 的区别?
函数组件
// 函数组件接收一个单一的 props 对象并返回了一个React元素
function Welcome (props) {
  	return <h1>Welcome {props.name}</h1>
}
ReactDOM.render(<Welcome name='react' />, document.getElementById('root'));
类组件
class Welcome extends React.Component {
  	render() {
    		return (
      			<h1>Welcome { this.props.name }</h1>
    		);
    }
}
ReactDOM.render(<Welcome name='react' />, document.getElementById('root'));
  1. 无论是使用函数或是类来声明一个组件,它决不能修改它自己的 props。
  2. 所有 React 组件都必须是纯函数,并禁止修改其自身 props 。
  3. React是单项数据流,父组件改变了属性,那么子组件视图会更新。
  4. 属性 props 是外界传递过来的,状态 state 是组件本身的,状态可以在组件中任意修改。
  5. 组件的属性和状态改变都会更新视图。
区别

函数组件和类组件当然是有区别的,而且函数组件的性能比类组件的性能要高,因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可。为了提高性能,尽量使用函数组件。

区别函数组件类组件
是否有 this没有
是否有生命周期没有
是否有状态 state没有
3. 纯组件PureComponent干嘛用的?与Component的区别?

React.PureComponent 与 React.Component 几乎完全相同,但 React.PureComponent 通过prop和state的浅对比来实现 shouldComponentUpate()。

React.PureComponent 的 shouldComponentUpdate() 只会对对象进行浅对比。如果对象包含复杂的数据结构,它可能会因深层的数据不一致而产生错误的否定判断(表现为对象深层的数据已改变视图却没有更新, 原文:false-negatives)。

// 如果定义了 shouldComponentUpdate(),无论组件是否是 PureComponent,它都会执行shouldComponentUpdate结果来判断是否 update。如果组件未实现 shouldComponentUpdate(),则会判断该组件是否是 PureComponent,如果是的话,会对新旧 props、state 进行 shallowEqual 比较,一旦新旧不一致,会触发 update。

* 浅对比:通过遍历对象上的键执行相等性,并在任何键具有参数之间不严格相等的值时返回false。 当所有键的值严格相等时返回true。shallowEqual

// React.PureComponent 的 shouldComponentUpate() 会忽略整个组件的子级,请确保所有的子级组件也是“Pure”的
(扩展:Immutable.js、浅比较和深比较)
/*
ImmutableJS 通过结构共享提供了不可变、持久化集合:
	不可变:一旦创建,一个集合便不能再被修改。
	持久化:对集合进行修改,会创建一个新的集合。之前的集合仍然有效。
	结构共享:新的集合会尽可能复用之前集合的结构,以最小化拷贝操作来减少性能、内存消耗,提高性能。

ImmutableJS 结合 PureComponent 可以很大程度的减少应用 re-render 的次数,可以大量的提高性能。
不足:
		获取组件属性必须用 get 或 getIn 操作(除了 Record 类型),这样和原生的.操作比起来就麻烦多了,如果组件之前已经写好了,还需要大量的修改。
		ImmutableJS 库体积比较大,大概56k,开启 gzip 压缩后16k。
		学习成本。
		难以调试,在 redux-logger 里面需要在 stateTransformer 配置里执行 state.toJS()。
*/

当你期望只拥有简单的props和state时,才去继承 PureComponent 来提升性能,或者在你知道深层的数据结构已经发生改变时使用 forceUpate() 。或者考虑使用不可变对象来促进嵌套数据的快速比较。

PureComponent的优点:不需要开发者自己实现shouldComponentUpdate,就可以进行简单的判断来提升性能。

PureComponent的缺点:可能会因深层的数据不一致而产生错误的否定判断,从而shouldComponentUpdate结果返回false,界面得不到更新。(避免此类问题最简单的方式是,避免使用值可能会突变的属性或状态,而是使用副本来返回新的变量。)

4. shouldComponentUpdate有什么作用?

避免一些不必要的render渲染。https://blog.csdn.net/u013003052/article/details/87894750

怎么判断渲染不渲染?一般做什么事情控制他渲染或者不渲染?传入的参数是如何比较的?

在shouldComponentUpdate()函数返回true时,才会触发render钩子。使用shouldComponentUpdate()以让React知道当前状态或属性的改变是否不影响组件的输出,默认返回ture,返回false时不会重写render,而且该方法并不会在初始化渲染和使用forceUpdate()时 被调用,我们要做的只是这样:

shouldComponentUpdate(nextProps, nextState) {
		return nextState.someData !== this.state.someData
}
// nextProps: 表示下一个props。
// nextState: 表示下一个state的值。

forceUpdate():调用forceUpdate()会导致组件跳过shouldComponentUpdate(),直接调用render()。这将触发组件的正常生命周期方法,包括每个子组件的shouldComponentUpdate()方法。forceUpdate就是重新render。有些变量不在state上,当时你又想达到这个变量更新的时候,刷新render或者state里的某个变量层次太深,更新的时候没有自动触发render,这些时候都可以手动调用forceUpdate自动触发render。

虚拟dom相关:

DOM (Document Objecet Model) 文档对象模型,DOM定义了访问HTML或是XML文档的标准,是一种使程序动态的访问,更新HTML文档或XML文档等的内容、结构以及样式的平台,操作接口,DOM定义了文档的对象和属性,以及访问它们的接口。
虚拟DOM(Virtual dom),也就是我们常说的虚拟DOM节点,它是通过JS的Object对象模拟DOM中的节点,然后再通过特定的render方法将其渲染成真实的DOM的节点,比操作真实DOM减少性能开销。

虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能。

1. 实现过程

​ 1• 用 JavaScript 对象结构表示 DOM 树的结构;
​ 2• 用这个树构建一个真正的 DOM 树,插到文档当中,当状态变更的时候重新构造一棵新的对象树;
​ 3• 用新的树和旧的树进行比较,记录两棵树差异把 2 所记录的差异应用到步骤 1 所构建的真正的DOM 树上,视图就更新。

VirtualDOM 是 React中的DOM对象。React使用JSX编写虚拟DOM对象,经过Babel编译之后会生成真正的DOM,然后会将真正的DOM插入(Render)到页面。由于更新 dom 成本昂贵,所以我们通过读取 virtual dom 这个低成本的对象,然后将vitural dom 通过 js 渲染到界面。

如果操作 dom 或更新了 dom ,我们可以通过 diff 方法,来对比新旧 virtual dom 后,找出不同地方,然后只更新发生变化的部分。这样来更新 dom 就大大地节省了成本,并且提供良好的用户体验。

// 创建虚拟节点
var temp = document.createDocumentFragment();

for( var i=0; i<100; i++ ){
    var li = document.createElement('li');
    // 将li放入虚拟节点中,这一步操作不会产生重绘回流
    temp.appendChild(li);
    li.innerHTML = i;
}
// 真实节点的操作
ul1.appendChild(temp);
2. 虚拟dom和real dom区别,性能差异?

减少DOM的操作:虚拟dom可以将多次操作合并为一次操作,减少DOM操作的次数。

3. diff算法是什么?

传统的 diff 算法也是一直都有的。diff 算法会对比新老虚拟 DOM ,记录下他们之间的变化,然后将变化的部分更新到视图上。其实之前的 diff 算法,是通过循环递归每个节点,然后进行对比,复杂程度为 O(n^3) ,n 是树中节点的总数,这样性能是非常差的。

4. dom-diff 的原理?

dom-diff 算法会比较前后虚拟 DOM ,从而得到 patches (补丁),然后与老 Virtual DOM 进行对比,将其应用在需要更新的地方,将 O(n^3) 复杂度的问题转换成 O(n^1=n) 复杂度的问题,得到新的Virtual DOM。
降低时间复杂度的方法:
1.两个不同类型的元素会产生不同的树;
2.对于同一层级的一组子节点,它们可以通过唯一 key 进行区分。

// diff比对:把树形结构按照层级分解,只比较同级元素
// key在diff里面的作用:提高了虚拟DOM比对的性能(key值要稳定),使用key来帮助React识别列表中所有子组件的最小变化
给列表结构的每个单元添加唯一的key属性,方便比较。
React只会匹配相同class(组件的名字)的 component 合并操作,
调用 component 的 setState 方法的时候, React 将其标记为 dirty,
到每一个事件循环结束, React 检查所有标记为 dirty的component重新绘制。

怎么解决 react 首页过大的问题?

  • 提高下载静态资源的速度

    提升下载静态资源的速度的方法有很多。升级HTTP1.1到HTTP2.0、开启gzip数据压缩、上cdn等,这些都是最有效提升速度的方法。

  • 优化代码提高运行速度

    优化代码;

    利用React的懒加载,在用webpack打包的时候进行代码的分割,减少首屏加载的体积;

    当然加载过程中提升用户体验也是重要的一环,虽然不能有效的提升运行速度,但可以使用户更加愉悦。

遇到最有挑战的 react 问题???

4. react和vue有什么区别吗?

  • M:Model 模型
  • V:View 视图
  • C:Controller 控制器
  • VM:ViewModel 视图模型
image-20210907173323758

​ vue的标签如v-model,比react的方便,其实也是一层封装好的语法糖,绑一个input也就不用再写change事件之类的。

​ react的jsx功能很强大,扩展性极强。

​ vue的dom操作很方便,各种方便的for指令 if指令等等。

​ react的思想很棒,各种抽象和模式使得代码更加美观等等。

1、监听数据变化的实现原理不同

Vue通过 getter/setter以及一些函数的劫持,能精确知道数据变化。

React默认是通过比较引用的方式(diff)进行的,如果不优化可能导致大量不必要的VDOM的重新渲染。为什么React不精确监听数据变化呢?这是因为Vue和React设计理念上的区别,Vue使用的是可变数据,而React更强调数据的不可变,两者没有好坏之分,Vue更加简单,而React构建大型应用的时候更加鲁棒。

2、数据流的不同

img

Vue1.0中可以实现两种双向绑定:父子组件之间,props可以双向绑定;组件与DOM之间可以通过v-model双向绑定。Vue2.x中去掉了第一种,也就是父子组件之间不能双向绑定了(但是提供了一个语法糖自动帮你通过事件的方式修改),并且Vue2.x已经不鼓励组件对自己的 props进行任何修改了。

React一直不支持双向绑定,提倡的是单向数据流,称之为onChange/setState()模式。不过由于我们一般都会用Vuex以及Redux等单向数据流的状态管理框架,因此很多时候我们感受不到这一点的区别了。

3、HoC和mixins

4、组件通信的区别

img

Vue中有三种方式可以实现组件通信:父组件通过props向子组件传递数据或者回调,虽然可以传递回调,但是我们一般只传数据;子组件通过事件向父组件发送消息;通过V2.2.0中新增的provide/inject来实现父组件向子组件注入数据,可以跨越多个层级。

React中也有对应的三种方式:父组件通过props可以向子组件传递数据或者回调;可以通过 context 进行跨层级的通信,这其实和 provide/inject 起到的作用差不多。React 本身并不支持自定义事件,而Vue中子组件向父组件传递消息有两种方式:事件和回调函数,但Vue更倾向于使用事件。在React中我们都是使用回调函数的,这可能是他们二者最大的区别。

5、模板渲染方式的不同

在表层上,模板的语法不同,React是通过JSX渲染模板。而Vue是通过一种拓展的HTML语法进行渲染,但其实这只是表面现象,毕竟React并不必须依赖JSX。

在深层上,模板的原理不同,这才是他们的本质区别:React是在组件JS代码中,通过原生JS实现模板中的常见语法,比如插值,条件,循环等,都是通过JS语法实现的,更加纯粹更加原生。而Vue是在和组件JS代码分离的单独的模板中,通过指令来实现的,比如条件语句就需要 v-if 来实现对这一点,这样的做法显得有些独特,会把HTML弄得很乱。

举个例子,说明React的好处:react中render函数是支持闭包特性的,所以我们import的组件在render中可以直接调用。但是在Vue中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以我们import 一个组件完了之后,还需要在 components 中再声明下,这样显然是很奇怪但又不得不这样的做法。

6、渲染过程不同

Vue可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。

React在应用的状态被改变时,全部子组件都会重新渲染。通过shouldComponentUpdate这个生命周期方法可以进行控制,但Vue将此视为默认的优化。

如果应用中交互复杂,需要处理大量的UI变化,那么使用Virtual DOM是一个好主意。如果更新元素并不频繁,那么Virtual DOM并不一定适用,性能很可能还不如直接操控DOM。

7、框架本质不同

Vue本质是MVVM框架,由MVC发展而来;

React是前端组件化框架,由后端组件化发展而来。

8、Vuex和Redux的区别

从表面上来说,store注入和使用方式有一些区别。在Vuex中, s t o r e 被直接注入到了组件实例中,因此可以比较灵活的使用:使用 d i s p a t c h 、 c o m m i t 提交更新,通过 m a p S t a t e 或者直接通过 t h i s . store被直接注入到了组件实例中,因此可以比较灵活的使用:使用dispatch、commit提交更新,通过mapState或者直接通过this. store被直接注入到了组件实例中,因此可以比较灵活的使用:使用dispatchcommit提交更新,通过mapState或者直接通过this.store来读取数据。在Redux中,我们每一个组件都需要显示的用connect把需要的props和dispatch连接起来。另外,Vuex更加灵活一些,组件中既可以dispatch action,也可以commit updates,而Redux中只能进行dispatch,不能直接调用reducer进行修改。

从实现原理上来说,最大的区别是两点:Redux使用的是不可变数据,而Vuex的数据是可变的,因此,Redux每次都是用新state替换旧state,而Vuex是直接修改。Redux在检测数据变化的时候,是通过diff的方式比较差异的,而Vuex其实和Vue的原理一样,是通过getter/setter来比较的,这两点的区别,也是因为React和Vue的设计理念不同。React更偏向于构建稳定大型的应用,非常的科班化。相比之下,Vue更偏向于简单迅速的解决问题,更灵活,不那么严格遵循条条框框。因此也会给人一种大型项目用React,小型项目用Vue的感觉。

五、其他汇总

CSS实现0.5px的线:

要实现小于1px的线条,有个先决条件:屏幕的分辨率要足够高,设备像素比要大于1,即css中的1个像素对应物理屏幕中1个以上的像素点。

伪元素缩放或渐变:

:before为上边框,:after为下边框

<div class="fineLine"></div>
<!-- fineLine的:before为上边框,:after为下边框 -->
<style type="text/css">
    .fineLine {
        position: relative;
    }
    .fineLine:before,.fineLine:after{
      position: absolute;
      content: " ";
      height: 1px;
      width: 100%;
      left: 0;
      transform-origin: 0 0;
      -webkit-transform-origin: 0 0;
    }
    .fineLine:before {
        top: 0;
        background: #000;
    }
    .fineLine:after {
        bottom: 0;
        border-bottom: 1px solid #000;
    }
    @media only screen and (-webkit-min-device-pixel-ratio: 1.5) {
        .fineLine:after,.fineLine:before {
            -webkit-transform: scaleY(.667);
        }
    }
    @media only screen and (-webkit-min-device-pixel-ratio: 2) {
        .fineLine:after,.fineLine:before {
            -webkit-transform: scaleY(.5);
        }
    }
</style>
box-shadow模拟:

box-shaodw适合模拟box四周都需要细线border的情况,而且支持border-radius。

<div class="fineLine"></div>
<style type="text/css">
.fineLine {
    box-shadow: 0 0 0 1px;
}
@media (min-resolution: 2dppx) {
    .fineLine {
        box-shadow: 0 0 0 0.5px;
    }
}
@media (min-resolution: 3dppx) {
    .fineLine {
        box-shadow: 0 0 0 0.33333333px;
    }
}
</style>
svg画线、border-image裁剪等

CSS中position的4种定位详解:(https://www.cnblogs.com/zhaodahai/p/6823721.html)

css中的position有4种取值,分别是static、fixed、relative、absolute。
详细解释:
static:相当于没有定位,元素会出现在正常的文档流中。
fixed:元素框的表现类似于absolute,但是fixed是相对于视窗本身,也就是浏览器窗口而定位的。所以,采用该定位的元素在页面下
拉的时候,其位置并不会发生变化。
relative:生成相对定位的元素,相对于元素本身的位置进行定位,它原本所占的空间仍然会保留。
absolute:生成绝对定位的元素,相对于static定位以外的第一个有定位的祖先元素进行定位。由于static定位相当于没有定位,所以
absolute定位实际上就是相对于有定位的第一个祖先元素定位,如果所有的祖先元素都没有定位,则相对于初始包含块或者
画布,一般就是body元素定位。

css点击穿透上层元素,实现点击下层元素的效果;

上层元素添加如下CSS样式即可;

pointer-events: none;

js数组遍历API汇总:

for循环、for… of循环、forEach、map、filter、some、every、reduce、reduceRight、find、findIndex、keys()、values()、entries()…

说明:
for of遍历:可以正确响应break、continue和return语句;
reduce():将数组中的元素通过回调函数最终转换为一个值;

/* 接收一个函数作为累加器(accumulator),函数有四个参数分别是:上一次的值,当前值,当前值的索引,数组
	 数组中的每个值(从左到右)开始缩减,最终为一个值。*/
[0, 1, 2, 3, 4].reduce(function(previousValue, currentValue, index, array) {
 		return previousValue + currentValue;
}); // => 10
/* reduce还有第二个参数,我们可以把这个参数作为第一次调用callback时的第一个值,上面这个例子因为没有第二个参数,所以直接从数组的第二项开始,如果我们给了第二个参数为5,那么结果就是这样的:*/
[0, 1, 2, 3, 4].reduce(function(previousValue, currentValue, index, array){
 		return previousValue + currentValue;
},5); // => 15
// 第一次调用的previousValue的值就用传入的第二个参数代替

reduceRight():功能和reduce()功能是一样的,不同的是reduceRight()从数组的末尾向前将数组中的数组项做累加。
map

S判断是否是数组的四种做法

1. 通过instanceof判断

2. 通过constructor判断

3. 通过Object.prototype.toString.call()判断

4. 通过Array.isArray()判断

// 1.通过instanceof判断: 需要注意的是,prototype属性是可以修改的,所以并不是最初判断为true就一定永远为真。
let a = [];
a instanceof Array; //true
let b = {};
b instanceof Array; //false


// 2.通过constructor判断: 同样,这种判断也会存在多个全局环境的问题,导致的问题与instanceof相同。
let a = [1,3,4];
a.constructor === Array;//true

// 3.通过Object.prototype.toString.call()判断: 它强大的地方在于不仅仅可以检验是否为数组,比如是否是一个函数,是否是数字等等
let a = [1,2,3]
Object.prototype.toString.call(a) === '[object Array]';//true
//检验是否是函数
let a = function () {};
Object.prototype.toString.call(a) === '[object Function]';//true
//检验是否是数字
let b = 1;
Object.prototype.toString.call(a) === '[object Number]';//true

// 4.通过Array.isArray()判断: 用于确定传递的值是否是一个数组,返回一个布尔值。
let a = [1,2,3]
Array.isArray(a);//true
// Array.isArray() 是在ES5中提出,也就是说在ES5之前可能会存在不支持此方法的情况
// 解决:
if (!Array.isArray) {
  Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
  };
}

对象合并的方法:Object.assign 、拓展运算符、遍历赋值合并等

判断一个对象是否为空:

  • 最常见的思路,for...in... 遍历属性,为真则为“非空数组”;否则为“空数组”
for (var i in obj) {
  	// 如果不为空,则会执行到这一步,返回true
    return true
}
return false // 如果为空,返回false
  • 通过 JSON 自带的 stringify() 方法来判断:JSON.stringify() 方法用于将 JavaScript 值转换为 JSON 字符串。
if (JSON.stringify(data) === '{}') {
    return false // 如果为空,返回false
}
return true // 如果不为空,则会执行到这一步,返回true
  • ES6 新增的方法 Object.keys()Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组。

    如果我们的对象为空,他会返回一个空数组,我们通过判断它的长度来知道它是否为空:

    if (Object.keys(object).length === 0) {
        return false // 如果为空,返回false
    }
    return true // 如果不为空,则会执行到这一步,返回true
    
  • Object.getOwnPropertyNames()方法
    此方法是使用Object对象的getOwnPropertyNames方法,获取到对象中的属性名,存到一个数组中,返回数组对象,我们可以通过判断数组的length来判断此对象是否为空

1、超出JavaScript安全整数限制的数字计算问题?

JavaScript中的基本数据类Number是双精度浮点数,它可以表示的最大安全范围是正负9007199254740991,也就是2的53次方减一,在浏览器控制台分别输入Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER可查看对应的最大/小值

const max = Number.MAX_SAFE_INTEGER;
// → 9_007_199_254_740_991
// 注意:为了便于阅读,我使用下划线作为分隔符将这些数字分组为千位数。数字文字分隔符提案对普通的JavaScript数字文字使用正确。

// 将这个最大值加一,可以得到预期的结果:
max + 1;
// → 9_007_199_254_740_992 ✅
// 但是,如果我们再次增加它,结果不再可以完全表示为JavaScript Number:
max + 2;
// → 9_007_199_254_740_992 ❌

我们会发现max+1和max+2的结果一样。只要我们在JavaScript中获得这个特定的值,就无法判断它是否准确。对安全整数范围以外的整数(即从Number.MIN_SAFE_INTEGERNumber.MAX_SAFE_INTEGER)的任何计算可能会失去精度。出于这个原因,我们只能依靠安全范围内的数字整数值。

**BigInt**是JavaScript中的一个新的原始类型,可以用任意精度表示整数。使用BigInt,即使超出JavaScript Number 的安全整数限制,也可以安全地存储和操作大整数(chrome 67+开始支持BigInt)。

要创建一个BigInt,在数字后面添加n后缀即可,例如,123变成123n。全局BigInt(number)函数可以用来将Number转换成BigInt。换句话说,BigInt(123) === 123n。让我们用这两种技术来解决我们之前遇到的问题:

BigInt(Number.MAX_SAFE_INTEGER) + 2n;
// → 9_007_199_254_740_993n ✅

// 我们将两个`Number` 相乘:
1234567890123456789 * 123;
// → 151851850485185200000 ❌
// 查看上面两个数字,末尾分别是9和3,9*3=27,然而结果末尾却是000,明显是错误的,让我们用`BigInt`代替:
1234567890123456789n * 123n;
// → 151851850485185185047n ✅
// 这次我们得到了正确的结果。

Number 的安全整数限制不适用于BigInt。因此,BigInt我们可以执行正确的整数运算而不必担心失去精度。
BigInt是JavaScript语言中的一个原始类型。因此,可以使用typeof操作符检测到这种类型:

typeof 123;
// → 'number'
typeof 123n;
// → 'bigint'

2、为什么 0.1 + 0.2 = 0.30000000000000004?

这是数学运算中的浮点运算。
首先,对于不同的进制数值系统,只有当前进制数的质因子作为分母时,才能被除干净
以十进制为例,它的质因子为2、5,因此1/2、1/4、1/5、1/8和 1/10都能被除干净,相反,1/3、1/6 和 1/结果都是循环小数。
那么二进制时,质因子为2,所以1/2、1/4 和 1/8都可以被除净可以被精确表示,1/5 或者 1/10就变成了循环小数,那么0.2与0.1成了循环小数。
而计算机天生只能存储整数,它只能用某种方法来表示小数,例如使用浮点数,可见:https://www.cnblogs.com/yanze/p/10112673.html ,用这种方式,碰到除不净的情况,只能对数据进行截断处理,精度丧失。
JS中采用的IEEE 754的双精度标准,计算机内部存储数据的编码的时候,0.1在计算机内部根本就不是精确的0.1,而是一个有舍入误差的0.1。当代码被编译或解释后,0.1已经被四舍五入成一个与之很接近的计算机内部数字,以至于计算还没开始,一个很小的舍入错误就已经产生了

因此0.1+0.2!=0.3。

备注:可以认为:除不净即无法被精确表达。

3、如何将一个数字比如100000000展示为100,000,000?

1.正则替换:
var str = "100000000000";

var reg = /(?=(\B)(\d{3})+$)/g;  或者	var reg = /\B(?=(?:\d{3})+$)/g

console.log(str.replace(reg, ","));

补充几个概念: 单词边界非捕获分组(non-capturing group)

\B 匹配非单词边界, 即两边都是单词 \w = [0-9a-zA-Z] , 可是非单词边界太泛了, 我们需要一些限定词。

?= 就是一个限定词, 限定后面的字符串都是符合 (?:\d{3})+$ 这个规则。

?: 这是一个非捕获分组, 当匹配到 一个或多个 紧靠末尾 的三位数时, 这个规则生效, 但并不保存这个分组。)

2.用js拆分添加,生成

4、代码规范

一、整洁代码:代码正确 + 简洁明了 + 清晰易读 + 短小精确
二、命名:
1.准确:名字与意义匹配 + 易于区别
2.实用:使用读的出来的名称 + 使用可搜索的名称
3.明确:一个概念对应一个词 + 不用双关语 + 使用有意义的语境
三、函数
1.短小
2.职责单一
3.一块代码中,函数的抽象层级需一致
4.函数命名规范(参照二)
5.参数尽可能少
6.如果函数需要的参数要求数量有多种,应考虑将其封装成类
7.实用异常类代替返回错误码,抽离try/catch代码块,使代码更加简洁
四、注释
1.少用注释,尽可能通过规范的代码来表达
2.不使用无意义的注释
3.必要的注释:法律信息 | 提供信息的注释 | 对代码意图进行解释的注释 | 警示信息,防止踩坑 | TODO注释:未来得及完成的部分
4. 对于无用的代码应直接删除而不是注释
五、格式
1.为什么需要规范格式:易维护 + 易拓展
2.垂直格式:行数少、短小精悍
概念隔离:不同的的概念/逻辑,代码使用空行隔离
相关靠近:对于关系紧密的代码,尽量写在一起
3.水平格式:缩进、对齐

5、递归与尾递归!!!!!

1、递归

关于递归的概念,我们都不陌生。简单的来说递归就是一个函数直接或间接地调用自身,是为直接或间接递归。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。用递归需要注意以下两点:(1) 递归就是在过程或函数里调用自身。(2) 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。

递归一般用于解决三类问题:

(1)数据的定义是按递归定义的。(Fibonacci函数,n的阶乘)

(2)问题解法按递归实现。(回溯)

(3)数据的结构形式是按递归定义的。(二叉树的遍历,图的搜索)

递归的缺点:

递归解题相对常用的算法如普通循环等,运行效率较低。因此,应该尽量避免使用递归,除非没有更好的算法或者某种特定情况,递归更为适合的时候。在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储,因此递归次数过多容易造成栈溢出。

2、尾递归

顾名思义,尾递归就是从最后开始计算, 每递归一次就算出相应的结果, 也就是说, 函数调用出现在调用者函数的尾部, 因为是尾部, 所以根本没有必要去保存任何局部变量. 直接让被调用的函数返回时越过调用者, 返回到调用者的调用者去。尾递归就是把当前的运算结果(或路径)放在参数里传给下层函数,深层函数所面对的不是越来越简单的问题,而是越来越复杂的问题,因为参数里带有前面若干步的运算路径。

尾递归是极其重要的,不用尾递归,函数的堆栈耗用难以估量,需要保存很多中间函数的堆栈。比如f(n, sum) = f(n-1) + value(n) + sum; 会保存n个函数调用堆栈,而使用尾递归f(n, sum) = f(n-1, sum+value(n)); 这样则只保留后一个函数堆栈即可,之前的可优化删去。

6、怎么解决模块循环引用的问题!!!!!!

7、前端性能优化手段:

前端性能优化手段从以下几个方面入手:加载优化执行优化渲染优化样式优化脚本优化

  • 减少 HTTP 请求数
  • 减少 DNS 查询
  • 使用 CDN
  • 避免重定向
  • 图片懒加载
  • 减少 DOM 元素数量
  • 减少 DOM 操作
  • 使用外部 JavaScript 和 CSS
  • 压缩 JavaScript 、 CSS 、字体、图片等
  • 优化 CSS Sprite
  • 使用 iconfont
  • 字体裁剪
  • 多域名分发划分内容到不同域名
  • 尽量减少 iframe 使用
  • 避免图片 src 为空
  • 把样式表放在 中
  • 把外部js脚本放在页面底部
加载优化

​ 减少HTTP请求、缓存资源、压缩代码、无阻塞、首屏加载、按需加载、预加载、压缩图像、减少Cookie、避免重定向、异步加载第三方资源。

执行优化:

​ CSS写在头部,JS写在尾部并异步、避免img、iframe等的src为空、尽量避免重置图像大小、图像尽量避免使用DataURL。

渲染优化

​ 设置viewport、减少DOM节点、优化动画、优化高频事件、GPU加速。

样式优化

​ 避免在HTML中书写style、避免CSS表达式、移除CSS空规则、正确使用 display、不滥用float等。

脚本优化

​ 减少重绘和回流、缓存DOM选择与计算、缓存.length的值、尽量使用事件代理、尽量使用 id选择器、touch事件优化。

优化具体解析:
加载优化
  • 减少HTTP请求:尽量减少页面的请求数(首次加载同时请求数不能超过4个),移动设备浏览器同时 响应请求为4个请求( 支持4个, 支持6个)

    合并CSS和JS
    使用CSS精灵图
    
  • 缓存资源:使用缓存可减少向服务器的请求数,节省加载时间,所有静态资源都要在服务器端设置 缓存,并且尽量使用长缓存(使用时间戳更新缓存)

    缓存一切可缓存的资源
    使用长缓存
    使用外联的样式和脚本
    
  • 压缩代码:减少资源大小可加快网页显示速度,对代码进行压缩,并在服务器端设置GZip

    压缩代码(多余的缩进、空格和换行符)
    启用Gzip
    
  • 无阻塞:头部内联的样式和脚本会阻塞页面的渲染,样式放在头部并使用 方式引入,脚本放 在尾部并使用异步方式加载

  • 首屏加载:首屏快速显示可大大提升用户对页面速度的感知,应尽量针对首屏的快速显示做优化

  • 按需加载:将不影响首屏的资源和当前屏幕不用的资源放到用户需要时才加载,可大大提升显示速度和降低总体流量(按需加载会导致大量重绘,影响渲染性能)

    懒加载
    滚屏加载
    Media Query加载
    
  • 预加载:大型资源页面可使用 Loading ,资源加载完成后再显示页面,但加载时间过长,会造成用户流失

    可感知Loading:进入页面时 Loading
    不可感知Loading:提前加载下一页
    
  • 压缩图像:使用图像时选择最合适的格式和大小,然后使用工具压缩,同时在代码中用 srcset 来按需显示(过度压缩图像大小影响图像显示效果)

    使用TinyJpg和TinyPng压缩图像
    使用CSS3、SVG、IconFont代替图像
    使用img的srcset按需加载图像
    选择合适的图像: webp 优于 jpg, png8 优于 gif
    选择合适的大小:首次加载不大于 1014kb、不宽于 640px
    PS切图时D端图像保存质量为80,M端图像保存质量为60
    
  • 减少Cookie:Cookie 会影响加载速度,静态资源域名不使用 Cookie

  • 避免重定向:重定向会影响加载速度,在服务器正确设置避免重定向

  • 异步加载第三方资源:第三方资源不可控会影响页面的加载和显示,要异步加载第三方资源

执行优化
  • CSS写在头部,JS写在尾部并异步
  • 避免img、iframe等的src为空:空 src 会重新加载当前页面,影响速度和效率
  • 尽量避免重置图像大小:多次重置图像大小会引发图像的多次重绘,影响性能
  • 图像尽量避免使用DataURL:DataURL 图像没有使用图像的压缩算法,文件会变大,并且要解码后再渲染,加载慢耗时长
渲染优化
  • 设置viewport:HTML的 viewport 可加速页面的渲染

    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, minimum-scale=1, maximum-scale=1">
    
  • 减少DOM节点:DOM节点太多影响页面的渲染,尽量减少DOM节点

  • 优化动画

    尽量使用CSS3动画
    合理使用requestAnimationFrame动画代替setTimeout
    适当使用Canvas动画:5个元素以内使用CSS动画 ,5个元素以上使用 Canvas动画 ,iOS8+ 可使用 WebGL动画
    
  • 优化高频事件:scroll 、 touchmove 等事件可导致多次渲染

    函数节流
    函数防抖
    使用requestAnimationFrame监听帧变化:使得在正确的时间进行渲染
    增加响应变化的时间间隔:减少重绘次数
    
  • GPU加速:使用某些HTML5标签和CSS3属性会触发GPU渲染,请合理使用(过渡使用会引发手机耗电量增加)

    HTML标签: video 、 canvas 、 webgl
    CSS属性: opacity 、 transform 、 transition
    
样式优化
  • 避免在HTML中书写style

  • 避免CSS表达式:CSS表达式的执行需跳出CSS树的渲染

  • 移除CSS空规则:CSS空规则增加了css文件的大小,影响CSS树的执行

  • 正确使用display: display会影响页面的渲染

    display: inline 后不应该再使用 float 、 margin 、 padding 、 width 和 height
    display: inline-block 后不应该再使用 float
    display: block 后不应该再使用 vertical-align
    display: table-* 后不应该再使用 float 和 margin
    
  • 不滥用float:float 在渲染时计算量比较大,尽量减少使用

  • 不滥用Web字体:Web字体需要下载、解析、重绘当前页面,尽量减少使用

  • 不声明过多的font-size:过多的 font-size 影响CSS树的效率

  • 值为0时不需要任何单位:为了浏览器的兼容性和性能,值为 0 时不要带单位

  • 标准化各种浏览器前缀

    无前缀属性应放在最后
    CSS动画属性只用-webkit-、无前缀两种
    其它前缀为-webkit-、-moz-、-ms-、无前缀四种(Opera 改用 blink 内核, -o- 已淘汰)
    

避免让选择符看起来像正则表达式:高级选择符执行耗时长且不易读懂,避免使用

脚本优化
  • 减少重绘和回流

    避免不必要的DOM操作
    避免使用document.write
    减少drawImage
    尽量改变class而不是style,使用classList代替className
    
  • 缓存DOM选择与计算:每次DOM选择都要计算和缓存

  • 缓存.length的值:每次 .length 计算用一个变量保存值

  • 尽量使用事件代理:避免批量绑定事件

  • 尽量使用id选择器: id 选择器选择元素是最快的

  • touch事件优化:使用 tap ( touchstart 和 touchend )代替 click (注意响应过快,易引发误操作)

常用规则

8、如果重写这个项目能优化什么东西?

8.1、前端白屏问题

9、常见的 Web 安全问题,怎么防御

10、讲下如何负载均衡

11、Jenkins怎么使用的?

12、CSS相关:

1. display: none; 与 visibility: hidden; 的区别:
  • 联系:它们都能让元素不可见

  • 区别:

    • display:none;会让元素完全从渲染树中消失,渲染的时候不占据任何空间;visibility: hidden;不会让元素从渲染树消失,渲染师元素继续占据空间,只是内容不可见
    • display: none;是非继承属性,子孙节点消失由于元素从渲染树消失造成,通过修改子孙节点属性无法显示;visibility:hidden;是继承属性,子孙节点消失由于继承了hidden,通过设置visibility: visible;可以让子孙节点显式
    • 修改常规流中元素的display通常会造成文档重排。修改visibility属性只会造成本元素的重绘
    • 读屏器不会读取display: none;元素内容;会读取visibility: hidden元素内容
2. CSS有哪些属性可以继承?哪些属性不能继承?
  • 可继承的样式: font-size, font-family, color, cursor,line-height, visibility,font(word-break、letter-spacing、text-align、text-rendering、word-spacing、white-space、text-indent、text-transform、text-shadow)
  • 不可继承的样式:border padding margin width height background
3. CSS选择符有哪些?
    • id选择器( # myid)
    • 类选择器(.myclassname)
    • 标签选择器(div, h1, p)
    • 相邻选择器(h1 + p)
    • 子选择器(ul > li)
    • 后代选择器(li a)
    • 通配符选择器( * )
    • 属性选择器(a[rel = “external”])
    • 伪类选择器(a:hover, li:nth-child)
4. CSS优先级算法如何计算?
  • 相同权重,定义最近者为准:行内样式 > 内部样式 > 外部样式
  • 含外部载入样式时,后载入样式覆盖其前面的载入的样式和内部样式
  • 选择器优先级: 行内样式[1000] > id[100] > class[10] > Tag[1]
  • 在同一组属性设置中,!important 优先级最高,高于行内样式
5. CSS3新增伪类有那些?
:root 					选择文档的根元素,等同于 html 元素
:empty 					选择没有子元素的元素
:target 				选取当前活动的目标元素
:not(selector) 	选择除 selector 元素意外的元素
:enabled 				选择可用的表单元素
:disabled 			选择禁用的表单元素
:checked 				选择被选中的表单元素
:after 					在元素内部最前添加内容
:before 				在元素内部最后添加内容
:nth-child(n) 	匹配父元素下指定子元素,在所有子元素中排序第n
:nth-last-child(n) 匹配父元素下指定子元素,在所有子元素中排序第n,从后向前数
:nth-child(odd)
:nth-child(even)
:nth-child(3n+1)
:first-child
:last-child
:only-child
:nth-of-type(n) 	匹配父元素下指定子元素,在同类子元素中排序第n
:nth-last-of-type(n) 匹配父元素下指定子元素,在同类子元素中排序第n,从后向前数
:nth-of-type(odd)
:nth-of-type(even)
:nth-of-type(3n+1)
:first-of-type
:last-of-type
:only-of-type
::selection 			选择被用户选取的元素部分
:first-line 			选择元素中的第一行
:first-letter 		选择元素中的第一个字符
6. CSS3有哪些新特性?
  • 新增各种CSS选择器 (: not(.input):所有 class 不是“input”的节点)
  • 圆角 (border-radius:8px)
  • 多列布局 (multi-column layout)
  • 阴影和反射 (Shadow\Reflect)
  • 文字特效 (text-shadow、)
  • 文字渲染 (Text-decoration)
  • 线性渐变 (gradient)
  • 增加了旋转,缩放,定位,倾斜,动画,多背景:transform:\scale(0.85,0.90)\ translate(0px,-30px)\ skew(-9deg,0deg)\Animation:
7. 请列举几种隐藏元素的方法
  • visibility: hidden; 这个属性只是简单的隐藏某个元素,但是元素占用的空间任然存在
  • opacity: 0; CSS3属性,设置0可以使一个元素完全透明
  • position: absolute; 设置一个很大的 left 负值定位,使元素定位在可见区域之外
  • display: none; 元素会变得不可见,并且不会再占用文档的空间。
  • transform: scale(0); 将一个元素设置为缩放无限小,元素将不可见,元素原来所在的位置将被保留
  • <div hidden="hidden"> HTML5属性,效果和display:none;相同,但这个属性用于记录一个元素的状态
  • height: 0; 将元素高度设为 0 ,并消除边框
  • filter: blur(0); CSS3属性,将一个元素的模糊度设置为0,从而使这个元素“消失”在页面中
8. CSS优化、提高性能的方法有哪些?
  • 多个css合并,尽量减少HTTP请求
  • 将css文件放在页面最上面
  • 移除空的css规则
  • 避免使用CSS表达式
  • 选择器优化嵌套,尽量避免层级过深
  • 充分利用css继承属性,减少代码量
  • 抽象提取公共样式,减少代码量
  • 属性值为0时,不加单位
  • 属性值为小于1的小数时,省略小数点前面的0
  • css雪碧图
9. a标签上四个伪类的执行顺序是怎么样的?

link > visited > hover > active (L-V-H-A love hate 用喜欢和讨厌两个词来方便记忆)

13、经常遇到的浏览器的JS兼容性有哪些?解决方法是什么?

  • 当前样式:getComputedStyle(el, null) VS el.currentStyle
  • 事件对象:e VS window.event
  • 鼠标坐标:e.pageX, e.pageY VS window.event.x, window.event.y
  • 按键码:e.which VS event.keyCode
  • 文本节点:el.textContent VS el.innerText

六、Webpack打包相关!!!

1、Loader:

它是一个打包方案,webpack只知道如何打包js文件,不知道如何打包非js文件,Loader可以告诉webpack如何打包这些文件。

• 常用loader:
vue-loader(打包.vue文件)
file-loader(打包图片、.txt等,打包图片时单独生成一个图片文件)
url-loader:
• 使用loader打包静态资源
图片
字体
样式

2、plugins

使打包更加便捷。
plugin可以在webpack运行到某一时刻时帮你做一些事情,比如html-webpack-plugin会在打包结束这一时刻自动生成html文件,并把打包生成的js自动引入到html文件中。

• 常用插件:
html-webpack-plugin
clean-webpack-plugin
hot-module-replacement-plugin:热更新

3、Entry 与 Output

4、SourceMap

5、WebpackDevServer

• webpack – watch:可以监听变动并自动打包;它不能启服务器,每次打包后需要手动刷新浏览器。
• webpack-dev-server:

6、HotModuleReplacement(HMR)

7、Babel

把ES6代码转换成浏览器可以识别的ES5代码

webpack如何将.jsx,.vue转化成js等基本形式的

自己写过 webpack 的插件吗

懒加载原理、实现?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: C/C++ 是应用广泛的编程语言,其在数据结构应用方面也十分重要。面试中相关的 C/C++ 数据结构问题主要围绕数组、链表、二叉树和图等方面。以下是一些常见问题及其解答: 1. 如何反转一个单向链表? 答:可以使用三个指针来实现:cur 代表当前节点,pre 代表上一个节点,next 代表下一个节点。每次遍历时,将 cur 的 next 指向 pre,然后将三个指针分别向后移动即可。 2. 如何判断两个链表是否相交,并找出相交的点? 答:可以分别遍历两个链表,得到各自的长度。然后让长的链表先走 n 步,使得两个链表剩余的长度相等。接下来同时遍历两个链表,比较节点是否相同即可找出相交的点。 3. 如何判断一个二叉树是否为平衡二叉树? 答:可以计算每个节点的左右子树深度差,如果任何一个节点的深度差大于1,则此树不是平衡二叉树。可以使用递归实现,每次计算当前节点的深度,然后递归判断其左右子树是否平衡。 4. 如何实现图的深度优先搜索(DFS)和广度优先搜索(BFS)算法? 答:DFS 可以使用递归实现。从某个节点开始,逐个访问其未被访问的邻接节点,并将其标记为已访问。然后对每个未被访问的邻接节点递归调用 DFS 函数。BFS 可以使用队列实现。从某个节点开始,将其加入队列,并标记为已访问。然后从队列中弹出节点,并访问其所有未被访问的邻接节点,并将其加入队列中。重复此过程直到队列为空。 以上是一些常见的 C/C++ 数据结构面试问题及其解答。在面试中,除了掌握相关算法和数据结构知识外,还需多做练习和积累经验,才能更好地应对各种面试问题。 ### 回答2: C语言是一种用于编写系统级程序的高级编程语言,具有简单、高效、灵活等特点,是许多操作系统、编译器等软件的首选语言,也是许多企业在进行面试时重点考察的技能。在C/C++数据结构面试题中,经常会涉及到各种数据结构相关的算法和应用,测试面试者的算法思维能力和实现能力。 其中,常见的数据结构包括链表、栈和队列、二叉树、搜索树、哈希表等。在面试时,会常常涉及代码设计和实现,比如实现链表的插入、删除、查找操作,实现二叉树的遍历、查找操作等。 此外,在数据结构面试中,还经常涉及排序和查找算法,如冒泡排序、快速排序、归并排序、二分查找、哈希查找等。同时,面试者还需要解决一些较为复杂的算法问题,如图的最短路径问题,最小生成树问题等。 总之,C/C++数据结构面试题涵盖了运用数据结构的各种算法和实现方法,需要面试者具备扎实的编程基础和算法思维能力。在备战面试时,可以多做练习,熟悉常用的数据结构和算法,提高理解和实现能力,从而更好地应对面试挑战。 ### 回答3: 面试过程中常见的C/C++数据结构面试题有很多。以下就介绍几个常见的题目并给出解答。 1. 求两个有序数组的中位数 题目描述:给定两个升序排列的整形数组,长度分别为m和n。实现一个函数,找出它们合并后的中位数。时间复杂度为log(m+n)。 解答:这个问题可以使用二分法求解。首先,我们可以在两个数组中分别选出所谓的中间位置,即(i+j)/2和(k+l+1)/2,其中i和j分别是数组A的起始和结束位置,k和l分别是数组B的起始和结束位置。判断A[i+(j-i)/2]和B[k+(l-k)/2]的大小,如果A的中间元素小于B的中间元素,则中位数必定出现在A的右半部分以及B的左半部分;反之,则必定出现在A的左半部分以及B的右半部分。以此类推,每一次都可以删去A或B的一半,从而达到对数级别的时间复杂度。 2. 堆排序 题目描述:对一个长度为n的数组进行排序,时间复杂度为O(nlogn)。 解答:堆排序是一种常用的排序算法,在面试中也经常被考察。堆排序的具体过程是首先将数组构建成一个最大堆或最小堆,然后不断将堆顶元素与最后一个元素交换,并将最后一个元素从堆中剔除。这样,每次剔除后,堆都会重新调整,使得剩下的元素仍然保持堆的性质,直到堆中只剩下一个元素为止。 3. 链表反转 题目描述:反转一个单向链表,例如给定一个链表: 1->2->3->4->5, 反转后的链表为: 5->4->3->2->1。 解答:链表反转题目也是非常常见,其思路也比较简单。遍历链表,将当前节点的next指针指向前一个节点,同时记录当前节点和前一个节点,直至遍历到链表末尾。 以上这三个问题分别从二分法、堆排序和链表三个方面介绍了常见的C/C++数据结构面试题,希望能帮助面试者更好地准备面试。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值