收集整理的美团面试题

374 阅读42分钟

算法题

blog.csdn.net/qq_41800649…

排序

快排

let arr = [2,5,6,7,9,12,3,6,8,0,0,12,3,4];

function partition(arr,l,r){
    let mid = arr[l];
    while(l <= r){
        while(l<r && arr[r] >= mid) r--;
        arr[l] = arr[r];
        while(l<r && arr[l] <= mid) l++;
        arr[r] = arr[l];
        if(l === r) break;
    }
    arr[l] = mid;
    return l;
}

function quickSort(arr,left,right){
    if(arr.length <= 1) return;
    if(left < right){
        let index = partition(arr,left,right);
        quickSort(arr,index+1,right);
        quickSort(arr,left,index-1);
    }
    return;
}
quickSort(arr,0,arr.length-1);
console.log(arr);

冒泡排序

let arr = [2,5,6,7,9,12,3,6,8,0,0,12,3,4];
function babbleSort(arr){
    if(arr.length <= 1) return;
    let len = arr.length;
    for(let i = 0; i < len ; i++){//外层循环走len次
        let flag = false;//标记位减少循环次数
        for(let j = 0; j < len - i - 1; j++){//内层循环走len-i-1次
            if(arr[j] > arr[j+1]) {
                [arr[j],arr[j+1]] = [arr[j+1],arr[j]];
                flag = true;
            }
        }
        if(!flag) break;
    }
    return;
}
babbleSort(arr);
console.log(arr);

数组

数组有哪些方法

push pop shift unshift
slice splice reverse

创建数组:Array.of、Array.from
改变自身(9 种):poppushshiftunshiftreversesortsplice、copyWithin、fill
不改变自身(12 种):concat、join、slice、toString、toLocaleString、valueOf、indexOf、lastIndexOf、未形成标准的 toSource,以及 ES7 新增的方法 includes,以及 ES10 新增的方法 flat、flatMap
不会改变自身的遍历方法一共有(12 种): forEach、every、some、filter、map、reduce、reduceRight,以及 ES6 新增的方法 find、findIndex、keysvalues、entries。


链接:https://juejin.cn/post/6937526265201033230

随机排列的数组处理后所有奇数排在前面 偶数排在后面 要求不能新开数组

let arr = [1,2,3,3,4,5,6,6,7,8,9,9];
function specialSort(arr){
    let left = 0,right = arr.length -1;
    while(left <= right){//双指针
        while(left < right && arr[left]%2 === 1) left++;
        while(left < right && arr[right]%2 === 0) right--;
        if(left === right) return;
        [arr[left],arr[right]] = [arr[right],arr[left]];
        left++;
        right--;
    }
    return;
}
specialSort(arr);
console.log(arr);

数组乱序 实现随机排序

let arr = [1,2,3,3,4,5,6,6,7,8,9,9];
arr.sort((a,b)=>{
    return Math.random()<0.5 ? -1 : 1;
})
console.log(arr);
let arr = [1,2,3,3,4,5,6,6,7,8,9,9];
function randomSort(arr){
    for(let i = 0; i < arr.length; i++){
        let t = parseInt(Math.random()*arr.length);
        [arr[i],arr[t]] = [arr[t],arr[i]];
    }
    return;
}
randomSort(arr);
console.log(arr);

求数组中最大的第k个数

let arr = [10,2,3,35,4,95,6,6,7,78,9,9];

function partition(arr,left,right){
    let mid = arr[left];
    while(left <= right){
        while(left < right && arr[right] <= mid) right--;
        arr[left] = arr[right];
        while(left < right && arr[left] >= mid)  left++;
        arr[right] = arr[left];
        if(left === right) break;
    }
    arr[left] = mid;
    return left;
}

function findKnth(arr,k){
    let left = 0;
    let right = arr.length - 1;
    let index = partition(arr,left,right);
    while(index !== k-1){
        if(index > k-1)
            right = index -1;
        if(index < k-1)
            left = index + 1;
        index = partition(arr,left,right);
    }
    return arr[index];       
}
console.log(findKnth(arr,5));

数组去重

set

let arr = [10,2,3,10,9,35,4,95,6,6,7,78,78,9,9];
let set = new Set(arr);
arr = [...set];
console.log(arr);

map

let arr = [10,2,3,10,9,35,4,95,6,6,7,78,78,9,9];

let map = new Map();
let newArr = [];
arr.forEach((item)=>{
    if(!map.has(item)){
        newArr.push(item);
        map.set(item,1);
    }
})
console.log(newArr);

splice

let arr = [10,2,3,10,9,35,4,95,6,6,7,78,78,9,9];
arr.sort((a,b)=>a-b);
for(let i = 1; i < arr.length; i++){
    if(arr[i] === arr[i-1]){
        arr.splice(i,1);//删去重复的
        i--;//还指向当前的元素
    }
}
console.log(arr);

数组扁平化

flat

let arr = [10,2,[3,10,[9,35,4,95]],6,6,7,78,[78,9],9];
arr = arr.flat(Infinity);//展平成最大值
console.log(arr);

reduce

let arr = [10,2,[3,10,[9,35,4,95]],6,6,7,78,[78,9],9];

function flatter(arr){
   return arr.reduce((pre,cur,index)=>{
        if(Array.isArray(cur)){//如果是数组就递归
           return pre.concat(flatter(cur));
        }else{//不是就直接连接
           return pre.concat(cur);
        }
    },[])
}

console.log(flatter(arr));

数学

Pow函数的实现和优化

function pow(x,n){
    if(n === 0) return x;
    return x*pow(x,n-1);
}
递归,空间复杂度是O(n)

利用尾递归优化(尾递归是返回的时候最后执行的操作是一个函数)
空间复杂度是O(1)

function pow(x,n,total = 1){
    if(n === 0) return total;
    return pow(x,n-1,total*x);
}

斐波那契数列求和

//输入一个数,表示到第x个fib的数求和
function fibSum(x){
    if(x === 0 || x === 1) return x + 1;
    let arr = [1,1];
    let sum = 2;
    for(let i = 2; i <= x; i++){
        let temp = arr[0] + arr[1];
        sum += temp;
        arr[0] = arr[1];
        arr[1] = temp; 
    }
    return sum;
}

console.log(fibSum(10));

判断一个点是否在三角形内部

两个大数乱序集合只有一个不同或者都相同,如何分辨(直接相加,超出溢出也没问题。最后提醒异或)

let arr = [1,1,2,2,4,5,6,4,5,6,3];//找出只有一个的数

let result = arr.reduce((pre,cur,index)=>{
    if(index === 0) return cur;//设定初始值
    else{
        return pre^cur;//异或
    }
})

console.log(result);

字符串

一串数字每隔3个加一个逗号实现

let num = 1237890345;
let newNum = num.toLocaleString();//把数字转化成特定语言环境下的字符串
console.log(newNum);//1,237,890,345

let num = 1237890345;
let str = num+'';
let newStr = '';
let count = 0;
for(let i = str.length-1; i>=0; i--){
    if(count%3===0 && count!==0){
        newStr = str[i] +","+newStr;
    }else{
        newStr = str[i] + newStr;
    }
    count++;
}
console.log(newStr);//1,237,890,345

