随手记

197 阅读18分钟

html/css相关

1.h5新特性

  • 语义化标签
  • 增强型表单
  • 新事件(onrize,onScroll,ondrag,onerror,onplay,onpause)

2.css3

  • 伪类添加效果
  • 伪元素,创建了元素
  • background border
  • 文本效果
  • transform transition animation

3.移动端适配

将视图的宽度设为页面的宽度

rem

将视图按照一定的比例划分。动态设置根元素font-size

// rem-demo/util/rem.js
// 设置基准大小
const baseSize = 32
function setRem () {
  // 当前页面宽度相对于 750 宽的缩放比例
  const scale = document.documentElement.clientWidth / 750
  document.documentElement.style.fontSize = (baseSize * Math.min(scale, 2)) + 'px'
}
// 初始化
setRem()
window.onresize = function () {
  setRem()
}

vw/vh

将页面分成100份,1vw = device-width/100,缺点无法修改,无法通过js干预。

4. 重绘、重排

重绘

元素外观改变重新绘制

  • 改变元素的颜色、背景、阴影等

重排(回流)

浏览器重新计算元素的几何属性

  • 添加或删除可见的dom元素
  • 元素尺寸改变
    优化建议:样式集中操作、对脱离文档流的元素进行修改,使用transform属性
    现代浏览器会对频繁的回流或重绘操作进行优化:浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。

5. BFC

里面的元素不影响外面的元素 创建:display:inline-block position:absolut 应用:margin重叠,清除内部浮动

js相关

1. 事件冒泡、事件委托

事件冒泡

在一个对象上触发事件,如果对象绑定了事件就会触发,如果没有,就会像父级对象传播,最终由父级对象触发。

事件委托

因为事件在冒泡过程中会上传到父节点,父节点可以通过事件对象获取到目标节点,所以可以把子节点的监听函数定义在父节点上,由父节点统一处理子节点的事件,这种方式叫做事件代理。

2.路由模式

2.1 后端路由

接收到客户端的http请求,读取文件,返回数据。
优点:安全性,SEO好
缺点:加大了服务器的压力

2.2 前端路由

优点:访问新页面只是换了下路径,路径匹配到对应的组件,然后渲染,用户体验好 缺点:前进后退会重新发起请求,没有利用好缓存,不利于SEO

2.2.1 hash

利用location.hash来实现,#后面的值,利用hashchange来监听hash的变化。

2.2.2 history API

利用history.pushState,history.replaceState,监听popState事件

3.escape,encodeURI,encodeURIComponent区别

escape编码字符串 encodeURI方法不会对下列字符编码 ASCII字母 数字 ~!@#$&*()=:/,;?+'

encodeURIComponent方法不会对下列字符编码 ASCII字母 数字 ~!*()'

所以encodeURIComponent比encodeURI编码的范围更大。

4.XHR和fentch的区别

对比内容xhrfetch
设计模式事件机制,业务代码耦合在事件中promise的异步模式,更解耦
请求中止可实现不可实现
异常状态可差别处理也认为正常,需要追加自定义的回调函数处理
超时是否支持支持不支持
携带cookie携带不携带,需要主动添加
兼容性只有部分浏览器支持
是否支持跨域不支持,需要使用jsonp原生支持
稳定性试验阶段

5.Promise

用Promise.race解决接口超时间未回应。返回最快的结果。

-每一个异步任务都想得到结果就使用Promise.allSettled() -异步任务要求每个都成功才能往下执行就使用Promise.all()

6.event-loop

浏览器或node解决js单线程运行被阻塞的机制。

一些概念

  • 堆(Heap):一种数据结构,利用完全二叉树维护的一组数据。
  • 栈(Stack):一种数据结构,按照后进先出原则存储数据。
  • 队列(Queue):按照先进先出原则存储数据。

宏任务和微任务

  • MacroTask(宏任务):Script全部代码,setTimeout,setInterval,I/O,UI Rendering
  • MicroTask(微任务):Promise,Object.observe,MutationObserve,Process.NextTick(Node)

浏览器中的EventLoop

j完毕,栈被清空了,就代表主线程空闲了,这个时候就会去任务队列中按照顺序读取一个任务放到栈中执行(异步任务有了运行结果,就会在任务队列中放置一个事件)。每次栈被清空,都会去读取任务队列中有没有任务,有就读取执行,一直循环读取执行操作。当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件, 然后再去宏任务队列中取出一个事件。同一次事件循环中, 微任务永远在宏任务之前执行。

js中的异步任务

执行栈在执行完同步任务后,会检查执行栈是否为空,如果为空就会检查微任务队列是否为空,如果为空,就执行宏任务,否则就一次性执行完所有微任务。每次执行完耽搁宏任务,就会检查微任务队列是否为空,如果不为空的话,就按照先入先出的原则执行完全部微任务后设置微任务队列为null,在去执行宏任务,如此循环。

7.call apply

传参格式不同

let arr1 = [14,5,7,9]
Math.max.call(null,14,5,7,9) //14
Math.max.call(null,arr1) //NaN
Math.max.apply(null,arr1) //14

8.bind

返回改变上下文后的一个函数,可以绑定参数

9.this-指针,指向调用函数的对象

1. 默认绑定

默认规则通常是独立函数调用

function sayHi(){
    console.log('Hello,', this.name);
}
var name = 'YvetteLau';
sayHi();

调用sayHi(),非严格模式下this指向全局对象,打印Hello,YvetteLau。严格模式下,this指向undefined,会报错。

2. 隐式绑定

调用是在某个对象上触发的,调用位置上存在上下文关系。

