PS. 这些很多知识点其实都可以单拎出来讲一个篇章, 后面我会详细做下总结。

let的一个知识点

只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。在代码块中,使用let命令声明变量之前,该变量都是不可用的,这在语法上称为“暂时性死亡”

1
2
3
4
5
6
7
8
9
10
11
12
13
// 因为setTimeout是异步函数,所以会延后执行。循环会先走完,所以i是10
// var中的i其实是全局的,所以console.log打印的是最后变成10的全局变量
for (var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i)
}, 1000)
}
// A方法中的i,每一次循环都是let下的一个块作用域变量,能够记住i并保存到内存中
for (let i = 0; i < 10; i++) {
setTimeout(function A() {
console.log(i);
}, 1000)
}

Object.keys()和Reflect.ownKeys()的区别

Object.keys() 返回自己的可枚举属性

Reflect.ownKeys() 返回所有的自己属性,不管可枚举还是不可枚举,所以可以遍历出方法属性

实现一个浅拷贝

1
2
3
4
5
6
7
8
9
10
// 浅拷贝
function shallowCopy(obj) {
var copyObj = {}
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
copyObj[key] = obj[key];
}
}
return copyObj;
}

实现一个简易深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 判断是否为对象,保留是数组的情况。typeof [] === 'object'
function isObject(obj) {
return typeof obj === 'object' && object != null;
}
// 简易深拷贝
function deepCopy(obj) {
// 如果非对象则返回该值
if (!isObject(obj)) return obj;
var copyObj = Array.isArray(obj) ? [] : {};
for (var key in obj) {
if (isObject(obj[key]) {
// 递归
copyObj[key] = deepCopy(obj[key]);
} else {
copyObj[key] = obj[key];
}
}
return copyObj;
}

lodash常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 过滤数组
_.uniq([1, 1, 2, 3]); // [1, 2, 3]
_.uniqBy([{ age: 10 }, { age: 11 }, { age: 10 }], 'age');
// => [{ age: 10 }, { age: 11 }]
_.uniqBy([{ name: 'joe', age: '10'}, { name: 'tome', age: '11'}, { name: 'joe', age: '10'}], _.isEqual);
// => [{ name: 'joe', age: '10'}, { name: 'tome', age: '11'}]

// 获取随机数
_.random(0, 10)

// 深度比较,非内存地址比较
_.isEqual({ a:1 }, { a:1 }); // true

_.isNil() // null or undefined

_.cloneDeep() // 深拷贝
_.clone() // 浅拷贝

_.find() // 找到数组中符合条件的对象

_.flatten() // 数组扁平化,降维一层
_.flattenDeep() // 数组扁平化至一维

数组去重

var arr = [1,2,3,3,3,3,4,5,5];
var newArr = [...new Set(arr)];

对象数组去重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

var source = [
{ name: 'Tom' },
{ name: 'Jack' },
{ name: 'Tom' },
{ name: 'Sun' },
{ name: 'Jack' },
{ name: 'Tom' }
];
var hash = {};
var target = source.filter((item) => {
if (!hash[item.name]) {
hash[item.name] = item.name;
return item;
}
});
console.log(hash);
// {Tom: "Tom", Jack: "Jack", Sun: "Sun"}
console.log(target);
// [{ name: 'Tom' }, { name: 'Jack' }, { name: 'Sun' }]

简易数组扁平化

1
2
3
4
5
6
7
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
// 现在这种方式默认是将N维数组都扁平化,无法指定降几维,还需优化

小trick

1
2
3
// a b 两变量互换
let a = 1, b = 2;
[a, b] = [b, a];

实现add(1)(2)(3)…

1
2
3
4
5
6
7
8
9
10
11
12
function add(x) {
var sum = x;
var tem = function(y) {
sum = sum + y;
return tem; //返回自身
}
tem.toString = function() { //改写toString
return sum;
}
return tem;
}
// add(1)(2)(3) 6

js节流与防抖

防抖:规定只在用户最后一次点击后开始计时执行事件,只要用户再次点击就从这次点击开始计时。适合防止用户暴力点击等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 防抖
function debounce(fn, delay) {
var timer = null;
// 利用闭包
return function() {
// 说明在delay时间内,内存中已经有timer在执行了
if (timer !== null) {
// 清除定时器
clearTimeout(timer);
}
// 清除后,又重新开始计时
timer = setTimeout(fn, delay);
}
}
function handle() {
console.log('I am 防抖函数处理的事件');
}
window.addEventListner('click', debounce(handle, 1000))

节流:规定在一定时间内,只执行一次事件,适合滚动加载数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 节流 通过时间戳实现
function throtten(fn, delay) {
var prev = Date.now();
return function() {
var that = this;
var args = arguments;
var now = Date.now();
// 如果到了指定时间
if (now - prev >= delay) {
fn.call(that, args);
// 利用闭包特性,重新对prev赋值
prev = Date.now();
}

}
}
window.addEventListener('scroll', throtten(handle, 1000));

一道面试题

1
2
3
4
5
6
7
var a = { n: 1 };
var b = a;
a.x = a = { n: 2};
// 重点在于. 的运算优先级大于赋值运算的优先级。
// 所以会先执行 `a.x`,相当于给a赋值一个属性x, a.x = undefined
// 此时 a = { x: undefined, { n:1 }}, b = { x: undefined, { n:1 }}
// 最后 a = { n: 2 }, b = { x: { n: 2 }, { n: 1} }

0.1 + 0.2 === 0.3

解决方法可以设置一个误差范围值,通常称为“机器精度”(machine epsilon)

从es6开始,可以用Number.EPSILON去解决
es6之前版本写polyfill:

1
2
3
4
5
6
7
8
9
10
11
if (!Number.EPSILON) {
Number.EPSILON = Math.pow(2,-52);
}
function isNumberEqual(num1, num2) {
// Math.abs()返回一个绝对值
return Math.abs(num2 - num1) < Number.EPSILON;
}
var a = 0.1 + 0.2;
var b = 0.3;
isNumberEqual(a, b); // true
a === b; // false

一般解决float算数问题可以用,(0.1 * 100 + 0.2 * 100)/100 === 0.3 解决

React生命周期

start

contructor

static getDerivedStateFromProps

componentWillMount

render

componentDidMount

runtime

componentWillUnmount

事件冒泡、捕获、委托

事件捕获&冒泡:DOM事件流-Document到触发事件的那个节点一层层向下捕获直到事件源,然后再向上冒泡回到document。

Tip:IE提出了事件冒泡,Netscape提出了事件捕获。

事件委托:利用事件冒泡原理,把本应该添加到某个元素上的事件委托给他的父级,从而减少DOM交互。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="father">
<h5 id="child1">hello</h5>
<h5 id="child2">world</h5>
<h5>lucky</h5>
</div>
<script type="text/javascript">
// 事件委托
var father = document.getElementById('father').
father.addEventListener('click', function(e){
var e = e || window.event;
if (e.target.nodeName.toLowerCase() == 'h5') {
alert(e.target.innerHTML);
}
});

// AddEventListener的第三个参数,默认为false(冒泡流)
// 如果更改成true,就定义事件为捕获流。
</script>

redux-saga

  • 集中处理了所有异步操作,异步接口一幕了解
  • action是普通对象,这跟redux同步的action一模一样
  • 通过effect, 方便异步接口测试
  • 通过worker和watcher可以实现非阻塞异步调用,并且同时可以实现非阻塞调用下的事件监听
  • 异步操作流程是可以控制的,可以随时取消异步操作

优化网页性能

gzip

减小服务器返回文件的大小,让浏览器本地解压缩 再渲染html, 但需注意下cpu损耗的问题

  • 浏览器需发送一个请求头,request header里设置Accept-Encoding: gzip
  • 如果服务器压缩了文件,会返回Content-Encoding: gzip

适当引入图片转base64

减少了一次Http请求数,但是Css的体积也会随之增大一些,适用于极小或者极简单(背景图)。可以当成本地图片,重复使用不会重复请求。没有跨域问题,无需考虑缓存。可以被gzip。

js文件位置

放在</body>前, 防止浏览器下载并执行js文件,阻塞了html和css的渲染。或者在<script>中加入defer参数,延迟下载该js文件直到html渲染完成。加入async参数可以让浏览器异步下载js,不影响html的渲染

减少请求数

  • js/css压缩合并
  • 雪碧图
  • 图片懒加载
  • 使用Svg图

CDN

选择优秀的CDN服务,可以适当提高网页速度

设置http header

request header中设置 Cache-Control: max-age=123456000

尽量使用ajax get

在进行ajax请求时,尽量选择get方法,这样可以使用客户端缓存,提高请求速度

前端跨域解决方案

1. jsonp

https://www.kuayu.com/admin?name=joe&callback=myCall 只适合简单跨域解决,只能用get请求方式,并且需要后端配合返回callback()

// 服务端需给回调函数返参
function myCall(res) {
    // do some render function 
    console.log(res);
}

2. iframe相关

3. nginx代理跨域

跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。

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

nginx具体配置:

#proxy服务器
server {
    listen       81;
    server_name  www.domain1.com;

    location / {
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
        index  index.html index.htm;

        # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为*
        add_header Access-Control-Allow-Credentials true;
    }
}

前端代码示例:

var xhr = new XMLHttpRequest();

// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;

// 访问nginx中的代理服务器
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);
xhr.send();

4. Nodejs中间件代理

node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。

5. WebSocket协议跨域

WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。 原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。

6. 跨域资源共享(CORS)

普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。

7. postMessage跨域

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

总结

简单的跨域请求jsonp即可,复杂的cors,窗口之间JS跨域postMessage,开发环境下接口跨域用nginx反向代理或node中间件比较方便

http缓存

缓存的开关:cache-control, pragma =>{ pragma, Expires } (过时)

缓存校验:Last-Modified, Expires: GMT时间, etag (根据类似md5算法对服务器中的资源进行计算出值,然后作对比)

#CSS

BFC

BFC: Block Fromatting Context 块级格式上下文

触发方式

  1. float的值不是none。
  2. position的值不是static或者relative。
  3. display的值是inline-block、table-cell、flex、table-caption或者inline-flex
  4. overflow的值不是visible

三栏布局

  1. flex布局,左右设固定宽度,中间设置flex-grow: 1 (flex-shrink: 0)
  2. 利用float: left, 做圣杯布局&双飞燕布局。后者比前者多一个父div,但少了很多css代码
  3. 绝对定位
  4. grid
  5. table
  6. float + margin (left, right, main)不推荐,不利于seo