在一个字符串中找出所有的顺序子串(给定字符串“adc”,输出“a”, “d”, “c”, “ad”, “dc”, “adc”)

let str = 'abcar';
function findChildStr(str){
    let set = new Set();
    for(let i = 1; i <= str.length; i++){//长度
        for(let j = 0; j+i <= str.length; j++){
            let childStr = str.substring(j,i+j);//从下标j 到i+j
            set.add(childStr);
        }
    }
    return [...set].join(',');
}
console.log(findChildStr(str));//a,b,c,r,ab,bc,ca,ar,abc,bca,car,abca,bcar,abcar

求两个字符串的最长公共子序列

动态规划
function LSC(text1,text2){
	let len1 = text1.length;
    let len2 = text2.length;
    
    let dp = [];
    for(let i = 0; i <= len1; i++){
    	dp[i] = [];
        for(let j = 0; j <= len2; j++){
        	dp[i].push(0);
        }
    }
    
    for(let i = 0; i < len1; i++){
    	for(let j = 0; j < len2; j++){
        	if(text1[i] === text2[j]){
            	dp[i+1][j+1] = dp[i][j] + 1;
            }else{
            	dp[i+1][j+1] = Math.max(dp[i][j+1],dp[i+1][j]);
            }
        }
    }
    
    return dp[len1][len2];
}

从英文文章中找出现次数最多的前三个单词

function counts(article){
    //去除首尾空格 全部转为大写
    article = article.trim().toUpperCase();
    //匹配出所有单词 正则 + 表示至少有一个
    var array = article.match(/[A-z]+/g);
    //规范原文章
    article = " "+array.join("  ")+" ";

    var max = 0,word,num = 0,maxword="";
    //遍历字符串数组
    for(var i = 0; i < array.length; i++) {        
        word = new RegExp(" "+array[i]+" ",'g');//规定匹配模式
    num = article.match(word).length;//匹配单词
    if(num>max){
        max=num;
        maxword = array[i];
    }
   }
   console.log(maxword+" "+max);
}
counts("Age has reached the end of the beginning of a word. May be guilty in his seems to passing a lot of different life became the appearance of the same day;");

二叉树和链表

二叉树遍历

//结构
function TreeNode(x){
	this.val = x;
    this.left = null;
    this.right = null;
}

1.前序遍历(根左右)
function preDfs(root){
	if(!root) return;
    console.log(root.val);
    preDfs(root.left);
    preDfs(root.right);
}
2.中序遍历
3.后序遍历
4.层次遍历
function layBfs(root){
	if(!root) return;
    let queue = [];
    queue.push(root);
    while(queue.length){
    	let top = queue.shift();
        console.log(top.val);
        if(top.left) queue.push(top.left);
        if(top.right) queue.push(top.right);
    }
    return;
}

在单循环链表的head后面插入一个node

//单链表的数据结构
function ListNode(x){
    this.val = x;
    this.next = null;
}
//构造一个循环单链表
let head = new ListNode(0);
let pre = head;
for(let i = 1; i < 5; i++){
    let l1 = new ListNode(i);
    pre.next = l1;
    pre = pre.next;
}
pre.next = head;

//插入一个新节点
function insertNode(head,x){
    let node = new  ListNode(x);
    let tail = head.next;
    head.next = node;
    node.next = tail;
    return;
}

insertNode(head,19);

//验证链表
let count = 0;
pre = head;
while(pre && count <10){
    console.log(pre.val);
    pre = pre.next;
    count++;
}

反转单向链表

//单链表的数据结构
function ListNode(x){
    this.val = x;
    this.next = null;
}
//构造一个单链表
let head = new ListNode(0);
let pre = head;
for(let i = 1; i < 5; i++){
    let l1 = new ListNode(i);
    pre.next = l1;
    pre = pre.next;
}


function reverseNode(head){
    let newHead = new ListNode(0);
    let tail = newHead.next;
    //采用头插法
    while(head){
        let p1 = head;
        head = head.next;
        newHead.next = p1;
        p1.next = tail;
        tail = p1;
    }
    return newHead.next;
}

let newHead = reverseNode(head);

//验证链表
pre = newHead;
while(pre ){
    console.log(pre.val);
    pre = pre.next;
}

合并递增序链表

//单链表的数据结构
function ListNode(x){
    this.val = x;
    this.next = null;
}
//构造一个单链表
let head1 = new ListNode(0);
let pre = head1;
for(let i = 1; i < 10; i = i+2){
    let l1 = new ListNode(i);
    pre.next = l1;
    pre = pre.next;
}

//构造一个单链表
let head2 = new ListNode(1);
pre = head2;
for(let i = 2; i < 10; i = i+2){
    let l1 = new ListNode(i);
    pre.next = l1;
    pre = pre.next;
}

//合并链表
function concatList(head1,head2){
    let newHead = new ListNode(0);
    let p = newHead;
    let temp = null;
    while(head1 && head2){
        if(head1.val > head2.val){
            temp = head2;
            head2 = head2.next;
        }else{
            temp = head1;
            head1 = head1.next;
        }
        p.next = temp;
        p = p.next;
    }
    if(head1) p.next = head1;
    if(head2) p.next = head2;
    return newHead.next;
}

let newHead = concatList(head1,head2);

//验证链表
pre = newHead;
while(pre ){
    console.log(pre.val);
    pre = pre.next;
}

前端基础题

html

doctype 的作用

<!DOCTYPE html>
用于声明该html文件解析类型。
一般放置在文件的第一行,浏览器有两种模式:标准模式和怪异模式,doctype会触发标准模式(统一的W3C标准),反之会进入怪异模式(不同浏览器的怪异模式会有不同)

语义化标签的理解

在html5中有很多语义标签,比如<header><nav><main><article><section><footer>,这些标签传达了一些它所包含内容的一些信息,比如<header>中就是网页的头部内容,可能会有Logo 标题等一些重要信息。

- 代码结构清晰:在没有CSS的情况下也可以呈现出易读的结构,让其他开发人员也更加容易理解,减少了差异化。
- 有利于SEO:爬虫会根据这个标签来确定关键字的权重,因此可以和搜索引擎建立良好的沟通,帮助爬虫抓取更多有效的信息。
- 方便其他设备进行解析:例如屏幕阅读器,盲人阅读器,移动设备等。
- 

<meta charset 什么意思

<meta>这个标签会放在<head>中,用于提供和页面有关的元信息。

- 定义页面的字符集编码
<meta charset="utf-8"/>

- 定义搜索引擎的关键词
<meta name="keywords" content="这个一个...的网站"/>

- 定义了一个编译指示指令 ,会替换http请求头中的相应字段
-- 定义内容策略
<meta http-equiv="content-security-policy" content=""/>
-- 定义传送的数据格式
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>

- 移动端布局
<meta name="viewport" content="width=device-width,initial-scale = 1.0"

**内联元素、块级元素、内联元素转换成块级 **

html标签主要分为两类:内联元素和块级元素。

块级元素:<div><h1><p><ul><li>等,宽度和高度可以使用widthheight设置,默认宽度等于父元素宽度,高度被子元素撑开,可以设置padding margin,独占一行。

内联元素:<a><span>等,宽度高度不能设置,被内容撑开,可设置 左右的内外边距,按行顺序排开。