function sayHi(){
    console.log('Hello,', this.name);
}
var person1 = {
    name: 'YvetteLau',
    sayHi: function(){
        setTimeout(function(){
            console.log('Hello,',this.name);
        })
    }
}
var person2 = {
    name: 'Christina',
    sayHi: sayHi
}
var name='Wiliam';
person1.sayHi();
setTimeout(person2.sayHi,100);
setTimeout(function(){
    person2.sayHi();
},200);

  • Hello,Wiliam:this是默认绑定,非严格模式下指向全局
  • Hello,Wiliam:this是隐式绑定,person2.sayHi赋值给了一个变量,绑定丢失,最后执行这个变量,this就和person2没有关系了
  • Hello,Christina:this是隐式绑定,指向person2

3. 显式绑定

通过call,apply,bind显式的指定this所指向的对象。

function sayHi(){
    console.log('Hello,', this.name);
}
var person = {
    name: 'YvetteLau',
    sayHi: sayHi
}
var name = 'Wiliam';
var Hi = person.sayHi;
Hi.call(person); //Hi.apply(person)

输出Hello,YvetteLau,将Hi绑定到了person。 但是用了硬绑定依然会绑定丢失

function sayHi(){
    console.log('Hello,', this.name);
}
var person = {
    name: 'YvetteLau',
    sayHi: sayHi
}
var name = 'Wiliam';
var Hi = function(fn) {
    fn();
}
Hi.call(person, person.sayHi); 

输出Hello,Wiliam。虽然Hi的this绑定到了person,但是person.sayHi赋值给fn,最后执行fn,隐式绑定丢失默认全局。

调用时硬绑定可以防止绑定缺失

function sayHi(){
    console.log('Hello,', this.name);
}
var person = {
    name: 'YvetteLau',
    sayHi: sayHi
}
var name = 'Wiliam';
var Hi = function(fn) {
    fn.call(this);
}
Hi.call(person, person.sayHi);

输出Hello,YvetteLau,person.sayHi赋值给fn,fn用call硬绑定person.sayHi,sayHi的this指向person。

4. new绑定

function sayHi(name){
    this.name = name;
	
}
var Hi = new sayHi('Yevtte');
console.log('Hello,', Hi.name);

输出Hello,Yevtte,用new sayHi的this绑定到了Hi对象上。

5. 绑定的优先级

new绑定>显式绑定>隐式绑定>默认绑定

6. 绑定例外

将null、undefined作为this传入call bind apply 会被忽略,应用默认绑定。

var foo = {
    name: 'Selina'
}
var name = 'Chirs';
function bar() {
    console.log(this.name);
}
bar.call(null); //Chirs 

7. 箭头函数

(1)函数体内的this对象,继承的是外层代码块的this。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

(5)箭头函数没有自己的this,所以不能用call()、apply()、bind()这些方法去改变this的指向.

var obj = {
    hi: function () {
        console.log(this);
        return () => {
            console.log(this);
        }
    },
    sayHi: function () {
        return function () {
            console.log(this);
            return () => {
                console.log(this);
            }
        }
    },
    say: () => {
        console.log(this);
    }
}
let hi = obj.hi();  //显式绑定,obj
hi();               //箭头函数,继承上一层this,obj
let sayHi = obj.sayHi(); //把return的function赋值给sayHi
let fn1 = sayHi(); //执行赋值后的sayHi,绑定丢失,执行默认绑定,输出window
fn1(); //箭头函数,继承上一层this,输出window
obj.say(); //箭头函数,网上找this,找到了全局的this指向window

10.如何判断this指向

  1. 函数是否存在new 调用,如果有,this绑定的是新创建的对象。
  2. 函数是否通过call apply调用,或者bind硬绑定,如果是this指向的是绑定的对象。
  3. 函数是否在某个上下文对象中调用,如果是,this绑定的是这个上下文对象。
  4. 如果以上都不是,使用默认绑定,严格模式下绑定到undefined,否则绑定到全局对象。
  5. null undefined作为this传入call apply bind 会被忽略,应用默认绑定规则。
  6. 箭头函数继承外层代码块的this。

11. 变量

11.1块级作用域只要存在let这个区域就不受外部影响

    var a = 123
    if (true) {
        a = 'abc' // ReferenceError
        let a;
    }
    

11.2不能在函数内部重复申明变量

    function func(arg) {
      let arg;
    }
    func()
    // Uncaught SyntaxError: Identifier 'arg' has already been declared

11.3 const所保证的不是变量的值不得改动

而是变量指向的内存地址所保存的数据不得改动。对于简单数据类型,值就保存在变量指向的内存相当于常量。对于复杂的数据类型,变量指向的内存地址,保存的是指向实际数据的指针。const只能保证这个指针是不变的。

12.正则

12.1 模糊匹配

字符组

  1. 比如[123456abcdefGHIJKLM],可以写成[1-6a-fG-M]
  2. 排除字符组,[^abc],表示是一个除"a"、"b"、"c"之外的任意一个字符
  3. 常见简写 有了字符组的概念后,一些常见的符号我们也就理解了。因为它们都是系统自带的简写形式。

**\d**就是[0-9]。表示是一位数字。记忆方式:其英文是digit(数字)。

**\D**就是[^0-9]。表示除数字外的任意字符。

**\w**就是[0-9a-zA-Z_]。表示数字、大小写字母和下划线。记忆方式:w是word的简写,也称单词字符。

**\W**是[^0-9a-zA-Z_]。非单词字符。

**\s**是[ \t\v\n\r\f]。表示空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页符。记忆方式:s是space character的首字母。

**\S**是[^ \t\v\n\r\f]。 非空白符。

