面试题/经验积累

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 [email protected] -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张图片

面试题/经验积累_第2张图片

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

  • ::after /
    / 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
面试题/经验积累_第3张图片

标准盒模型:盒模型的宽高只是内容(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视口
.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)到屏幕上,展示整个页面。
    (不同浏览器内核不同,所以渲染过程不太一样)

    面试题/经验积累_第4张图片

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">hhbutton>
    <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 请求发送后,结果被浏览器拦截
但是有三个标签是允许跨域加载资源:
  • ② 使用watch监测值的变化,v-if以及this.$nextTick配合使用

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

    2.Vue 插槽:

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

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

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

    插槽的作用:让用户可以拓展组件,更好地复用组件和对其做定制化处理。
    插槽的分类:
    1.默认插槽
    2.具名插槽
    // 元素有一个特殊的 attribute:name, 这个 attribute 可以用来定义额外的插槽
    // 一个不带name的出口会带有隐含的名字"default"		<=  默认插槽
    // 父组件在向具名插槽提供内容的时候,我们可以在一个