<img> <input>也是内联元素,但是是可替换元素,所以可以设置宽和高。

display:block;//设置成块级元素
display:inline;//设置成内联元素
display:inline-block;//设置成内联块,不独占一行,但是可以设置高度和宽度。

css

盒子模型

盒子模型有两种,可以使用box-sizing设置
(content-box)标准盒模型:width就是content的宽度,不包括padding和border
(border-box)IE盒模型:width包括了content+padding+border

水平居中

1.margin:0 auto;
2.父元素上 display:flex; justify-content:center;
3.position:absolute; margin:auto; left:0; right:0;

垂直居中

1.父元素 display:flex; align-items:center;
2.position:absolute; margin:auto; top:0; bottom:0;
3.position:absolute; top:50%; transform:translateX(-50%);

        .box1{
            width:100%;
            height:500px;
            background-color: aquamarine;
            text-align:center;/*内联元素水平居中*/
            line-height:500px;/*行高=高度 垂直居中*/
        }
        .box2{
            width:100px;
            height:100px;
            background-color:brown;  
            margin: 0 auto;
            display:inline-block;
        }

选择器优先级

1.id选择器(100#container
2.class选择器(10.class
3.标签选择器(1span
4.子元素选择器 div > span
5.兄弟选择器 div + span
6.属性选择器 a[href]
7.伪类 a::hover

伪元素 div::after  div::before

bfc

BFC是块级格式化上下文。开启BFC就会形成一个与外面隔离的渲染区域,BFC区域内的元素不会影响外面的元素。
触发条件:
1.根元素Html就是一个bfc
2.float不为none
3.overflow不为visible
4.position不为static或者relative
5.display:inline-block,table-cell,flex
特性:
1.bfc内部盒子会自上向下排列,且相对的外边距会重叠。
2.bfc内部所有元素都和包含块的左边接触
3.bfc区域不和float区域相叠加
4.bfc不会影响外面的布局
5.bfc计算高度的时候会把浮动元素也计算在内

浮动布局

float:left;会让元素左浮动

1.clear:both;可以清除浮动
2.也可以使用伪元素清除
 div::after{
 	display:block;
    content:'';
    clear:both;
 }
3.开启新的bfc也可以清除浮动的影响
overflow:hidden;

position

position:static默认
position:relative; 相对定位 使用left right top bottom 会让其相对自己的位置进行移动。

position:absolute; 绝对定位 ,使用left right top bottom 相对第一个不是static的父元素进行定位。

position:fixed 相对浏览器窗口进行定位

高度塌陷

子元素浮动,父元素就会高度塌陷,在父元素最后加一个伪元素
div::after{
	display:block;
    content:"";
    clear:both;
}

隐藏元素

display:none;
会从页面中直接消失,引发重绘和重排,不出现在Layout树中,也不会渲染在页面上。

visibility:hidden;
消失于页面中,但占据相应位置,只会触发重绘,不会重排,自身绑定的事件不会被触发。

opacity:0;
不会消失,只是看不到,自身绑定的事件可以被触发。(动画效果可以生效)

flex

父元素 
display:flex;//开启flex布局
flex-wrap:wrap;//自动换行
flex-direction:row;//主轴方向是横向  column
justify-content:center;//项目水平居中
align-items:center;//项目垂直居中

关于对齐方式:
 flex-start 是沿着主轴开始位置对齐
 flex-end 是沿着主轴结束位置对齐
 center:居中
 space-between 两边靠着边缘  中间距离相等
 space-around 两边距离是中间距离的一半
 
子元素

felx:主轴方向 增长的量 缩小的量 基准
flex: 0 0 300px; //这个主元素主轴方向是300px;
flex: 1 0 0;//沿着主轴方向撑满剩余的长度

order:-1//越小越排在前面

align-self:stretch//单个子元素伸展

单位 blog.csdn.net/VickyTsai/a…

pt 
px :CSS像素
vm :屏幕的宽度
vh :屏幕的高度 100vh表示撑满高度
em :
rem: 以根的font-size为基准 进行放大缩小

z-index

https://juejin.cn/post/6942357662482825223
HtmL会默认开启一个层叠上下文。

使用z-index不为auto时会自动开启一个新的层叠上下文,在这个层叠上下文中有多个层叠等级,从低到高分别是:
1.当前层叠上下文的背景和边框
2.z-index为负数的元素,同时会开启一个新的重叠上下文
3.块级元素
4.float元素
5.内联元素
6.z-index:0 有定位
7.z-index为正数,且有定位Position

在页面显示的时候,同一个父级层叠上下文的层叠级越高,那它就越接近人这面

但是在比较不同父级层叠上下文的元素的时候,会以父级上下文决定。

轮播图 圣杯布局

双飞翼布局

两栏布局

<style>
.container{
    width:100vw;
    height:100vh;
    display:flex;
    flex-direction:row;
}
.left{
    flex:0 0 300px;
    height:100%;
}
.right{
    flex:1;
    height:100%;
}
</style>
<body>
	<div class="container">
    	<div class="left"></div>
        <div class="right"></div>
    </div>
</body>

**移动端适配 **

@media(max-width:768px){
	
}

css 解析顺序 css放在头部和尾部有什么差别

js

**DOM标签有哪些,分类,区别 **

DOM节点分类三种:元素节点,文本节点,属性节点,其中文本节点和属性节点是元素节点的子节点。

dom操作有哪些

查找元素:
document.getElementById('id');//通过id进行查找,返回唯一的节点
document.getElementsByClassName('class');//返回值是nodelist类型
document.getElementsByTagName('div');//返回nodelist

document.getElementsByName('name');//通过Name属性获得

document.querySelector('ul');//通过接受一个一个CSS选择符,返回第一个匹配的dom对象。

document.querySelectorAll('li');//返回一个nodelist


querySelector和getElementBy...的区别在于,前者获得的是一个静态集合,后者获得的是一个动态集合

根据层次查找节点:
父元素.childNodes;//获取父元素的所有子节点(包括回车和子元素节点)
父元素.children;//获取当前元素的所有子元素节点

父元素.firstChild;//获取第一个子节点
父元素.firstElementChild;//获取第一个子元素节点

父.lastChild;
父.lastELementChild;

子.parentNode;//获取父节点

当前节点.previousSibling;//前一个兄弟节点
.previousElementSibling

创建节点:
document.createElement('p');//创建元素节点
document.createTextNode('文字');//创建文本节点

添加节点:
父元素.appendChild(新的元素);

删除节点:
父元素.removeChild(旧的子元素);

替换节点:
父元素:replaceChild(新元素,旧元素);

插入节点:
父元素.insertBefore(新元素,旧元素)//在旧元素之前插入新元素

复制节点:
旧元素.cloneNode(true);//深度复制
false只复制当前节点,不复制子节点。

获取属性节点和设置属性节点
元素节点.getAttribute('class');
元素节点.setAttribute('class','btn1');
或者 
元素节点.style="background-color:red; ..."
元素节点.style.color =   style.backgroundColor = (小驼峰)
元素节点.onclick = 
元素节点.className = 

设置文本节点
元素节点.innerHTML='<h1>111</h1>';
元素节点.innerText = '你好';

js基本类型,区别

js基本数据类型有:Number,String,Boolean,null,undefined,Symbol,Object.
其中Object属于引用类型的总称,又可以分为Array,function,Date,RegExp,一般对象。

基本数据类型是存储在栈中的,变量名和变量值以键值对的形式存储,所以通过变量名可以直接获取到变量值。引用类型的数据值存在堆中,栈中存储变量名和堆中的地址,所有在使用变量名的时候取到的只是值的地址,而不是变量值本身。

区分变量数据类型的方法。
1.typeof 可以区分 number string boolean symbol undefined function 会返回相应的字符串,其他的都会返回一个objectNaN表示非数字
typeof NaN === 'number'

2.A instanceof B 可以区分 当前对象是否是操作符右边的实例(A.__proto__ === B.prototype)
[] instanceof Array 返回true
new Date() instanceof Date;//true
new Person() instanceof Person;
但是根据原型链的作用,当instanceof Object的时候也是true()

3.Object.prototype.toString.call()最全面,但是比较繁琐
Object.prototype.toString.call('') ;   // [object String]
Object.prototype.toString.call(1) ;    // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
Object.prototype.toString.call(new Error()) ; // [object Error]
Object.prototype.toString.call(document) ; // [object HTMLDocument]
Object.prototype.toString.call(window) ; //[object Window]


怎么判断一个数组是不是Array类型?
1. c istanceof Array;//true
2. Array.isArray(c);//true
3. Obeject.prototype.toString(c);//[Obejct Array];

js 脚本 defer 和 async 的区别

1.异步 js 脚本在执行的时候主线程会停止吗?
defer不会
async2.js是单线程吗

单线程的特点是同一个时刻只能执行一个任务因为一些和用户的互动和操作DOM等相关操作决定了js要使用单线程,否则多线程会带来同步问题如果一个线程在修改DOM,另一个线程要删除DOM,就需要对这个共享资源进行加锁,使执行任务非常繁琐

3.JS是解释性语言和编译性语言区别是什么

JavaScript 代码需要在机器(node 或者浏览器)上安装一个工具(JS 引擎)才能执行这是解释型语言需要的编译型语言程序能够自由地直接运行

变量提升不是代码修改在这个过程中没有生成中间代码变量提升只是 JS 解释器处理事情的方式

JIT 即时编译 是唯一一点我们可以对 JavaScript 是否是一个解释型语言提出疑问的理由但是 JIT 不是完整的编译器,它在执行前进行编译而且 JIT 只是 MozillaGoogle 的开发人员为了提升浏览器性能才引入的JavaScriptTC39 从来没有强制要求使用 JIT

因此,虽然 JavaScript 执行时像是在编译或者像是一种编译和解释的混合,我仍然认为 JavaScript 是一个解释型语言或者是一个今天很多人说的混合型语言,而不是编译型语言

作用域 执行性函数的作用域

匿名函数:
自执行函数,即定义和调用合为一体。我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在执行完后很快就会被释放,关键是这种机制不会污染全局对象。
下面是 JavaScript 处理声明语句的过程:

一旦 V8 引擎进入一个执行具体代码的执行上下文(函数),它就对代码进行词法分析或者分词。这意味着代码将被分割成像foo = 10这样的原子符号(atomic token)。
在对当前的整个作用域分析完成后,引擎将 token 解析翻译成一个AST(抽象语法树)。
引擎每次遇到声明语句,就会把声明传到作用域(scope)中创建一个绑定。每次声明都会为变量分配内存。只是分配内存,并不会修改源代码将变量声明语句提升。正如你所知道的,在JS中分配内存意味着将变量默认设为undefined。
在这之后,引擎每一次遇到赋值或者取值,都会通过作用域(scope)查找绑定。如果在当前作用域中没有查找到就接着向上级作用域查找直到找到为止。
接着引擎生成 CPU 可以执行的机器码。
最后, 代码执行完毕。
所以变量提升不过是执行上下文的小把戏,而不是许多网站描述的源代码修改。在执行任何语句之前,解释器就要从创建执行上下文后已经存在的作用域(scope)中找到变量的值。

js 的 this 指向问题 谈了this和改变指向性

1.普通函数中的this
2.对象方法中的this
3.构造函数中的this
4.箭头函数中的this

改变this
fn.call(this,一些参数)
apply
bind

对象的深拷贝和浅拷贝

1.数据类型-》存放的地方-》浅拷贝只拷贝一层,深拷贝是全部复制
2.浅拷贝的实现方法
    直接赋值
    let arr1 = [1,2,3];
    let arr2 = arr1;
    
    Object.assign(target,source);
    
    扩展运算符
    let arr3  = [...arr1];
    let obj1 = {...obj2};
    
    数组的方法 slice()  concat()
 深拷贝
     JSON.parse(JSON.stringify())
     拷贝的对象中如果有函数,undefined,symbol,当使用过JSON.stringify()进行处理之后,都会消失。
     
    

闭包了解吗?写一个函数第一次call他时打印1 第二次2 第三次3

闭包函数和词法作用域组成的一个特殊区域。
是调用函数的时候返回一个函数,通过这个函数可以访问局部变量,也可以实现局部变量的保存。



function foo(){
	let a = 1;
	return function print(){
    	console.log(a);
        a++;
    }
}

let print2 = foo();
print2();//1
print2();//2
print2();//3

垃圾回收

1.引用计数法
当前空间还有变量访问或者引用的时候就不会清除,只有没有变量引用了再去清除
当时会有循环引用的问题,就会造成内存的泄露。

var obj1 = {a:obj2};
var obj2 = {a:obj1};


2.标记清除法

先简单讲一下 JS 中引用垃圾回收策略大体是什么样的一个原理,当一个变量被赋予一个引用类型的值时,这个引用类型的值的引用计数加 1。就像是代码中的 obj1 这个变量被赋予了 obj1 这个对象的地址,obj1 这个变量就指向了这个 obj1(右上)这个对象,obj1(右上)的引用计数就会加1.当变量 obj1的值不再是 obj1(右上)这个对象的地址时,obj1(右上)这个对象的引用计数就会减1.当这个 obj1(右上)对象的引用计数变成 0 后,垃圾收集器就会将其回收,因为此时没有变量指向你,也就没办法使用你了。
看似很合理的垃圾回收策略为什么会有问题呢?
就是上面讲到的循环引用导致的,下面来分析一下。当 obj1 这个变量执行 obj1 这个对象时,obj1 这个对象的引用计数会加 1,此时引用计数值为 1,接下来 obj2 的 b 属性又指向了 obj1 这个对象,所以此时 obj1 这个对象的引用计数为 2。同理 obj2 这个对象的引用计数也为2.
当代码执行完后,会将变量 obj1 和 obj2 赋值为 null,但是此时 obj1 和 obj2 这两个对象的引用计数都为1,并不为 0,所以并不会进行垃圾回收,但是这两个对象已经没有作用了,在函数外部也不可能使用到它们,所以这就造成了内存泄露。

在现在广泛采用的标记清除回收策略中就不会出现上面的问题,标记清除回收策略的大致流程是这样的,最开始的时候将所有的变量加上标记,当执行 cycularReference 函数的时候会将函数内部的变量这些标记清除,在函数执行完后再加上标记。

这些被清除标记又被加上标记的变量就被视为将要删除的变量,原因是这些函数中的变量已经无法被访问到了。像上述代码中的 obj1 和 obj2 这两个变量在刚开始时有标记,进入函数后被清除标记,然后函数执行完后又被加上标记被视为将要清除的变量,因此不会出现引用计数中出现的问题,因为标记清除并不会关心引用的次数是多少。

arguments是什么与数组区别

剩余参数和arguments对象之间的区别主要有三个:

剩余参数只包含那些没有对应形参的实参,而arguments对象包含了传给函数的所有实参。
arguments对象不是一个真正的数组,而剩余参数是真正的Array实例,也就是说你能够在它上面直接使用所有的数组方法,比如sort,map,forEach或pop。而arguments需要借用call来实现,比如[].slice.call(arguments)。
arguments对象还有一些附加的属性(如callee属性)。
剩余语法和展开运算符看起来很相似,然而从功能上来说,是完全相反的。
剩余语法(Rest syntax) 看起来和展开语法完全相同,不同点在于, 剩余参数用于解构数组和对象。从某种意义上说,剩余语法与展开语法是相反的:展开语法将数组展开为其中的各个元素,而剩余语法则是将多个元素收集起来并“凝聚”为单个元素


arguments
函数的实际参数会被保存在一个类数组对象arguments中。

类数组(ArrayLike)对象具备一个非负的length属性,并且可以通过从0开始的索引去访问元素,让人看起来觉得就像是数组,比如NodeList,但是类数组默认没有数组的那些内置方法,比如push, pop, forEach, map。

我们可以试试,随便找一个网站,在控制台输入:

var linkList = document.querySelectorAll('a')
会得到一个NodeList,我们也可以通过数字下标去访问其中的元素,比如linkList[0]。

但是NodeList不是数组,它是类数组。

Array.isArray(linkList); // false
回到主题,arguments也是类数组,arguments的length由实参的数量决定,而不是由形参的数量决定。

function add(a, b) {
  console.log(arguments.length);
  return a + b;
}
add(1, 2, 3, 4);
// 这里打印的是4,而不是2
arguments也是一个和严格模式有关联的对象。

在非严格模式下,arguments里的元素和函数参数都是指向同一个值的引用,对arguments的修改,会直接影响函数参数。
function test(obj) {
  arguments[0] = '传入的实参是一个对象,但是被我变成字符串了'
  console.log(obj)
}
test({name: 'jack'})
// 这里打印的是字符串,而不是对象

在严格模式下,arguments是函数参数的副本,对arguments的修改不会影响函数参数。但是arguments不能重新被赋值(可以修改)

Array.from()干嘛用的,类数组大概有哪些?

事件循环

在浏览器引擎执行js代码的时候,其实是单线程执行的,会有两个队列,宏任务队列和微任务队列。

宏任务包括:script里的代码,setTimeout setInterval,I/O请求(ajax请求)
微任务包括:promise.then  process.nextTick MutationObserver

执行的具体过程是:
首先从宏任务队列中取出一个宏任务,放在执行栈中开始执行,当遇到微任务的时候就存放到当前宏任务的微任务队列,当遇到宏任务的时候就存放到宏任务队列,当钱的宏任务执行完之后,开始执行微任务队列的所有任务,然后执行requestAnimationFrame回调,事件处理,DOM操作,布局绘制。然后重新取出一个宏任务。

js 如何设置异步

async/await 是一个语法糖,你知道 await 后面怎么用吗?

promise系列(这里俺不那么熟,没有实现过,于是小姐姐问了个代码题,和eventloop挂钩的)

promise有哪几种转移状态

有没有用过promise

promise封装异步打印操作实现

代码题,promise实现延时(最开始说了思路,然后手动实现)

generator,状态机的理解,应用

我要想在低版本浏览器用Promise,怎么办,你项目怎么兼容的,答babel,那不用babel呢,怎么实现,答曰settimeout模拟?不知道对不对

发布订阅模式 只包含emit on 有没有return的区别

发布订阅模式是一种设计模式,通过一个事件中心来让不同方法订阅事件,当事件发生的时候就触发相应的回调函数。

订阅者在订阅事件的时候,只关注事件本身,而不关心谁会发布这个事件;发布者在发布事件的时候,只关注事件本身,而不关心谁订阅了这个事件。

1.nodejs中的Events
let events = require('events');
const pbb = new events.EventEmitter();//创建发布订阅对象。

ajax("./test.json",function(data){
	pbb.emit('success1',data);//发布事件
})

pbb.on('success1',function(data){//订阅事件
	console.log(data);
})

2.自定义发布订阅对象
class PubSub{
	constructor(){
    	this.events = {};
    }
    //发布事件
    emit(eventName,data){
    	if(this.events[eventName]){
        	this.events[eventName].forEach((item)=>{
            item.apply(this,data);
            })
        }
    }
    //订阅事件
    on(eventName,callback){
    	if(this.events[eventName]){
        	this.events[eventName].push(callback);
        }else{
        	this.events[eventName] = [callback];
        }
    }
    
}

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
观察者模式我们可能比较熟悉的场景就是响应式数据,如 Vue 的响应式、Mbox 的响应式。

目标
观察者

/**
 * 观察监听一个对象成员的变化
 * @param {Object} obj 观察的对象
 * @param {String} targetVariable 观察的对象成员
 * @param {Function} callback 目标变化触发的回调
 */
function observer(obj, targetVariable, callback) {
  if (!obj.data) {
    obj.data = {}
  }
  Object.defineProperty(obj, targetVariable, {
    get() {
      return this.data[targetVariable]
    },
    set(val) {
      this.data[targetVariable] = val
      // 目标主动通知观察者
      callback && callback(val)
    },
  })
  if (obj.data[targetVariable]) {
    callback && callback(obj.data[targetVariable])
  }
}

事件流,事件捕获和事件冒泡

DOM2的事件流分为三个阶段:
事件捕获阶段,事件目标阶段,事件冒泡阶段。
当某个节点元素发生事件的时候,会从window节点逐步下降到发生事件的节点,这个过程就是事件捕获阶段,当到达该节点后如果有相应的回调函数就会执行,之后又会逐步上升至window节点,这就是事件冒泡阶段。

element.addEventListener('onclick',fn,bool);当booltrue的时候说明在捕获阶段触发回调函数,否则在冒泡阶段触发。
event.stopPropagation()可以取消冒泡。

e.target是被触发的元素,e.currentTarget是事件监听的元素。

事件委托就是利用了事件流的传播。
给父元素绑定事件,这样当触发子元素的时候,就都可以监听到,并且相应同一个方法。

ES6你都了解哪些

let const:
这个问题首先要提到作用域和作用域链,作用域也叫做词法环境,是指变量或者函数可以使用的范围。
当在本作用域内找不到要使用的变量的时候就会向上一级的作用域查找,这就是形成了作用域链。

声明一个变量的时候可以用var let const,
其中letconst 是块作用域,而且不会变量提升,所以存在暂时性死区。

var可以进行变量提升,变量提升的意思就是在一个函数内部,首先会把其中所有var声明的变量提升到函数顶部,只是提升声明(在词法环境中注册),但是并没有赋值,所有在赋值前使用该变量的话不会报错,但是其值是undefined。

let而且会绑定它所在的块作用域,在一个块作用域里不能声明两个变量名相同的变量
const声明一个只读的常量。一旦声明,它所存的地址就不能改变,对于常量来说,就是这个常量不能改变。对于其他引用类型来说,其值还是可以变得。

同时constlet一样是块级作用域,有暂存性死区,不能再同一个块里声明两个名字相同的变量。

类这个概念怎么实现的

ES5function People(name,age){
	this.name = name;
    this.age = age;
}
People.prototype.sayName = function(){
	console.log(this.name);
}


ES6class People{
	constructor(name,age){
    	this.name = name;
        this.age = age;
    }
    sayName(){
    	console.log(this.name);
    }
}

区别在于:
1.ES5的构造函数本质上还是函数,所以可以直接调用,返回undefined
ES6class不能直接调用会抛出错误。

2.ES5的实例对象可以遍历所有属性,ES6只能遍历对象上的属性,不能遍历原型上的。for(let k in p1)

3.ES6static静态方法只能通过类调用,不会出现在实例上;另外如果静态方法包含 this 关键字,这个 this 指的是类,而不是实例。static声明的静态属性和方法都可以被子类继承。

js继承方式

1.原型继承
子类的prototype指向父类的一个实例对象,但是这样所有子类实例的父类属性都是共享的。

function Parent(){
	this.name = 'tom';
}

function Child(age){
	this.age = age;
}
Child.prototype = new Parent();

2.组合继承
这样做每个子对象的父对象都不一样,而且可以共享父对象的方法。缺点就是Parent调用了两次。

function Child(name,age){
	Parent.call(this,name);
    this.age = age;
}
Child.prototype = new Parent();

3.寄生组合继承
解决了属性重复的问题
function Child(name,age){
	Parent.call(this,name);
    this.age  = age;
}
Child.prototype = Parent.prototype;
/*
	或者
    for(let k in Parent.prototype){
    	Child.prototype[k] = Parent.prototype[k];
    }
*/
Child.prototype.constructor = Child;

4.ES6的extend

class Child extends Parent{
	constructor(name,age){
    	super(name);
        this.age = age;
    }
}

5.多继承的实现
function P1(){}

function P2(){}

function Child(){
	P1.call(this);
    P2.call(this);
}

for(let k in P1.prototype){
	Child.prototype[k] = P1.prototype[k];
}

for(let k in P2.prototype){
	Child.prototype[k] = P2.prototype[k];
}

Child.prototype.constructor = Child;

**prototype是个什么概念,有什么优点 **

原型和原型链是存在于对象上的概念。每一个构造函数都有一个prototype属性指向它的原型,原型也有一个constructor属性指向构造函数,通过构造函数构造出的实例可以通过__proto__属性访问到原型。

在这个实例对象调用它的方法或者属性的时候,如果不存在于它的空间中,就会通过__proto__这个指针向上找,直到找到null为止,这个过程访问的就是原型链的过程。

原型链的最后都是null。

在prototype上添加方法可以使得所有实例对象共享此方法,减少了内存开销

节流防抖 节流是什么 函数节流实现

节流和防抖可以实现性能优化。
节流throttle是指在一段时间内只响应一次事件。


防抖debounce是指两次事件触发的时间要大于一个固定的时间才可以被执行。

循环的方法 ,各个方法的区别

遍历数组forEach  map reduce  filter
遍历对象 let key in obj   let key of obj

for in是对数组 或者对象遍历key值,
遍历对象的可枚举属性,而且还会遍历原型上的属性,是没有特定顺序的。

for of 是es6新加的遍历工具,可以遍历数组,类数组对象,map,set等具有迭代器的对象,迭代器是一个接口可以对不同的数据提供统一的访问接口

forEach本质上也是数组的循环,和for循环语句差不多,但是语法简单了.并且forEach不会改变原数组,不会返回新数组.
mapfilter都不会改变原数组,都会返回新数组.也正是因为能返回一个新数组,所以可以链式调用.不同之处在于map是对原数组进行加工,进行一对一的关系映射,而filter则是对原数组过滤,保留符合要求的数组成员.


for...in则会遍历对象的可枚举属性,包括原型对象上的属性.主要是为遍历对象而设计,不适用于遍历数组.

for...of遍历具有Iterator迭代器的对象,避开了for...in循环的所有缺陷,并且可以正确响应break,continuereturn语句等.
for await...of 将异步循环变成同步循环
every和some有点类似于断言的感觉,返回布尔类型
Object.keys()返回一个给定对象的所有可枚举属性的字符串数组

柯里化 代码题柯里化sum(1)(2)(3)

function add(a,b,c){
    return a + b + c;
}

function curryAdd(fn){
    let len = fn.length;//获取fn的参数的长度
    let args = [];
    return function add(){
         args.push(...arguments);
         if(args.length === len){
             return fn.apply(null,args);
         }else{
             return add;
         }
    }
}
let newAdd = curryAdd(add);
console.log(newAdd(1,2)(3));

前端模块化,ES6怎么用的,node怎么用的,ES5require呢(node直接说不了解。)

模块化就是一个函数或者文件引用另一个文件,把功能分拆成不同的函数和文件,在需要的时候引入

(ES5)node的方法 :commonjs规范 同步加载  运行时加载
输出的是值的拷贝
let moudleA = require('./indexA');
module.exports = {
    moudleA
}

ES6的方法  异步加载  编译器输出接口
输出的是值的引用
import moudle from './indexA';
export moudle

浏览器的同源政策

浏览器本省的同源政策是:不能从不同源的url中请求ajax

浏览器如何跨域 get 和 post 的区别 说说你知道的状态码 浏览器渲染过程   我自己给自己挖坑提到了堆、栈----à堆和栈是什么?          
浏览器兼容性是怎么回事

手写一个Set class, 支持add() remove() js怎么动态生成function,new function()参数 代码题数组去重,set和indexOf===lastIndexOf 代码题 cssStyleSheet -》 css_style_sheet,开始想的是遍历,面试官提醒下replace传函数,改过来了

操作系统

操作系统里的死锁产生条件有啥,怎么解决,银行家算法讲讲 死锁是什么

操作系统,分页和分段是什么,node用过吗后台垃圾回收没问题为什么内存一直在增高(内存分布碎片问题) **线程和进程是什么 举例说明 **

简单介绍一下线程/进程;资源共享问题;进程之间如何通信;线程竞争问题;资源锁介绍一下 进程和线程,进程间通信

计算机网络

网络第三层作用

缓存 http缓存机制发展历史 http缓存 缓存有哪些 协商缓存说一下

强缓存协商缓存具体例子举一下,协商缓存status code(304) 浏览器缓存 强缓存 协商缓存? 强缓存用到了什么HTTP header

cookie细节 cookie,localstorage,sessionStorage区别 token 会不会过期 原理什么玩意

http请求头包含些什么 7.https和http(谈了区别,各自连接方式及相关拓展) 8.http2.0(结合http1.0谈了区别)

HTTP状态码有哪些 http的请求头。

应用层又那些协议? TCP UDP 区别什么 TCP三次握手及每次传送的内容?

get,post请求的区别,301,304代码 get和post,有听说过幂等性吗 get和post的区别。

安全: MD5加密知道吗? 对称加密都有什么? https ssl过程对称和非对称加密 xss攻击,csrf攻击以及如何预防 https加密过程以及为什么这样做 中间人攻击了解吗 DNS域名解析 、 DNS服务器原理 DNS TCP重定向(返回的地址在location) 从输入URL到页面展示 浏览器渲染过程 重绘回流 减少重绘回流的方法

简单介绍一下跨域以及其方法

页面生命周期 zhuanlan.zhihu.com/p/111316766 cdn有用过吗,没用过。。那说下原理呗大概怎么样子的

负载均衡、负载均衡数据一致性、服务器跟踪用户session、 session共享

前端性能优化

数据结构

队列有什么特点

数组和链表的区别?哪种情况用数组哪种情况用链表。

java里面的ArrayList的自动扩容你了解过吗?

学过java面向对象对吧,说一下java文件如何执行的(字节码 - jvm - 后面就不知道了)

C/C++程序编译流程 详细介绍

git

项目中工程化内容(gitlab git gitlab-ci)

Git的各类操作 git命令 git rebase 和 git merge 的区别 git fetch 和 git pull git 如何对比代码

编译原理

学过编译原理对吧,编译原理流程说一下,词法分析语法分析之类的,词法分析方法说一下自顶向下方法或者其他的你说一个名字出来(忘记了。。)

数据库

sql语句有哪些?表联合是什么?你知道数据库原理吗?

设计模式

说几个你了解的设计模式?在哪用到过或者看到过。

观察者模式

7、 发布/订阅和观察者模式的区别?

两种模式都存在订阅者和发布者(具体观察者可认为是订阅者、具体目标可认为是发布者),但是观察者模式是由具体目标调度的,而发布/订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会。

8、 代理模式 ?

代理模式是一种对象结构型模式,在该模式中引入了一个代理对象,在客户端和目标访问对象之间起到中介的作用。代理对象可以屏蔽或删除客户不想访问的内容和服务,也可以根据客户需求增加新的内容和服务。

linux

Linx修改文件权限的指令是什么。怎么查看端口是否被占用。

webpack

我也不熟,就问了个plugin,我简单回答了下,估计看我不熟也没细问了

打包工具,webpack使用 webpack优化,分包,压缩等 webpack分包 tree shaking/code spliting webpack 的 css 加载器和你用的 stylus 的 css 有什么不同 优化项目体积,如何测试项目性能

react

zhuanlan.zhihu.com/p/304213203 JSX jsx 是一种js对象,写法和普通的html很像。

react通过jsx描述视图,在16版本中是通过Babel-loader转译成React.createElement的形式,这个函数可以把jsx对象转化为虚拟dom对象,然后在render里面通过diff算法对比新旧虚拟dom对象,从而做出最终的dom操作。

在16版本中一定要引入react就是因为jsx的转译会调用react.createElement。

但是在17版本中,对jsx进行了优化,在package中引入了两个新的入口,这些入口被Babel和typescript等编译器使用,自动转化jsx对象为虚拟dom对象。

import {jsx as _jsx} from 'react/jsx-runtime';

function App() {
  return _jsx('h1', { children: 'Hello world' });
}

有几种类型的组件

类组件和函数式组件

函数组件是一个纯函数,它接收一个props对象返回一个react元素。而类组件需要去继承React.Component并且创建render函数返回react元素

-类组件中可以使用生命周期函数,类组件使用的时候要实例化

-函数组件,可以接收唯一带有数据的props(代表属性)对象,并返回一个React元素,可以使用Hooks,但是不能使用生命周期函数(因为生命周期事React.Component类实现的方法,而函数式组件没有继承这个类),函数组件直接执行函数取返回结果即可。

判断是否是一个类组件:AComponent.prototype instanceof React.Component

函数组件的优点:

在react16.8版本中添加了hooks,使得我们可以在函数组件中使用useState钩子去管理state,使用useEffect钩子去使用生命周期函数。因此,2、3两点就不是它们的区别点。从这个改版中我们可以看出作者更加看重函数组件,而且react团队曾提及到在react之后的版本将会对函数组件的性能方面进行提升。

生命周期 旧的生命周期(16)

新的生命周期(17)

即将废弃三个钩子 提出两个新的钩子 从props处得到一个新的state,而且以这个为主不会被本组件更新(很少用)

16的生命周期

  • 初始阶段 constructor构造函数、getDefaultProps props默认值、getInitialState state默认值

  • 挂载阶段 getDerivedStateFromProps:组件每次被 rerender的时候,包括在组件构建之后(虚拟 dom之后,实际 dom挂载之前),每次获取新的 props或 state之后;每次接收新的props之后都会返回一个对象作为新的 state,返回null则说明不需要更新 state;配合 componentDidUpdate,可以覆盖 componentWillReceiveProps的所有用法

render

componentDidMount

  • 更新阶段

  • 卸载阶段

  • 错误处理 hooks

setState是异步的吗

调用setState之后发生了什么

1.在 setState 的时候,React 会为当前节点创建一个 updateQueue 的更新列队。

2.然后会触发 reconciliation 过程,在这个过程中,会使用名为 Fiber 的调度算法,开始生成新的 Fiber 树, Fiber 算法的最大特点是可以做到异步可中断的执行。

3.然后 React Scheduler 会根据优先级高低,先执行优先级高的节点,具体是执行 doWork 方法。

4.在 doWork 方法中,React 会执行一遍 updateQueue 中的方法,以获得新的节点。然后对比新旧节点,为老节点打上 更新、插入、替换 等 Tag。

5.当前节点 doWork 完成后,会执行 performUnitOfWork 方法获得新节点,然后再重复上面的过程。

6.当所有节点都 doWork 完成后,会触发 commitRoot 方法,React 进入 commit 阶段。

7.在 commit 阶段中,React 会根据前面为各个节点打的 Tag,一次性更新整个 dom 元素。

多次触发setState,会render几次

react中如何对state中的数据进行修改?setState为什么是一个异步的?

为什么建议传递给 setState的参数是一个callback而不是一个对象?

虚拟DOM 虚拟DOM是对真实dom结构的抽象,本质上也是一个js对象。react虚拟DOM的结构是:

const vNode = {
  key: null,
  props: {
    children: [
      {type: 'span', ...},
      {type: 'span', ...}
    ],
    className: "red",
    onClick: ()=>{}
  },
  ref: null,
  type: "div",
  ...
}

其中type的类型有

  • classComponent
  • function Component
  • 原生标签
  • 文本节点
  • react内置标签

vdom就是通过props的children形成vdom树。

为什么要使用虚拟DOM

1.可以减少DOM操作,频繁的DOM操作会造成浏览器的回流和重绘,所以抽象出虚拟DOM结构,通过比对新旧虚拟DOM就可以得出一个最小的更新序列,最大程度上复用旧DOM。
2.使用虚拟DOM,可以更好的跨平台,因为虚拟dom本质上就是一个js对象,这个对象的可移植性更高,在React Native ReactVR中都可以使用。

虚拟DOM的缺点

1.首次渲染需要也需要进行一次VDOM的计算,所以比innerHTML插入要慢。
2.更适合虚拟DOM的频繁大量更改,但是单一的更改虚拟DOM会花费事件进行计算工作。

虚拟DOM的渲染过程

1.首先把jsx对象转化成虚拟DOM,这一部分在16版本中是通过react.reateElement实现的,在新版本中通过引入新的入口进行实现。

2.虚拟DOM通过ReactDOM.render(vdom,container)(如果是第一次)生成真实DOM,并且插入到container上。

3.在render里面会有一个协调过程,具体是diff算法,会比较新的Vnode和旧的Vnode,以此得出应该更新哪一部分。

4.Render过程:把变化更新到真实DOM上(patch)

key

keys是用来追踪列表中哪些元素被修改、添加、消除的辅助标志。 key 标记唯一性 用来区分老的和新的dom节点

render(){
	return(
    	<ul>
        {this.state.list.map((item,key)=>{
            return <li key={key}>{item}</li>
            })}
        </ul>
    )
}

key和组件创建起一个对应关系,如果key相同,组件属性变化,则react只更新对应的属性,没有变化就不更新。如果key不同,则react先销毁该组件,然后重新创建该组件。

diff算法

传统 diff 算法的时间复杂度是 O(n^3),这在前端 render 中是不可接受的。为了降低时间复杂度,react 的 diff 算法做了一些妥协,放弃了最优解,最终将时间复杂度降低到了 O(n)。

那么 react diff 算法做了哪些妥协呢?,参考如下:

1、tree diff:只对比同一层的 dom 节点,忽略 dom 节点的跨层级移动

如下图,react 只会对相同颜色方框内的 DOM 节点进行比较,即同一个父节点下的所有子节点。当发现节点不存在时,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。

这样只需要对树进行一次遍历,便能完成整个 DOM 树的比较。

image.png

这就意味着,如果 dom 节点发生了跨层级移动,react 会删除旧的节点,生成新的节点,而不会复用。

2、component diff:如果不是同一类型的组件,会删除旧的组件,创建新的组件

image.png

3、element diff:对于同一层级的一组子节点,需要通过唯一 id 进行来区分

如果没有 id 来进行区分,一旦有插入动作,会导致插入位置之后的列表全部重新渲染。

这也是为什么渲染列表时为什么要使用唯一的 key。

组件之间通信方式有哪些

1、 通过 props 传递

子组件向父组件通信 1、 主动调用通过 props 传过来的方法,并将想要传递的信息,作为参数,传递到父组件的作用域中

跨层级通信 1、 使用 react 自带的 Context 进行通信,createContext 创建上下文, useContext 使用上下文。

参考下面代码:

import React, { createContext, useContext } from 'react';

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

export default App;

2、使用 Redux 或者 Mobx 等状态管理库

3、使用订阅发布模式

相关链接: React Docs

12、React 父组件如何调用子组件中的方法? 1、如果是在方法组件中调用子组件(>= react@16.8),可以使用 useRef 和 useImperativeHandle:

const { forwardRef, useRef, useImperativeHandle } = React;

const Child = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    getAlert() {
      alert("getAlert from Child");
    }
  }));
  return <h1>Hi</h1>;
});