. 就是[^\n\r\u2028\u2029]。通配符,表示几乎任意字符。换行符、回车符、行分隔符和段分隔符除外。记忆方式:想想省略号...中的每个点,都可以理解成占位符,表示任何类似的东西。

量词

  1. 常见简写

{m,} 表示至少出现m次。

{m} 等价于{m,m},表示出现m次。

? 等价于{0,1},表示出现或者不出现。记忆方式:问号的意思表示,有吗?

+ 等价于{1,},表示出现至少一次。记忆方式:加号是追加的意思,得先有一个,然后才考虑追加。

* 等价于{0,},表示出现任意次,有可能不出现。记忆方式:看看天上的星星,可能一颗没有,可能零散有几颗,可能数也数不过来。

  1. 贪婪模式和惰性模式 贪婪模式:尽可能多的匹配 惰性模式:加个问号{m,}?,尽可能少的匹配

  2. 多选分支 (p1|p2|p3),其中p1p2p3是子模式,用|(管道符)分隔,表示其中任何之一。是惰性模式。

12.2 位置匹配

12.2.1 ^和$

^(脱字符)匹配开头,在多行匹配中匹配行开头。

$(美元符号)匹配结尾,在多行匹配中匹配行结尾。

12.2.2 \b和\B

\b是单词边界,具体就是\w\W之间的位置,也包括\w^之间的位置,也包括\w$之间的位置。

\B就是\b的反面的意思,非单词边界。\w\w\W\W^\W\W$之间的位置。

12.2.3 (?=p)和(?!p)

(?=p),其中p是一个子模式,即p前面的位置。 比如(?=l),表示'l'字符前面的位置

(?!p)就是(?=p)的反面意思

(?<=p)(?<!p)环视,即看看右边或看看左边

12.3括号的使用

12.3.1 分组和分支结构

/a+/匹配连续出现的“a”,而要匹配连续出现的“ab”时,需要使用/(ab)+/ 多选分支结构(p1|p2)中,提供了子表达式的所有可能。

12.3.1 分组替换

其中replace中的,第二个参数里用$1$2$3指代相应的分组

var regex = /(\d{4})-(\d{2})-(\d{2})/; 
var string = "2017-06-12";
var result = string.replace(regex, "$2/$3/$1"); 
console.log(result); // => "06/12/2017"

12.3.3 反向引用

var regex = /\d{4}(-|/|.)\d{2}\1\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // false

注意里面的\1,表示的引用之前的那个分组(-|/|.)。不管它匹配到什么(比如-),\1都匹配那个同样的具体某个字符。

我们知道了\1的含义后,那么\2\3的概念也就理解了,即分别指代第二个和第三个分组。

未完待续。。。

13.数组方法

ES5

13.1 push/pop

 const colors = [];
        colors.push('red', 'black')
        console.log(colors);//['red', 'black']
        const item = colors.pop();
        console.log(item);//'black'
        console.log(colors);//['red']

13.2 shift/unshift

const colors1 = ['red', 'black'];
        const item1 = colors1.shift();
        console.log(item1);//'red'
        console.log(colors1);//['black']
        colors1.unshift('yellow')
        console.log(colors1);//['yellow','black']

13.2 reverse/sort

 const value = [1, 5, 66, 3, 98]
        value.reverse();
        console.log(value);// [98, 3, 66, 5, 1]
        value.sort()
        console.log(value);//[1, 3, 5, 66, 98]
        value.sort((v1, v2) => v2 - v1)
        console.log(value);//[98, 66, 5, 3, 1]

13.3 concat/slice/splice

const colors = ['red', 'green', 'blue'];
        const colors1 = colors.concat('yellow', ['black'])
        console.log(colors1);//["red", "green", "blue", "yellow", "black"]

        const colors2 = colors1.slice(1, 3);
        console.log(colors1); //["red", "green", "blue", "yellow", "black"]
        console.log(colors2);//["green", "blue"]

        const colors3 = colors1.splice(1, 3);
        console.log(colors1); //["red", "black"]
        console.log(colors3);//["green", "blue", "yellow"]

13.4 indexOf & lastIndexOf

const colors = ['red', 'green', 'blue', 'green', "blue", "yellow"];
        console.log(colors.indexOf('green'))//1
        console.log(colors.indexOf('green', 2))//3
        console.log(colors.lastIndexOf('green'))//3
        console.log(colors.lastIndexOf('green', 2))//1

13.5 every & some , filter, forEach, map

const colors = ['red', 'green', 'blue', 'green', "blue", "yellow"];

        const someResult = colors.some(item => item.length > 4)
        console.log(someResult);//true

        const everyResult = colors.every(item => item.length > 4)
        console.log(everyResult);//false

        const filterResult = colors.filter(item => item.length > 4)
        console.log(colors)//['red', 'green', 'blue', 'green', "blue", "yellow"]
        console.log(filterResult);//["green", "green", "yellow"]

        const forEachResult = colors.forEach(item => console.log(item.len))
        console.log(colors)//['red', 'green', 'blue', 'green', "blue", "yellow"]
        console.log(forEachResult);//undefined

        const mapResult = colors.map(item => ({ color: item }))
        console.log(colors)//['red', 'green', 'blue', 'green', "blue", "yellow"]
        console.log(mapResult);//[{color: "red"},{ color: "green" }.....]

13.6 reduce&reduceRight