const Parent = () => {
  const childRef = useRef();
  return (
    <div>
      <Child ref={childRef} />
      <button onClick={() => childRef.current.getAlert()}>Click</button>
    </div>
  );
};

2、如果是在类组件中调用子组件(>= react@16.4),可以使用 createRef:

const { Component } = React;

class Parent extends Component {
  constructor(props) {
    super(props);
    this.child = React.createRef();
  }

  onClick = () => {
    this.child.current.getAlert();
  };

  render() {
    return (
      <div>
        <Child ref={this.child} />
        <button onClick={this.onClick}>Click</button>
      </div>
    );
  }
}

class Child extends Component {
  getAlert() {
    alert('getAlert from Child');
  }

  render() {
    return <h1>Hello</h1>;
  }
}

refs 是什么?

ref可以在react中获取原生dom元素 refs是一个js对象,里面收集了当前组件实例的所有ref。

1.字符串形式 this.refs.input1
<input ref="input1" />

2.回调函数形式 this.input1
<input ref={currentNode => this.input1 = currentNode} />
但是这种内联函数的形式会让组件更新的时候使回调被执行两次

3.React.createRef()
input1 = React.createRef();
this.input1.current就是该元素
<input ref={this.input1} />

受控组件(Controlled Component)与非受控组件(Uncontrolled Component)的区别

React 的核心组成之一就是能够维持内部状态的自治组件,不过当我们引入原生的HTML表单元素时(input,select,textarea 等),我们是否应该将所有的数据托管到 React 组件中还是将其仍然保留在 DOM 元素中呢?这个问题的答案就是受控组件与非受控组件的定义分割。

受控组件(Controlled Component)代指那些交由 React 控制并且所有的表单数据统一存放的组件

而非受控组件(Uncontrolled Component)则是由DOM存放表单数据,并非存放在 React 组件中。我们可以使用 refs 来操控DOM元素

React为什么自己定义一套事件体系呢,与浏览器原生事件体系有什么关系?

react的事件使用:
1.通过onXxx的属性指定事件处理函数
<input onClick={this.handleClick}/>
这是合成事件,不是dom的原生事件,合成事件通过事件委托方式,把事件委托给组件最外层的元素。

原因:为了更好的兼容性。

2.通过event.target得到发生事件的DOM元素对象

为了解决跨浏览器兼容性问题,React 会将浏览器原生事件(Browser Native Event)封装为合成事件(SyntheticEvent)传入设置的事件处理器中。这里的合成事件提供了与原生事件相同的接口,不过它们屏蔽了底层浏览器的细节差异,保证了行为的一致性。另外有意思的是,React 并没有直接将事件附着到子元素上,而是以单一事件监听器的方式将所有的事件发送到顶层进行处理。这样 React 在更新 DOM 的时候就不需要考虑如何去处理附着在 DOM 上的事件监听器,最终达到优化性能的目的。