const colors = ['red', 'green', 'blue', 'green', "blue", "yellow", "blue",];

        const reduceResult = colors.reduce((prev, cur, idx) => {
            prev[cur] ? prev[cur] = Array.isArray(prev[cur]) ? [...prev[cur], cur] : [prev[cur], cur] : prev[cur] = cur
            return prev
        }, {})

        console.log(colors);// ["red", "green", "blue", "green", "blue", "yellow", "blue"]
        console.log(Object.values(reduceResult)); //["red",["green", "green"],["blue", "blue", "blue"], "yellow"]

        const reduceRightResult = colors.reduceRight((prev, cur, idx) => {
            prev[cur] ? prev[cur] = Array.isArray(prev[cur]) ? [...prev[cur], cur] : [prev[cur], cur] : prev[cur] = cur
            return prev
        }, {})
        
        const reduceReverse = colors.reverse().reduce((prev, cur, idx) => {
            prev[cur] ? prev[cur] = Array.isArray(prev[cur]) ? [...prev[cur], cur] : [prev[cur], cur] : prev[cur] = cur
            return prev
        }, {})

        console.log(colors);// ["red", "green", "blue", "green", "blue", "yellow", "blue"]
        console.log(Object.values(reduceRightResult)); //[["blue", "blue", "blue"], "yellow",["green", "green"],"red"]
        console.log(colors);// ["red", "green", "blue", "green", "blue", "yellow", "blue"]
        console.log(Object.values(reduceReverse)); //[["blue", "blue", "blue"], "yellow",["green", "green"],"red"]

ES6

13.7 Array.from() & Array.of()

 // Array.from()对类数组或可迭代对象创建一个新的浅拷贝
const obj = {
            0: 'red',
            1: 'black',
            length: 3
        }
        const arr = Array.from(obj)
        console.log(arr);//["red", "black", undefined]
        console.log(Array.from('foo'));//["f", "o", "o"]
        // Array.of()创建数组实例
        console.log(Array.of('foo'));//["foo"]

13.8 fill

const color = ['red', 'black', 'yellow']
const fillResult = color.fill('white')
console.log(color);//["white", "white", "white"]
console.log(fillResult);//["white", "white", "white"]

13.8 find & findIndex

const color = ['red', 'black', 'yellow'];
        const findResult = color.find(i => i.length > 3)
        console.log(color);//["red", "black", "yellow"]
        console.log(findResult); //black

        const findIndexResult = color.findIndex(i => i.length > 3)
        console.log(color);//["red", "black", "yellow"]
        console.log(findIndexResult); //1

13.9 entries(), keys() & values()

const colorObj = {
            name: 'red',
            rgba: '(255,0,0)',
            af: 'f00'
        }
        console.log(Object.entries(colorObj));//[["name", "red"],"rgba", "(255,0,0)"],["af", "f00"]]
        console.log(Object.keys(colorObj));//["name", "rgba", "af"]
        console.log(Object.values(colorObj));//["red", "(255,0,0)", "f00"]

ES7

13.10 includes

const color = ['red', 'black', 'yellow'];
        console.log(color.includes('red'));//true
        console.log(color.includes('red', 1));//false

ES10

13.11 flat

const value = [1, 2, [3, 4, [5, 6]]];
        const flatResult = value.flat();
        console.log(value); //[1, 2, [3, 4, [5, 6]]]
        console.log(flatResult);//[1, 2, 3, 4, [5, 6]]
        const flatResultDeep = value.flat(Infinity);
        console.log(flatResultDeep);//[1, 2, 3, 4, 5, 6]

        const flatMapResult = value.flatMap(i => i * 2);
        console.log(value); //[1, 2, [3, 4, [5, 6]]]
        console.log(flatMapResult);//[2, 4, NaN]

浏览器相关

1. 网络安全

1.1 sql注入

后端依赖前端返回的数据进行sql拼接查询
解决办法:后端对前端传递的数据进行非法校验,业务校验

1.2XSS跨站脚本攻击

通过某种方式在前端代码中注入js,最常见就是表单提交。
解决办法:标签替换/cookie http-only/Content-Security-Policy域名白名单

1.3CSRF跨站请求伪造

利用操作者权限完成某个操作,原理就是cookie的同源策略
解决办法:增加额外校验/get改post/判断urlReferer/判断IP/增加时效性

1.4网页嵌套攻击

通过iframe嵌套网页,嵌套透明化,页面点击触发自己的事件。
解决办法:header设置不允许嵌套X-FARME-OPTIONS/判断当前页面是不是顶层窗口

2. 从url输入之后发生了什么

  • 查找缓存
  • DNS解析
  • TCP连接
  • 发送请求
  • 接受响应
  • 关闭tcp连接
  • 渲染页面
  1. 构建dom树
  2. 构建css树
  3. 构建render tree
  4. 布局
  5. 绘制

3. 浏览器缓存

强缓存

直接从浏览器缓存获取,Expires (一个绝对的时间)和 Cache-Control(可设置缓存最大时间,是否被服务器缓存等)

协商缓存

浏览器带着缓存标志像服务器请求,服务端决定是否使用缓存。Last-Modified / If-Modified-Since (最后修改的时间/是否被缓存)和 Etag / If-None-Match(文件是否被修改)

React

1.React事件机制

JSX上的事件并没有绑定在真实dom上,而是在document监听了所有事件,当合成事件冒泡到document,React将事件内容封装交给真正的处理函数。

2.合成事件目的

  • 抹平了浏览器之前的兼容性
  • 减少内存消耗,可统一订阅移除事件。原生浏览器事件每一个都会有事件对象,而合成事件有一个事件池来统一创建喝销毁,当事件被需要可以从池子里复用对象。

4.React事件和HTML事件区别

  • 事件名称命名方式
  • 事件函数处理语法
  • 阻止默认行为

5.React事件代理

利用虚拟dom实现了一个合成事件层 事件委派:把事件绑定到做外层,用统一的事件监听器,监听器内部映射了所有组件内部事件的监听和处理函数。 自动绑定:React组件中每个方法的上下文都会指向改组件的实例,即自动绑定this为当前组件。

6.React高阶组件,Render props, hooks

React高阶组件

是一种设计模式,高阶组件是一个函数,参数是组件返回一个新的组件。

Render props

告诉组件需要渲染什么内容的函数。

hooks

不编写class的情况下使用React特性

7.React Fiber(基于浏览器的单线程调度算法)

React V15 渲染对比虚拟dom后会同步更新,React V16 通过Fiber让这个过程变得异步可中断。

8.React.Component React.PureComponent

React.PureComponent是一个纯组件,减少render函数执行次数,提高组件性能。当state或者props更新时可以通过shouldComponentUpdate return false来阻止更新。React.PureComponent会自动执行shouldComponentDidUpdate,并对数据进行浅比较,如果是引用数据类型的数据只会比较是不是同一个地址。

9.React.createClass和extends React.Component

区别在于方法定义和静态属性的声明。React.createClass本质是一个函数。

10.React重新渲染

1.setState
2.父组件重新渲染

11.重新渲染render做什么

1.对新旧虚拟DOM进行对比。 2.对新旧两棵树进行深度优先遍历,把新节点和旧节点进行对比,有差异就放到一个对象里。 3.遍历差异对象,根据差异的类型,用相应的规则更新DOM树。

12.React Fragment

不添加节点对子列表进行分组。

13.React获取dom元素

  • 元素内字符串格式定义ref
  • 元素内函数形式定义ref
  • React16 create Ref render阶段不可以获取refs,因为dom还没生成

14.React后生命周期

创建期

  1. constructor(props)
  2. componentWillMount()
  3. render()
  4. componentDidMount() 组件dom节点插入到dom树中加载完后立即执行

props发生变化时

  1. componentWillReceiveProps(nextProps,nextContext)
  2. shouldComponentUpdate(nextProps,nextState,nextContext) 渲染新的props,state前被调用,如果返回false,getSnapShotBeforeUpdate(),render,componentDidMount不会被调用。主要用于性能优化。
  3. componentWillUpdate(nextProps, nextState, nextContext)
  4. render() 5.componentDidUpdate(prevProps,prevStates,snapshot)

state发生变化时

  1. shouldComponentUpdate(nextProps,nextState,nextContext)
  2. componentWillUpdate(nextProps, nextState, nextContext)
  3. render() 4.componentDidUpdate(prevProps,prevStates,snapshot)

卸载时

  1. componentWillUnmount 组件被卸载销毁前调用,如取消定时器,取消网络请求,componentDidMount中创建的监听器。

16.3版本新增

  1. static getDerivedStateFromProps(props,status) 当组件需要props的改变来更新state,返回值为state对象,每次触发render前都会触发此方法。
  2. getSnapShotBeforeUpdate(prevProps,prevStat) render输出被渲染到dom之前调用,使组件能够在被更改之前获取到当前值。这个生命周期返回的任何值都会被作为第三个参数传递给componentDidUpdate()。

15.hooks为什么不能写在条件判断里

react靠调用的顺序知道那个state调用哪个useState,多次渲染只要保证hooks调用顺序一致,React就能将state与对应的hook关联。

16. 类组件与函数组件有什么异同?

相同点: 组件是 React 可复用的最小代码片段,它们会返回要在页面中渲染的 React 元素。也正因为组件是 React 的最小编码单位,所以无论是函数组件还是类组件,在使用方式和最终呈现效果上都是完全一致的。

我们甚至可以将一个类组件改写成函数组件,或者把函数组件改写成一个类组件(虽然并不推荐这种重构行为)。从使用者的角度而言,很难从使用体验上区分两者,而且在现代浏览器中,闭包和类的性能只在极端场景下才会有明显的差别。所以,基本可认为两者作为组件是完全一致的。

不同点:

  • 它们在开发时的心智模型上却存在巨大的差异。类组件是基于面向对象编程的,它主打的是继承、生命周期等核心概念;而函数组件内核是函数式编程,主打的是 immutable、没有副作用、引用透明等特点。
  • 之前,在使用场景上,如果存在需要使用生命周期的组件,那么主推类组件;设计模式上,如果需要使用继承,那么主推类组件。但现在由于 React Hooks 的推出,生命周期概念的淡出,函数组件可以完全取代类组件。其次继承并不是组件最佳的设计模式,官方更推崇“组合优于继承”的设计概念,所以类组件在这方面的优势也在淡出。
  • 性能优化上,类组件主要依靠 shouldComponentUpdate 阻断渲染来提升性能,而函数组件依靠 React.memo 缓存渲染结果来提升性能。
  • 从上手程度而言,类组件更容易上手,从未来趋势上看,由于React Hooks 的推出,函数组件成了社区未来主推的方案。
  • 类组件在未来时间切片与并发模式中,由于生命周期带来的复杂度,并不易于优化。而函数组件本身轻量简单,且在 Hooks 的基础上提供了比原先更细粒度的逻辑组织与复用,更能适应 React 的未来发展。

17 Vue与React的选择?

Webpack

静态模块打包器,将程序根据入口生成一个依赖关系,将这些模块打包成一个或多个bundle.

1.核心概念

  • input入口
  • optput出口
  • loader转换器,将文件转换成浏览器能认识的
  • plugins扩展插件,在构建流程中加入扩展逻辑。

2.loader

babel-loader/style-loader/css-loader/postcss-loader/less-loader/autoprefixer/url-loader/file-loader test:匹配规则,针对符合规则的文件处理 use:使用的loader,可以是字符串,也可以是数组,数组中的内容可以是对象,配置options

3.plugins