fiber React Fiber 是一种基于浏览器的单线程调度算法。

React Fiber 用类似 requestIdleCallback 的机制来做异步 diff。但是之前数据结构不支持这样的实现异步 diff,于是 React 实现了一个类似链表的数据结构,将原来的 递归diff 变成了现在的 遍历diff,这样就能做到异步可更新了。

hooks juejin.cn/post/686774…

redux和Mobx的区别 segmentfault.com/a/119000001… Redux 是一个为 JavaScript 应用设计的,可预测的状态容器。

它解决了如下问题:

跨层级组件之间的数据传递变得很容易 所有对状态的改变都需要 dispatch,使得整个数据的改变可追踪,方便排查问题。 但是它也有缺点:

概念偏多,理解起来不容易 样板代码太多

得益于 Mobx 的 observable,使用 mobx 可以做到精准更新;对应的 Redux 是用 dispath 进行广播,通过Provider 和 connect 来比对前后差别控制更新粒度;

洗一个类似牛客面试房间的计时器

node

node常用包 node,ECMS,js之间的关系 node事件轮洵 fq软件大概是个什么原理,有自己搭过fq服务器吗,不知道没搭过是吧,那你想想大概是个什么操作?说给我听

typescript 和java script对比

css预处理器

sass stylus less

个人成长的路线思考,未来的规划

目前阶段:找实习,然后稳定工作,不断丰富自己的实践,提高代码产出能力,这个阶段就是学习为主,以项目推动再加上个人感兴趣的方向去学习,