html-webpack-plugin/clean-webpack-plugin/terser-webpack-plugin/css-minimizer-webpack-plugin/mini-css-extract-plugin/copy-webpack-plugin将单个文件或目录复制到构建目录/ProvidePlugin配置一些文件全局,不需要引入直接使用/HotModalReplacementPlugin/

4.devServer

配置一些本地启动的信息,host,端口,打印限制stats

5.devtool

source-map

6.按需加载

用到的时候再去import

7.resolve

modules 配置三方模块查找的路径 alias 设置路径别名

8.定义环境变量

webpack-merge DefinePlugin

9.跨域解决

deServer里配置proxy

devServer: {
proxy: { 
'/api': {
target: 'http://localhost:4000',
pathRewrite: {
'/api': ''
} 
}
} 
}

10.Babel

10.1 Preset

一些Plugin组成的合集

常见的Preset

  • @babel/preset-env,将高版本的js代码根据内置规则转译成低版本js,仅针对语法阶段的转译,如箭头函数,const/let。
  • babel-preset-react,将jsx转译,jsx最终会被编译成React.createElement()
  • babel-preset-typescript,讲ts编译成js.

10.2 前端基建中babel配置

  • babel-loader:识别匹配文件,接受对应参数的函数。
  • babel-core:将代码词法语法语义分析成AST抽象语法数。
  • babel-preset-env,通过babel-core告诉babel以什么样的规则进行转译。

10.3 polyfill

api和实例方法的转译需要polyfill

@babel/polyfill

往全局对象上添加属性,直接修改内置对象的原型的方法,这种方式会造成全局污染。 在babel-preset-env中有一个useBuiltIns参数,决定如何在preset-env种使用@babel/polyfill。

{
    "presets": [
        ["@babel/preset-env", {
            "useBuiltIns"false //-   `useBuiltIns`--`"usage"`| `"entry"`| `false`
        }]
    ]
}

false:只会转译语法。不会转译任何API和方法。 entry:需在入口文件手动引入core-js,根据broswerList全量引入polyfill uasge:根据broswerList以及代码中用到的API按需引入polyfill.

@babel/runtime

按需加载解决方案,需要自己手动引入。

@babel/plugin-transform-runtime

解决babel/runtime手动引入以及编译过程中重复生成冗余代码。

网络相关

1. http与https的区别与优缺点

  • http是超文本传输协议,是明文传输,连接是简单无状态的,端口是80.
  • https是在http的基础上加了一层ssl加密传输协议,比较安全,默认端口号是443,握手状态比较费时,需要ca证书。

2. https通信步骤

  1. 客户端发送支持加密的协议和版本给客户端。
  2. 服务端获取合适的协议。
  3. 服务端发送证书和公钥给客户端。
  4. 客户端通过根证书验证证书,生成对称密钥,通过公钥加密发送给客户端。
  5. 服务端通过私钥解密获得对称秘钥,加密数据发送给客户端。
  6. 客户端解密获取数据,建立连接。

3. TCP的三次握手

  1. 客户端发送syn到服务端
  2. 服务端确认收到后发送syn和ack到客户端。
  3. 客户端收到后发送ack到服务端。

4. 四次挥手

  1. 客户端发送链接释放给服务端
  2. 服务端收到后发送ack到客户端,等待关闭
  3. 服务端确认数据已经发送完成
  4. 客户端发送ack关闭连接

5. post和get的区别

get:数据放在url上,只发送一个tcp连接,大小有限制,会被浏览器主动缓存

post:数据放在请求体内,参数不被浏览器缓存所以更安全,发送两个tcp,先头后请求体

6. TCP和UDP

UDPTCP
是否连接无连接面向连接
是否可靠不可靠传输,不使用流量控制和拥塞控制可靠传输,使用流量控制和拥塞控制
连接对象个数支持一对一,一对多,多对一和多对多交互通信只能是一对一通信
传输方式面向报文面向字节流
首部开销首部开销小,仅8字节首部最小20字节,最大60字节
适用场景适用于实时应用(IP电话、视频会议、直播等)适用于要求可靠传输的应用,例如文件传输

手写js

1.防抖

触发多次重置倒计时只在倒计时结束后执行,如input输入

function debunce(fun, time) {
            let timer;
            return function () {
                clearTimeout(timer)
                timer = setTimeout(() => {
                    fun.apply(this)
                }, time)
            }
        }

2.节流

单位时间内执行一次,如滚动加载

 function throttle(fun, time) {
            let t1 = 0
            return function () {
                let t2 = new Date();
                if (t2 - t1 > time) {
                    fun.apply(this)
                    t1 = t2
                }

            }
        }

4.函数柯里化

高阶函数的一种特殊应用。将多参数的一个函数,转成一系列只有一个参数的函数。不停的递归拼接参数,参数满足后调用传入的函数。

4.1高阶函数

  • 函数的参数是一个函数,回调函数是一种高阶函数。
  • 函数返回一个函数,当前函数也是一个高阶函数。
const curry(fun,...args){
const funLen= fun.length;
return function(...innerArgs){
   innerArgs=args.concat(innerArgs);
   if(innerArgs.length<funLen){
       return curry(fun,...innerArgs)
   }else{
   fun(...innerArgs)
   }
    }
}

5.手写allpy

```js
    Function.prototype.apply2 = function (context) {
        // 不传参默认执行环境为window
        context = context || window;
        // 把调用apply2的函数(this)添加到执行环境上
        context.fn = this;
        // 拿到传入apply2的第二个参数
        const params = arguments[1];
        // 把参数放入需要执行的函数上
        const result = params ? context.fn(...params) : context.fn();
        // 删除添加的函数
        delete context.fn;
        // 返回执行结果
        return result;
    }

    testThis.apply2({ name: 'Ada' }, [30, 'teacher'])
    ```

6. 手写call

 Function.prototype.call2 = function (context) {
               // 不传参默认执行环境为window
               context = context || window;
               // 把调用call2的函数(this)添加到执行环境上
               context.fn = this;
               // 拿到传入call2的剩余参数
               const params = [...arguments].slice(1)
               // 把参数放入需要执行的函数上
               const result = context.fn(...params);
               // 删除添加的函数
               delete context.fn;
               // 返回执行结果
               return result;

           }
           // testThis(20)
           testThis.call2({ name: 'Lily' }, 10, 'student')

7.手写bind

Function.prototype.bind2 = function (context) {
          // 不传参默认执行环境为window
          context = context || window;
          // 拿到传入bind2的剩余参数
          const params = [...arguments].slice(1);
          let _this = this;
          // 把调用bind2的函数(this)添加到执行环境上
          context.fn = this;
          return function F() {
              // 被当作构造函数调用
              if (this instanceof F) {
                  _this(...params)
              } else {
                  const result = context.fn(...params);
                  // 删除添加的函数
                  delete context.fn;
                  // 返回执行结果
                  return result;
              }
          }
      }

      const bind2Test = testThis.bind2({ name: 'Rax' }, 40, 'president')
      bind2Test();
      new bind2Test();

8. new的实现

function NewMy(context) {
          const obj = new Object;
          obj._proto_ = context.prototype;
          const res = context.apply(obj, [...arguments.slice(1)])
          return typeof res === 'object' ? res : obj;
      }

9. 深拷贝

9.1 JSON.parse(JSON.stringify());

缺点:可以实现数组或对象深拷贝,但不能处理函数和正则

9.2 递归实现

function clone(target) {
            if (typeof target === 'object') {
                let newTarget = Array.isArray(target) ? [] : {};
                for (let key in target) {
                    newTarget[key] = clone(target[key])
                }
            } else {
                return target
            }
        }

9.3 存在循环引用,并且有其他类型的情况

 function deepClone(obj, map = new WeakMap()) {
           if (obj === null || typeof obj !== 'object') return obj;
           if (obj instanceof Date) return new Date(obj);
           if (obj instanceof RegExp) return new RegExp(obj);
           // 如果以上条件都不满足进行深拷贝

           // 查找之前是否已经拷贝过
           if (map.get(obj)) return map.get(obj);
           // 创建一个新的所属类
           let newTarget = new obj.constructor();
           map.set(obj, newTarget)
           for (let key in obj) {
               console.log(key);
               // 判断值是否在实例上定义
               if (obj.hasOwnProperty(key)) {
                   console.log(key);
                   newTarget[key] = deepClone(obj[key], map)
               }
           }
           return newTarget
       }

10.Promise

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
    constructor(executor) {
        try {
            executor(this.resolve, this.reject)
        } catch (err) {
            this.reject(err)
        }

    }

    status = PENDING;
    value = null;
    reason = null;

    // 储存回调函数
    onFulfilledCallback = [];
    onRejectedCallBack = [];

    // 箭头函数让this指向当前实例
    resolve = value => {
        if (this.status == PENDING) {
            this.status = FULFILLED;
            this.value = value;
            // 判断回调是否存在,如果存在就调用
            this.onFulfilledCallback && this.onFulfilledCallback.forEach(fn => fn(value));
        }
    }
    reject = reason => {
        if (this.status == PENDING) {
            this.status = REJECTED;
            this.reason = reason;
            // 判断回调是否存在,如果存在就调用
            this.onRejectedCallBack && this.onRejectedCallBack.forEach(fn => fn(reason));
        }
    }

    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
        // 为了链式调用需要创建一个promise并且return
        const promise2 = new MyPromise((resolve, reject) => {
            if (this.status == FULFILLED) {
                // 创建微任务等待promise2初始化完成
                queueMicrotask(() => {
                    try {
                        // 拿到成功回调的执行结果
                        const x = onFulfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (err) {
                        reject(err)
                    }

                })
            } else if (this.status == REJECTED) {
                queueMicrotask(() => {
                    try {
                        const x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (err) {
                        reject(err)
                    }
                })

            } else if (this.status == PENDING) {
                // 不知道状态的变化所以成功回调和失败回调都要存起来。
                this.onFulfilledCallback.push(() => {
                    try {
                        const x = onFulfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (err) {
                        reject(err)
                    }
                });
                this.onRejectedCallBack.push(() => {
                    try {
                        const x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (err) {
                        reject(err)
                    }
                })
            }
        })
        return promise2;
    }

    static resolve(param) {
        // 传入promise直接返回
        if (param instanceof MyPromise) { return param }
        return new MyPromise(resolve => {
            resolve(param)
        })
    }

    static reject(reason) {
        return new MyPromise((resolve, reject) => {
            reject(reason)
        })
    }

}

function resolvePromise(promise2, x, resolve, reject) {
    // console.log(promise2);
    // console.log(x);
    if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise # < Promise >'))
    }
    if (x && (typeof x === 'object' || typeof x === 'function')) {
        // 实现值穿透,拿到传入promise的value,并将value resolve成创建的promise的value将创建的promise的状态改变。
        // x.then(value => resolve(value), reason => reject(reason))
        let then;
        try {
            then = x.then;
        } catch (err) {
            return reject(err)
        }
        console.log(then);
        // x是promise的时候处理
        if (typeof then === 'function') {
            // 如果resolvePromise 和 rejectPromise 都调用了第一个优先调用,后面忽略
            let called = false;
            try {
                then.call(x,
                    y => {
                        if (called) return;
                        called = true;
                        // 还是做值穿透,y就是x.then里面的value
                        resolvePromise(promise2, y, resolve, reject)
                    },
                    r => {
                        if (called) return;
                        called = true;
                        reject(r)
                    }
                )
            } catch (err) {
                if (called) return;
                reject(err)
            }
        } else {
            // x是普通对象及函数直接处理
            resolve(x)
        }

    } else {
        // x是对象及函数外直接处理
        resolve(x)
    }
}

function other() {
    return () => { console.log('it is a function'); }
    return {
        'a': 111,
        'b': 222
    }
    return new MyPromise((resolve, reject) => {
        resolve('other')
    })
}

const promise = new MyPromise((resolve, reject) => {
    // 传入value或者reason,改变status
    // throw new Error('执行器error')
    resolve('success')
    // reject('err')//不会执行,status已经改变。
})
    .then()
    .then()
    // 拿到value或者reason,执行传入的回调函数
    .then(value => {
        console.log('resolve1', value);
        // throw new Error('then error')
        return other()
        // return MyPromise.resolve('resolve static')
    }, reason => {
        // throw new Error('then error')
        console.log('reject', reason);
    })
    .then(value => {
        console.log('resolve2', value);
    }, reason => {
        console.log('reject', reason);
    })

11. 获取url参数

function getSearchParams(url) {
            const decodeUrl = decodeURIComponent(url)
            let params = {};
            const urlArr = decodeUrl.split("?");
            const paramsArr = urlArr[1].split("&");
            for (let i = 0; i < paramsArr.length; i++) {
                const queryArr = paramsArr[i].split("=");
                try {
                    JSON.parse(queryArr[1])
                    params[queryArr[0]] = JSON.parse(queryArr[1])
                } catch (e) {
                    params[queryArr[0]] = queryArr[1];
                }
            }
            return params;
        }

12.手写instanceof

function myInstanceof(target, origin) {
            if (typeof target !== 'object' || target == null) return;
            if (typeof origin !== 'function') {
                throw new TypeError('origin must be function')
            };
            let proto = Object.getPrototypeOf(target)
            while (proto) {
                if (proto == origin.prototype) return true;
                proto = Object.getPrototypeOf(proto)
            }
            return false
        }
        console.log(myInstanceof([1, 2, 3], Array));

13.数组扁平化

 const arr = [[1, 2], [2, 3, [3, 4, 5]]]
      function flat(arr, depth = 1) {
          if (depth > 0) {
              return arr.reduce((pre, cur) => {
                  return pre.concat(Array.isArray(cur) ? flat(cur, depth - 1) : cur)
              }, [])
          }
          return arr;
      }

      console.log(flat(arr, 2))

14.手写reduce

 Array.prototype.myReduce = function (cb, initialV) {
          const arr = this;
          const total = initialV;
          for (let i = 0; i < arr.length; i++) {
              total = cd(total, arr[i], i, arr)
          }
          return total
      }

15.带并发的异步调度器

 class Scheduler {
          constructor() {
              this.wating = [];
              this.executing = [];
              this.max = 2;
          }
          add(promiseMaker) {
              if (this.executing.length < this.max) {
                  this.executing.push(promiseMaker)
                  this.run(promiseMaker)
              } else {
                  this.wating.push(promiseMaker)
              }
          }
          run(promiseMaker) {
              promiseMaker().then(() => {
                  this.executing.slice(this.executing.length - 1, 1);
                  this.wating.length > 0 && this.run(this.wating.shift())
              })
          }
      }

      const timeout = (time) => new Promise((resolve) => {
          setTimeout(resolve, time);
      });

      const scheduler = new Scheduler();
      const addTask = (time, order) => {
          scheduler.add(() => timeout(time).then(() => console.log(order)));
      };

      addTask(1000, "1");
      addTask(500, "2");
      addTask(300, "3");
      addTask(400, "4");

16.数组去重

function unique1(arr) {
           return [...new Set(arr)]
       }

       function unique2(arr) {
           return arr.filter((item, idx, array) => {
               return array.indexof(item) === idx
           })
       }

17.链式调用

function Query(data) {
            this.data = data;
            let whereArr = [];
            let result = [];
            this.where = function (predicate) {
                for (let i = 0; i < data.length; i++) {
                    if (predicate(data[i])) {
                        whereArr.push(data[i])
                    }
                }
                return this
            }
            this.orderBy = function (key, desc) {
                function compare(property, desc) {
                    return function (a, b) {
                        var value1 = a[property];
                        var value2 = b[property];
                        if (desc) {
                            return value2 - value1;
                        }
                        return value1 - value2;
                    }
                }
                whereArr.sort(compare(key, desc))
                return this
            }
            this.groupBy = function (key) {
                result = Object.values(whereArr.reduce((res, item) => {
                    res[item[key]] ? res[item[key]].push(item) : res[item[key]] = [item];
                    return res;
                }, {}));
                return this
            }
            this.execute = function () {

                return result
            }

        }
        const query = new Query(data)
        console.log(query.where(item => item.age > 18).orderBy('age', true).groupBy('city').execute());
 class Calculator {
            constructor(value) {
                this.value = value || 0;
            }
            add(count) {
                this.value += count;
                return this;
            }
            minus(count) {
                this.value -= count;
                return this;
            }
            multi(count) {
                this.value *= count;
                return this;
            }
            div(count) {
                if (this.value == 0) return;
                this.value = this.value / count;
                console.log(this.value);
                return this;
            }

        }

        const myCalculator = new Calculator(121);
        myCalculator.add(1).minus(2).multi(3).div(4)