2022前端面试经验总结

2022年秋招总结前端面经存档,已上岸大厂,分享给有需要的同学~ 【后续有时间补充面试记录】

事件循环

Vue生命周期函数

Vue中组件之间的通信

跨域问题以及解决方案

1.Html相关

简述一下src与href的区别

href 是指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的链接,用于超链接。

src是指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在位置;在请求src资源时会将其指向的资源下载并应用到文档内,例如js脚本,img图片和frame等元素。当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等元素也如此,类似于将所指向资源嵌入当前标签内。这也是为什么将js脚本放在底部而不是头部。

2.css相关

?css的盒子模型

CSS盒模型本质上是一个盒子,封装周围的HTML元素,它包括:边距margin,边框border,填充padding,和实际内容content。盒模型允许我们在其它元素和周围元素边框之间的空间放置元素。

box-sizing: content-box(W3C盒模型,又名标准盒模型):元素的宽高大小表现为内容的大小。 box-sizing: border-box(IE盒模型,又名怪异盒模型):元素的宽高表现为内容 + 内边距 + 边框的大小。背景会延伸到边框的外沿。

列举 CSS 选择器以及优先级

讲讲你知道的 CSS 动画属性有哪些

?css原生变量

基本用法:原生CSS变量是受HTML层级关系的限制的,并从其父级继承这个值。所以说最佳的实践就是定义在根伪类:root选择器上。定义之后可以使用var()函数使用。

相关特性:

  • 复杂值的用法

  • 变量的继承性

  • 变量的备用值 — CSS的var()函数可以接受两个参数,第一个作为他的值,第二个参数作为其备用值,备用值在第一个值失效的时候使用

  • 通过JS获取和设置CSS变量

    • 通过JavaScript获取和设置JavaScript变量的具体步骤如下,

      1. 获取指定DOM节点

      2. 通过getComputedStyle()方法获取当前DOM节点的CSSStyleDeclaration对象

      3. 通过CSSStyleDeclaration.getPropertyValue(变量名/属性名)获取指定的变量值

      4. 通过Element.style.setProperty() 修改或者设置一个变量的值

        实现代码如下:

        // 1
        const child = document.getElementById('child')
        // 2 3
        let color = getComputedStyle(child).getPropertyValue('--color')
        console.log(color) // #FF6A00
        // 4
        child.style.setProperty('--color', '#eeeccc')
        color = getComputedStyle(child).getPropertyValue('--color')
        console.log(color) // #eeeccc
        

?聊聊定位相关问题

  1. HTML 元素默认情况下的定位方式为 static(静态)。它始终根据页面的正常流进行定位
  2. position: relative 元素相对于其正常位置进行定位
  3. position: fixed 元素是相对于视口定位的,这意味着即使滚动页面,它也始终位于同一位置。
  4. position: absolute 元素相对于最近的定位祖先元素进行定位。然而,如果绝对定位的元素没有祖先,它将使用文档主体(body),并随页面滚动一起移动。
  5. position: sticky 粘性元素根据滚动位置在相对(relative)和固定(fixed)之间切换。起先它会被相对定位,直到在视口中遇到给定的偏移位置为止 – 然后将其“粘贴”在适当的位置(比如 position:fixed)。

?实现水平垂直居中有几种方法

1.使用 transform 变形

2.使用 transform 结合 position

3.margin:auto

keys:

子元素display:block。margin 0 auto只对块元素有效,父元素子元素必须都是块元素。你是不是曾经想过,**既然margin 0 auto可以水平居中,那么为什么margin auto不能水平垂直都居中呢?**我设置之后,垂直方向没有效果,这是为啥?其实margin:auto也好,margin:0 auto也罢,它们的auto其实是弹性计算,而普通的盒子模型(非弹性flex盒子)只有水平方向是弹性的,垂直方向不是弹性的。所以要想让margin:auto生效,很简单,父元素设置display:flex就可以啦!

4.给父盒子设置弹性布局

实现左边固定右边自适应

方法一:浮动。让左边浮动脱离文档流并设置固定宽度,然后右边设置overflow为auto。

.left {

    float: left; 
    width: 200px;

    height: 200px;

    background-color: aqua;

}

.right {

    overflow: auto;
    height: 300px;

    background-color: bisque;

}


方法二:绝对定位。左边设置为绝对定位固定宽度(也是脱离了文档流),右边设置margin-left为左边宽度。

.left {

    position: absolute;
    width: 200px;

    height: 200px;

    background-color: aqua;

}

.right {

    margin-left: 200px;
    height: 300px;

    background-color: bisque;

}


方法三:flex布局。左边设置固定宽度,右边设置flex为1,自动占满所有区域。

圣杯布局

双飞翼布局

3.js相关

重绘和重排

重绘(repaint):当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要 UI 层面的重新像素绘制,因此损耗较少。

回流(reflow):又叫重排(layout)。当元素的尺寸、结构或者触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。

或许这概念比较抽象,讲起来很难理解!简单点说,就比如我们页面中的某些颜色会发生动态改变,而木有影响到尺寸,布局、位置、结构这些改变的,就叫做重绘,而例如动态添加结点、改变尺寸、位置这些的,就叫做回流!

回流的损耗是比较大的!所以尽量不要产生太多的回流!就比如,样式的动态修改不要多步而尽量应一步到位!

为了避免大量的重绘和回流!

  1. 避免频繁操作样式,可汇总后统一一次修改
  2. 尽量使用 class 进行样式修改,而不是直接操作样式
  3. 减少 DOM 的操作,可使用字符串一次性插入
JS如何阻止事件冒泡和阻止默认事件。
  • event.stoppropagation()阻止事件冒泡,
  • event.preventdefault()阻止默认事件。

防抖和节流

防抖是需要等待多久时间才能再触发一次事件!

节流是多久时间内只能触发一次事件!

debounce_throttle.png

防抖

function debounce(fn, delay) {
  let timer = null;
  return function () {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, delay);
  }
}

节流

function throttle(fn, cycle) {
  let start = Date.now();
  let now;
  let timer;
  return function () {
    now = Date.now();
    clearTimeout(timer);
    if (now - start >= cycle) {
      fn.apply(this, arguments);
      start = now;
    } else {
      timer = setTimeout(() => {
        fn.apply(this, arguments);
      }, cycle);
    }
  }
}

手动实现轮播图效果

?如何区别数据类型

  • typeof

    console.log(typeof 2);               // number
    console.log(typeof true);            // boolean
    console.log(typeof 'str');           // string
    console.log(typeof undefined);       // undefined
    console.log(typeof []);              // object 
    console.log(typeof {});              // object
    console.log(typeof function(){});    // function
    console.log(typeof null);            // object
    

    无法区分Object、Array和Null,都返回Object

  • instanceof

    console.log(2 instanceof Number);                    // false
    console.log(true instanceof Boolean);                // false 
    console.log('str' instanceof String);                // false  
    console.log([] instanceof Array);                    // true
    console.log(function(){} instanceof Function);       // true
    console.log({} instanceof Object);                   // true
    

    优点:能够区分Array、Object和Function,适合用于判断自定义的类实例对象 缺点:Number,Boolean,String基本数据类型不能判断

  • Object.prototype.toString.call()

    var toString = Object.prototype.toString;
     
    console.log(toString.call(2));                      //[object Number]
    console.log(toString.call(true));                   //[object Boolean]
    console.log(toString.call('str'));                  //[object String]
    console.log(toString.call([]));                     //[object Array]
    console.log(toString.call(function(){}));           //[object Function]
    console.log(toString.call({}));                     //[object Object]
    console.log(toString.call(undefined));              //[object Undefined]
    console.log(toString.call(null));                   //[object Null]
    

    优点:精准判断数据类型 缺点:写法繁琐不容易记,推荐进行封装后使用

==其中如何区别数组和对象==

  1. 使用instanceof
  2. 使用Object.prototype.toString.call()

?原型链问题

?js处理数组的函数

如何随机访问数组中的数据

1.应用场景

随机获取数组元素.
2.学习/操作

1.随机获取数组一个元素

var items = [‘1′,’2′,’4′,’5′,’6′,’7′,’8′,’9′,’10’];

var item = items[Math.floor(Math.random()*items.length)];

解释:

Math.random()返回0到1之间的一个伪随机数,可能等于0,但是一定小于1

Math.floor方法返回小于参数值的最大整数(地板值)

结合起来:

随机获取一个0-1之间的小数, 然后乘以自身长度, 一定是一个大于等于0 小于自身长度的一个数. 如上面 长度为10, 则获取的一个随机数就为0-10之间的一个数[数组的下标].

最后根据下标获取数组元素值即可

2.随机获取几个元素

function getRandomArrayElements(arr, count) {undefined
var shuffled = arr.slice(0), i = arr.length, min = i – count, temp, index; //只是声明变量的方式, 也可以分开写
while (i– > min) {undefined

    //console.log(i);
    index = Math.floor((i + 1) * Math.random()); //这里的+1 是因为上面i--的操作  所以要加回来
    temp = shuffled[index];  //即值交换
    shuffled[index] = shuffled[i]; 
    shuffled[i] = temp;
    //console.log(shuffled);
}
return shuffled.slice(min);

}

var items = [‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ’10’];
console.log( getRandomArrayElements(items, 4) );

截图:

解析:

思路就是将新拷贝一份数组, 然后将数组按照获取元素的次数, 每次将元素交换[从倒数第二个开始往前开始, 与随机获取的数组下标元素对调位置], 最后截取数组元素个数 返回结果.

在上面的代码中,随机索引将存储在变量 rand 中,使用这个索引我们可以从数组中选择一个随机值,该值将存储在变量 rValue 中。你还可以使用按位 NOT 运算符 ~~ 或按位 OR 运算符 |而不是 Math.floor() 函数将浮点数转换为整数。使用按位运算符更快,但它可能不适用于包含数百万个值的数组。例如,让我们使用按位 NOT 运算符生成一个随机数。请参考下面的代码。

var myArray = ['one', 'two', 'three', 'four', 'five'];

var rand = ~~(Math.random()*myArray.length);
var rValue = myArray[rand];

console.log(rValue)

输出:

one

现在,让我们使用按位 OR 运算符生成一个随机数。请参考下面的代码。

var myArray = ['one', 'two', 'three', 'four', 'five'];

var rand = Math.random()*myArray.length | 0;
var rValue = myArray[rand];

console.log(rValue)

输出:

three

如果再次运行代码,输出将发生变化。你还可以创建一个函数来从给定数组中选择随机值,这样你就不必重写所有代码。例如,让我们创建一个函数来从给定数组中选取随机值并使用数组对其进行测试。请参考下面的代码。

function RandArray(array){
    var rand = Math.random()*array.length | 0;
    var rValue = array[rand];
    return rValue;
}

var myArray = ['one', 'two', 'three', 'four', 'five', 'six'];
var rValue = RandArray(myArray);
console.log(rValue)

输出:

six

如果再次运行代码,输出将发生变化。现在,要从数组中选择一个随机值,你只需调用 RandArray() 函数。

数组去重

let arr = [1,'1',2,'2',1,2,'x','y','f','x','y','f'];
function unique1(arr){
	let result = [arr[0]];
	for (let i = 1; i < arr.length; i++) {
		let item = arr[i];
		if(result.indexOf(item) == -1){
			result.push(item);
		}
	}
	return result;
}


console.log(unique1(arr));

== 和 === 区别

  • ==, 两边值类型不同的时候,要先进行类型转换,再比较
  • ===,不做类型转换,类型不同的一定不等。

==类型转换过程:

  1. 如果类型不同,进行类型转换
  2. 判断比较的是否是 null 或者是 undefined, 如果是, 返回 true .
  3. 判断两者类型是否为 string 和 number, 如果是, 将字符串转换成 number
  4. 判断其中一方是否为 boolean, 如果是, 将 boolean 转为 number 再进行判断
  5. 判断其中一方是否为 object 且另一方为 string、number 或者 symbol , 如果是, 将 object 转为原始类型再进行判断

经典面试题:[] == ![] 为什么是true

考的是数组的类型转换。

​ 在犀牛书49页,任意数组转换为字符串””,数字0和布尔值true

​ 所以![]会转为布尔值true再取反false

​ 然后根据相等运算符“==”的规则,有boolean的转为数字,有Object的转为原始值

​ 左右两边会变成

​ “” == 0

​ 最后如果是字符串和数字比较,会把字符串转为数字

0 == 0

​ 结果就是为true

转化步骤:

  1. !运算符优先级最高,![]会被转为为false,因此表达式变成了:[] == false
  2. 根据上面第(4)条规则,如果有一方是boolean,就把boolean转为number,因此表达式变成了:[] == 0
  3. 根据上面第(5)条规则,把数组转为原始类型,调用数组的toString()方法,[]转为空字符串,因此表达式变成了:'' == 0
  4. 根据上面第(3)条规则,两边数据类型为string和number,把空字符串转为0,因此表达式变成了:0 == 0
  5. 两边数据类型相同,0==0为true

阻止事件冒泡的 API

Vue中阻止事件冒泡的API

原生JS中阻止事件冒泡和捕获的API

JavaScript 和 TypeScript 的主要差异

TypeScript 可以使用 JavaScript 中的所有代码和编码概念,TypeScript 是为了使 JavaScript 的开发变得更加容易而创建的。例如,TypeScript 使用类型和接口等概念来描述正在使用的数据,这使开发人员能够快速检测错误并调试应用程序

  • TypeScript 从核心语言方面和类概念的模塑方面对 JavaScript 对象模型进行扩展。
  • JavaScript 代码可以在无需任何修改的情况下与 TypeScript 一同工作,同时可以使用编译器将 TypeScript 代码转换为 JavaScript。
  • TypeScript 通过类型注解提供编译时的静态类型检查。
  • TypeScript 中的数据要求带有明确的类型,JavaScript不要求。
  • TypeScript 为函数提供了缺省参数值。
  • TypeScript 引入了 JavaScript 中没有的“类”概念。
  • TypeScript 中引入了模块的概念,可以把声明、数据、函数和类封装在模块中。

==TS工程化上的优势==

1. 静态输入

静态类型化是一种功能,可以在开发人员编写脚本时检测错误。查找并修复错误是当今开发团队的迫切需求。有了这项功能,就会允许开发人员编写更健壮的代码并对其进行维护,以便使得代码质量更好、更清晰。

2. 大型的开发项目

有时为了改进开发项目,需要对代码库进行小的增量更改。这些小小的变化可能会产生严重的、意想不到的后果,因此有必要撤销这些变化。使用TypeScript工具来进行重构更变的容易、快捷。

3. 更好的协作

当发开大型项目时,会有许多开发人员,此时乱码和错误的机也会增加。类型安全是一种在编码期间检测错误的功能,而不是在编译项目时检测错误。这为开发团队创建了一个更高效的编码和调试过程。

4. 更强的生产力

干净的 ECMAScript 6 代码,自动完成和动态输入等因素有助于提高开发人员的工作效率。这些功能也有助于编译器创建优化的代码。

==JS工程化上的优势==

1. 人气

JavaScript 的开发者社区仍然是巨大而活跃的,在社区中可以很方便地找到大量成熟的开发项目和可用资源。

2. 学习曲线

由于 JavaScript 语言发展的较早,也较为成熟,所以仍有一大批开发人员坚持使用他们熟悉的脚本语言 JavaScript,而不是学习 TypeScript。

3. 本地浏览器支持

TypeScript 代码需要被编译(输出 JavaScript 代码),这是 TypeScript 代码执行时的一个额外的步骤。

4. 不需要注释

为了充分利用 TypeScript 特性,开发人员需要不断注释他们的代码,这可能会使项目效率降低。

5. 灵活性

有些开发人员更喜欢 JavaScript 的灵活性。

如何抉择

TypeScript 正在成为开发大型编码项目的有力工具。因为其面向对象编程语言的结构保持了代码的清洁、一致和简单的调试。因此在应对大型开发项目时,使用 TypeScript 更加合适。如果有一个相对较小的编码项目,似乎没有必要使用 TypeScript,只需使用灵活的 JavaScript 即可

4.Es6相关特性

Es6中常用的特性有 destructuring, default, rest arguments

  1. let则实际上为JavaScript新增了块级作用域。用它所声明的变量,只在let命令所在的代码块内有效。
  2. const定义常量 多用于引用第三方库的时声明的变量
  3. class, extends, super 新的class写法让对象原型的写法更加清晰
  4. 箭头函数 箭头函数没有自己的this指向,向外找第一个function的this
  5. 字符串模板
  6. 解构赋值
  7. 设置默认值default和展开运算符
  8. 对象字面量简写法 name:name 可以简写为 name
  9. async和await

Promise是什么 相关参考

async的实现原理

最开始的回调函数是callback,同步回调,需要等待,尤其进行需要顺序执行的就会产生大名鼎鼎的回调地狱。然后就有了promise进行异步执行,无需等待回调通过.then()直接进行在一个异步操作,但还是有缺点,无法精准的捕获错误。所以就加上了generator生成器,然后promise+generator就组成异步迭代生成器,也就是每次promise决议之后yied停一下,看一下有没有错误,没错误继续下一个,这样就可以实现错误的捕获了。但是这样实现起来是很麻烦的,就在这时候ES6推出了async..await 就是上面异步迭代生成器的语法糖,更好用了,但底层是一个意思。有了这样的一个思路,去查代码实现什么的就好理解了。

有了 promise 为什么还需要 async/await ?

async/await是一种编写异步代码的新方法。在这之前编写异步代码使用的是回调函数和promise。
async/await实际是建立在promise之上的。因此你不能把它和回调函数搭配使用。
async/await可以使异步代码在形式上更接近于同步代码。这就是它最大的价值。

var let const 的区别

5.Vue相关

????Vue 组件生命周期

  • beforeCreate(创建前) vue实例的挂载元素$el和数据对象 data都是undefined, 还未初始化

  • created(创建后) 完成了 data数据初始化, el还未初始化

  • beforeMount(载入前) vue实例的$el和data都初始化了, 相关的render函数首次被调用

  • mounted(载入后) 此过程中进行ajax交互

  • beforeUpdate(更新前)

  • updated(更新后)

  • beforeDestroy(销毁前)

  • destroyed(销毁后)

???vue2和vue3响应式的区别

Vue双向绑定原理

Vue数据双向绑定原理是通过数据劫持结合发布者-订阅者模式的方式来实现的,首先是对数据进行监听,然后当监听的属性发生变化时则告诉订阅者是否要更新,若更新就会执行对应的更新函数从而更新视图

???Vue中的组件通信

  1. 父组件给子组件传值通过props

  2. 子组件给父组件传值通过$emit触发回调

  3. 兄弟组件通信的方法

    1. eventBus(事件总线进行通信)

      通过实例一个vue实例eventBus作为媒介,要相互通信的兄弟组件之中,都引入eventBus

      eventBus.$emit进行传递

      eventBus.$on进行接收

    2. 让父组件允当两个子组件之间的中间件(通过父组件这个媒介去进行传递)

?vue中的nextTick是什么以及nextTick底层使怎么实现的

相关参考

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

我们可以理解成,Vue 在更新 DOM 时是异步执行的。当数据发生变化,Vue将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新

{{num}}
for(let i=0; i<100000; i++){
 num = i
}

如果没有 nextTick 更新机制,那么 num 每次更新值都会触发视图更新(上面这段代码也就是会更新10万次视图),有了nextTick机制,只需要更新一次,所以nextTick本质是一种优化策略

?vue中v-if和v-show的区别

v-show只编译一次,后面其实就是控制css,而v-if不停的销毁和创建,故v-show性能更好一点。 如果你的页面不想让其他程序员看到就用v-if,它不会在页面中显示。

?Vue的相关指令

v-once、v-for、v-if、v-show、v-on,v-model、v-text、v-html、v-bind、v-slot

?Vuex刷新之后数据丢失问题如何解决

?有没有使用过插槽

require.context()的用法详解

相关参考

1.require.context()
vue项目中 使用require.context()实现前端工程化引入文件

require.context(directory, useSubdirectories, regExp, mode = ‘sync’)
1
directory:表示检索的目录
useSubdirectories:表示是否检索子文件夹
regExp:匹配文件的正则表达式,一般是文件名
mode:加载模式,同步/异步
2.用来在组件内引入多个组件
// 从@/components/home目录下加载所有.vue后缀的组件
const files = require.context(‘@/components/home’, false, /.vue$/);
const components = {};

// 遍历files对象,构建components键值
files.keys().forEach(key => {
components[key.replace(/(./|.vue)/g, ”)] = files(key).default
});

export default {

components, // ES6语法糖,代表 components: components,

3.在main.js中引入大量公共组件
import Vue from ‘vue’
// 自定义组件
const requireComponents = require.context(‘../views/components’, true, /.vue/)
// 打印结果
// 遍历出每个组件的路径
requireComponents.keys().forEach(fileName => {
// 组件实例
const reqCom = requireComponents(fileName)
// 截取路径作为组件名
const reqComName =reqCom.name|| fileName.replace(/./(.*).vue/,’$1′)
// 组件挂载
Vue.component(reqComName, reqCom.default || reqCom)
})

4.用在vuex中加载module 或加载多个api接口
/**

  • The file enables @/store/index.js to import all vuex modules
  • in a one-shot manner. There should not be any reason to edit this file.
    */

const files = require.context(‘.’, false, /.js$/)
const modules = {}

files.keys().forEach(key => {
if (key === ‘./index.js’) return
modules[key.replace(/(./|.js)/g, ”)] = files(key).default
})

webpack你了解吗?它的打包过程是怎样的?你听过happy-track这个工具吗 相关参考

?列表组件的key可以使用index吗?

尽量不要使用索引值index作key值,一定要用唯一标识的值,如id等。因为若用数组索引index为key,当向数组中指定位置插入一个新元素后,因为这时候会重新更新index索引,对应着后面的虚拟DOM的key值全部更新了,这个时候还是会做不必要的更新,就像没有加key一样,因此index虽然能够解决key不冲突的问题,但是并不能解决复用的情况。如果是静态数据,用索引号index做key值是没有问题的。

?diff算法有了解吗?它是怎么工作的?为什么它是操作虚拟DOM?相关参考

Vue 组件修饰符(是个啥?)

Vue data 里面对象的属性是对象,那么最里面的对象的属性会不会双向绑定

Vue 数组怎么动态绑定的,性能问题

7.浏览器相关

????跨域产生的原因和解决方法

协议、域名、端口号相同为同源,否则不允许跨域,最初是指cookie不能共享,由于浏览器的表单提交时不受同源策略限制的,所以为了安全性,同源策略是必要的。
现在的同源策略限制的内容包括
1.cookie、localStorage、indexDB无法读取
2.DOM无法获取
3.AJAX不能发送

解决方法
  • CORS跨域资源共享cross-origin-resource-sharing

    服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问 ==服务器加请求头==

  • 服务器代理

  • JSONP

    由于浏览器的同源策略限制,不允许跨域请求;但是页面中的 script、img、iframe标签是例外,不受同源策略限制。

    Jsonp 就是利用script标签跨域特性进行请求。

    JSONP 的原理就是,先在全局注册一个回调函数,定义回调数据的处理;与服务端约定好一个同名回调函数名,服务端接收到请求后,将返回一段 Javascript,在这段 Javascript 代码中调用了约定好的回调函数,并且将数据作为参数进行传递。当网页接收到这段 Javascript 代码后,就会执行这个回调函数。

    JSONP缺点:它只支持GET请求,而不支持POST请求等其他类型的HTTP请求。

    js原生写法的JSONP
    <script>
        function createJs(sUrl){
            var oScript = document.createElement('script');
            oScript.type = 'text/javascript';
            oScript.src = sUrl;
    document.getElementsByTagName('head')[0].appendChild(oScript);
        }
        createJs('jsonp.js');
    </script>
    
  • 现在衍生出来的vue-cli脚手架项目里的Npm模块http-proxy-middleware也可以解决跨域,

websocket

websocket 是一种网络通信协议,是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通信的协议,这个对比着 http 协议来说,http 协议是一种无状态的、无连接的、单向的应用层协议,通信请求只能由客户端发起,服务端对请求做出应答处理。http 协议无法实现服务器主动向客户端发起消息,Websocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。

cookie的secure这个属性是干啥的

基于安全的考虑,需要给cookie加上Secure和HttpOnly属性,HttpOnly比较好理解,设置HttpOnly=true的cookie不能被js获取到,无法用document.cookie打出cookie的内容。

Secure属性是说如果一个cookie被设置了Secure=true,那么这个cookie只能用https协议发送给服务器,用http协议是不发送的。换句话说,cookie是在https的情况下创建的,而且他的Secure=true,那么之后你一直用https访问其他的页面(比如登录之后点击其他子页面),cookie会被发送到服务器,你无需重新登录就可以跳转到其他页面。但是如果这是你把url改成http协议访问其他页面,你就需要重新登录了,因为这个cookie不能在http协议中发送。

cookie的httpOnly是干啥的

如果cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击,窃取cookie内容,这样就增加了cookie的安全性,即便是这样,也不要将重要信息存入cookie。XSS全称Cross SiteScript,跨站脚本攻击,是Web程序中常见的漏洞,XSS属于被动式且用于客户端的攻击方式,所以容易被忽略其危害性。其原理是攻击者向有XSS漏洞的网站中输入(传入)恶意的HTML代码,当其它用户浏览该网站时,这段HTML代码会自动执行,从而达到攻击的目的。如,盗取用户Cookie、破坏页面结构、重定向到其它网站等。

301,401,403,405这些状态码都是干啥的

  • 301 Moved Permanently:资源的uri已更新,你也更新下你的书签引用吧。永久性重定向,请求的资源已经被分配了新的URI,以后应使用资源现在所指的URI。

  • 401 Unauthorized:该状态码表示发送的请求需要有通过HTTP认证(BASIC认证,DIGEST认证)的认证信息。

  • 403 Forbidden:不允许访问那个资源。该状态码表明对请求资源的访问被服务器拒绝了。(权限,未授权IP等)

  • 405表示请求的方式不对,请求的方式有get、post、head、put……常用的为post和get。

简述同步和异步的区别

同步是阻塞模式,异步是非阻塞模式。

同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;

异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。

进程和线程有什么区别?相关参考

一、进程、线程、协程的概念

进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。

线程:是进程的一个执行单元,是进程内科调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。

协程:是一种比线程更加轻量级的存在。一个线程也可以拥有多个协程。其执行过程更类似于子例程,或者说不带返回值的函数调用。

二、进程和线程的区别

地址空间:线程共享本进程的地址空间,而进程之间是独立的地址空间。

资源:线程共享本进程的资源如内存、I/O、cpu等,不利于资源的管理和保护,而进程之间的资源是独立的,能很好的进行资源管理和保护。

健壮性:多进程要比多线程健壮,一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。

执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口,执行开销大。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,执行开销小。

可并发性:两者均可并发执行。

切换时:进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。

其他:线程是处理器调度的基本单位,但是进程不是。

三、何时使用多进程,何时使用多线程?

对资源的管理和保护要求高,不限制开销和效率时,使用多进程。

要求效率高,频繁切换时,资源的保护管理要求不是很高时,使用多线程。

四、为什么会有线程?

每个进程都有自己的地址空间,即进程空间,在网络或多用户换机下,一个服务器通常需要接收大量不确定数量用户的并发请求,为每一个请求都创建一个进程显然行不通(系统开销大响应用户请求效率低),因此操作系统中线程概念被引进。

说说浏览器的多进程 相关参考

XSS和CSRF区别

  1. 跨站脚本攻击(Cross Site Scripting),为了不和层叠样式表 CSS 混淆,故将跨站脚本攻击缩写为 XSS。恶意攻击者往 Web 页面里插入恶意 Script 代码,当用户浏览该页之时,嵌入其中 Web 里面的 Script 代码会被执行,从而达到恶意攻击用户的目的。
  2. 跨站请求伪造(Cross-site request forgery),是伪造请求,冒充用户在站内的正常操作。我们知道,绝大多数网站是通过 cookie 等方式辨识用户身份,再予以授权的。所以要伪造用户的正常操作,最好的方法是通过 XSS 或链接欺骗等途径,让用户在本机(即拥有身份 cookie 的浏览器端)发起用户所不知道的请求。

区别:

  • 原理不同,CSRF是利用网站A本身的漏洞,去请求网站A的api;XSS是向目标网站注入JS代码,然后执行JS里的代码。
  • CSRF需要用户先登录目标网站获取cookie,而XSS不需要登录
  • CSRF的目标是用户,XSS的目标是服务器
  • XSS是利用合法用户获取其信息,而CSRF是伪造成合法用户发起请求

csrf的防御方法

CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。一个典型的CSRF攻击有着如下的流程:受害者登录a.com,并保留了登录凭证(Cookie)。攻击者引诱受害者访问了b.com。b.com 向 a.com 发送了一个请求:a.com/act=xx。浏览器会默认携带a.com的Cookie。a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求。

  • 验证 HTTP Referer 字段。该字段记录了请求的来源地址,每次都进行检查请求地址和网站域名是否相同,不同则判定为csrf攻击。缺点,有一些浏览器可以篡改Referer字段,有些浏览器也可以设置请求头不懈怠Referer字段。

  • 在请求地址中添加token。添加一些从cookie中获取不到的信息,然后生成随机token,最后服务端截取验证,如果没有或者不对则判定为csrf攻击。

用户打开浏览器,访问目标网站A,输入用户名和密码请求登录
• 用户信息在通过认证后,网站A产生一个cookie信息返回给浏览器,这个时候用户以可正常发送请求到网站A
• 用户在没有退出网站A之前在同一个浏览器打开了另一个新网站B。
• 新网站B收到用户请求之后返回一些攻击代码,并发出一个请求要求访问返回cookie的网站A
• 浏览器收到这些攻击性代码之后根据新网站B的请求在用户不知道的情况下以用户的权限操作了cookie并向网站A服务器发起了合法的请求。

验证HTTP的方法:Referer字段的好处就是实施起来特别简单,普通的网站开发不需要特别担心CSRF漏洞,只需要在最后面设置一个拦截器来验证referer的值就可以了,不需要改变已有的代码逻辑,非常便捷。但是这个方法也不是万无一失的,虽然referer是浏览器提供的,但是不同的浏览器可能在referer的实现上或多或少有自身的漏洞,所以使用referer的安全保证是通过浏览器实现的。使用token验证的方法要比referer更安全一些,需要把token放在一个HTTP自定义的请求头部中,解决了使用get或者post传参的不便性。

8.CS相关

http缓存的理解

缓存分为强缓存和协商缓存。强缓存不过服务器,协商缓存需要过服务器,协商缓存返回的状态码是304。两类缓存机制可以同时存在,强缓存的优先级高于协商缓存。当执行强缓存时,如若缓存命中,则直接使用缓存数据库中的数据,不再进行缓存协商。

  1. 强缓存

  • Expires

  • cache-control

  1. 协商缓存

  • Last-Modified 和 If-Modified-Since
  • Etag 和 If-None-Match

http缓存

  1. 第一次访问返回200并缓存资源文件
  2. 下一次访问,强缓存优先级高,首先比较cache-control中的max-age是否过期,没有就命中强缓存
  3. 协商缓存首先根据Etag判断资源有无修改,Etag一致则命中协商缓存,否则返回新的资源和Etag
  4. 没有Etag,比较Last-Modified和被请求文件最后修改时间,一致则命中协商缓存,否则返回新的资源和Last-Modified

http2.0

http2.0是一种安全高效的下一代http传输协议。安全是因为http2.0建立在https协议的基础上,高效是因为它是通过二进制分帧来进行数据传输。正因为这些特性,http2.0协议也在被越来越多的网站支持。

http1.1 和 http2.0 的区别

http****1.1 的头信息是 ASCII 编码(文本),而数据体可以使二进制也可以是文本,但是 http****2.0 无论是头信息还是数据体都是二进制,统称为帧,这是因为二进制解析起来更加的高效,并且紧凑,出错率也更少。

http2.0 3.0

常见的请求头和响应头

HTTP与HTTPS的区别

  • HTTP的URL由http://起始且默认使用端口80,而HTTPS的URL由https://起始且默认使用端口443
  • HTTP是超文本传输协议,信息是明文传输,HTTPS则是具有安全性的 SSL 加密传输协议
  • HTTP的连接很简单,是无状态的,HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 http 协议安全

http三次四次

三次握手那次最容易被攻击

OSI网络七层模型。HTTP、webSocket分别在哪一层?

比较常问的:

  • 应用层(TELNET,HTTP,FTP,NFS,SMTP,webSocket)
  • 传输层 (TCP,UDP)
  • 网络层 (IP)

HTTPS加密通信的过程

https是基于tcp协议的,客户端先会和服务端发起链接建立

接着服务端会把它的证书返回给客户端,证书里面包括公钥S.pub、颁发机构和有效期等信息

拿到的证书可以通过浏览器内置的根证书(内含C.pub)验证其合法性

客户端生成随机的对称加密秘钥Z,通过服务端的公钥S.pub加密发给服务端

客户端和服务端通过对称秘钥Z加密数据来进行http通信

TCP和UDP的区别是什么?什么情况下用到TCP,什么情况下用到UDP,举一些例子?相关参考

你了解HTTP和HTTPS的区别吗?SSL/TLS是什么,起了什么作用,怎么保证安全的?相关参考

说说在浏览器从输入url到页面显示的过程中发生了什么?

从输入页面URL到页面渲染完成大致流程为:

  • 解析URL
  • 浏览器本地缓存 (强缓存,协商缓存)
  • DNS解析
  • 建立TCP/IP连接
  • 发送HTTP请求
  • 服务器处理请求并返回HTTP报文
  • 浏览器根据深度遍历的方式把html节点遍历构建DOM树
  • 遇到CSS外链,异步加载解析CSS,构建CSS规则树
  • 遇到script标签,如果是普通JS标签则同步加载并执行,阻塞页面渲染,如果标签上有defer / async属性则异步加载JS资源
  • 将dom树和CSS DOM树构造成render树
  • 渲染render树

10.项目优化问题

使用CDN

采用CDN技术,最大的好处,就是加速了网站的访问——用户与内容之间的物理距离缩短,用户的等待时间也得以缩短。 而且,分发至不同线路的缓存服务器,也让跨运营商之间的访问得以加速。 例如中国移动手机用户访问中国电信网络的内容源,可以通过在中国移动假设CDN服务器,进行加速。

使用gzip压缩

文本压缩

合并请求

雪碧图

懒加载(路由懒加载、图片懒加载)

缓存资源

减少DOM操作

数据结构相关

?说说链表和数组的区别 优缺点

大疆一面

  • vue2.0和vue3.0的区别

    1. 性能更强,打包体积更小
    2. 更支持TS
    3. 生命周期函数改变,使用setup代替了之前的beforeCreated和created
    4. 使用proxy代替defineProperty
    Vue3如何实现数据绑定
    1. 对目标对象多设置一层拦截。无论对目标对象进行什么操作,都要经过这层拦截
    2. Proxy拦截的是整个对象,所以可以监听到一些数组或者对象属性的增删
  • resulful接口 四个接口的区别

    RESTful是一种架构的规范与约束、原则,符合这种规范的架构就是RESTful架构。

    get和post的区别

    post请求和get请求都是HTTP的请求方式,本质上来说是没有区别的,底层实现都是基于TCP/IP协议,但是请求有各种各样的方式,于是HTTP就对请求进行了划分和规定,于是就产生了get、post处理请求的分工和区别。

    1. GET提交的数据放在URL中,POST则不会。这是最显而易见的差别。这点意味着GET更不安全(POST也不安全,因为HTTP是明文传输抓包就能获取数据内容,要想安全还得加密)
    2. GET提交的数据大小有限制(是因为浏览器对URL的长度有限制,GET本身没有限制),POST没有
    3. GET能被缓存,POST不能
    4. GET回退浏览器无害,POST会再次提交请求(GET方法回退后浏览器在缓存中拿结果,POST每次都会创建新资源)
    5. GET可以被保存为书签,POST不可以。
    6. GET只允许ASCII字符,POST没有限制
    7. GET会保存在浏览器历史记录中,POST不会。

    post和put的请求

    1. 使用PUT必须明确的传入一个id,知道要操作的对象,但是POST不需要明确的知道要操作的对象。
    2. PUT修改对象是全部替换目标对象,POST一般是修改目标对象的部分内容。
    3. PUT是等幂的,对此请求创建的结果都是一样的,POST不是等幂的,可能由于网络原因或者其它原因多创建了几次,那么将会有多个用户被创建。
  • resulful和传统的RFC的区别

  • vue的生命周期函数(8个生命周期函数)

    image-20220116144614444转存失败,建议直接上传图片文件

  • 如果改变了数据会触发什么生命周期函数

    beforeUpdate 和 updated

  • vue中组件(父子和兄弟)进行通信

  • vue中mvvm的实现原理

    MVVM 由 Model,View,ViewModel 三部分构成,Model 层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑;View 代表UI 组件,它负责将数据模型转化成UI 展现出来,ViewModel 是一个同步View 和 Model的对象。
    在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
    ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

  • 前端路由表和后端路由表的区别

    在web开发早期的年代里,前端的功能远不如现在这么强大,一直是后端路由占据主导地位。无论是jsp,还是php、asp,用户能通过URL访问到的页面,大多是通过后端路由匹配之后再返回给浏览器的。浏览器在地址栏中切换不同的URL时,每次都向后台服务器发出请求,服务器响应请求,在后台拼接html文件返回给前端,并且每次切换页面时,浏览器都会刷新页面。

    在后端,路由映射表中就是不同的URL地址与不同的html + css + 后端数据 之间的映射

    前端路由应用最广泛的例子就是当今的SPA的web项目。不管是Vue、React还是Angular的页面工程,都离不开相应配套的router工具。

    整个页面就只有一整套的HTML+CSS+JS,这一套HTML+CSS+JS中包含了许多个网页,当我们请求不同的URL时,客户端会从这一整套HTML+CSS+JS中找到对应的HTML+CSS+JS,将它们解析执行,渲染在页面上。

    前端路由带来的最明显的好处就是,地址栏URL的跳转不会白屏了——这也得益于客户端渲染带来的好处。

  • 为什么会出现变量提升的问题

    首先我们要知道,JS在拿到一个变量或者一个函数的时候,会有两步操作,即解析和执行。

    在解析阶段,JS会检查语法,并对函数进行预编译。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明好可使用。在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局执行上下文类似,不过函数执行上下文会多出this、arguments和函数的参数。

    全局上下文:变量定义,函数声明
    函数上下文:变量定义,函数声明,this,arguments
    在执行阶段,就是按照代码的顺序依次执行。

    那为什么会进行变量提升呢?主要有以下两个原因:

    • 提高性能

    • 容错性更好

    (1)提高性能

    在JS代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了提高性能,如果没有这一步,那么每次执行代码前都必须重新解析一遍该变量(函数),而这是没有必要的,因为变量(函数)的代码并不会改变,解析一遍就够了。

    在解析的过程中,还会为函数生成预编译代码。在预编译时,会统计声明了哪些变量、创建了哪些函数,并对函数的代码进行压缩,去除注释、不必要的空白等。这样做的好处就是每次执行函数时都可以直接为该函数分配栈空间(不需要再解析一遍去获取代码中声明了哪些变量,创建了哪些函数),并且因为代码压缩的原因,代码执行也更快了。

    (2)容错性更好

    变量提升可以在一定程度上提高JS的容错性,看下面的代码:

    a = 1;
    var a;
    console.log(a);
    

    如果没有变量提升,这两行代码就会报错,但是因为有了变量提升,这段代码就可以正常执行。

    虽然,我们在可以开发过程中,可以完全避免这样写,但是有时代码很复杂的时候。可能因为疏忽而先使用后定义了,这样也不会影响正常使用。由于变量提升的存在,而会正常运行。

    总结:

    解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间
    声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行

  • this指向问题

  • hashhistory的区别

  • 闭包的实现原理

    闭包能够访问外部函数的变量,即使变量已经离开它所创建的环境,是因为外部变量会被闭包的作用域对象所持有。闭包这种特性实现了嵌套函数之间数据的隐式传递。

    keys:

    1,作用链域(子对象一级一级向上寻找父对象的变量,父对象的所有变量对子对象是可见的,反之不成立)
    2,垃圾回收机制(违背)
    3,私有化变量
    4, 闭包会在父函数外部改变父函数内部变量的值

    Javascript作用链域?

    答案:
    当需要从局部函数查找某一属性或方法时,如果当前作用域没有找到,就会上溯到上层作用域查找,直至全局函数,这种组织形式就是作用域链。
    全局函数无法查看局部函数的内部细节,但局部函数可以查看其上层的函数细节,直至全局细节。

    闭包的实现原理,其实是利用了作用域链的特性,我们都知道作用域链就是在当前执行环境下访问某个变量时,如果不存在就一直向外层寻找,最终寻找到最外层也就是全局作用域,这样就形成了一个链条。

  • 大量的使用闭包会有什么问题

    闭包造成内存泄漏. 因为闭包就是能够访问外部函数变量的一个函数,而函数是必须保存在内存中的对象,所以位于函数执行上下文中的所有变量也需要保存在内存中,这样就不会被回收,如果一旦循环引用或创建闭包,就会占据大量内存,可能会引起内存泄漏。

    因为使用闭包,可以使函数在执行完后不被销毁,保留在内存中,如果大量使用闭包就会造成内存泄露,内存消耗很大

    ==因此内存泄露会导致内部内存溢出==

    常见内存泄露的原因

    • 全局变量引起的内存泄露
    • 闭包引起的内存泄露:慎用闭包
    • dom清空或删除时,事件未清除导致的内存泄漏
    • 循环引用带来的内存泄露
  • 垃圾回收机制

    1. 垃圾回收机制:js的执行环境会自动对垃圾进行回收,这些垃圾指的是不再使用的变量所占用的内存,垃圾回收也就是释放那些不再使用的变量所占用的内存,收垃圾的过程 会按照固定的时间间隔周期性的执行 垃圾回收

    2. 常用方式:

      1. 标记清除:每声明一个变量,就会对该变量做一个标记(例如标记一个进入执行环境);当代码执行进入另一个环境中时,这时又做一个标记,(例如标记一个离开执行环境),等到垃圾回收执行时,会根据标记来决定要清除哪些变量进行释放内存。
      2. 引用计数:引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值的引用次数减1,当这个值的引用次数变为0的时候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,当垃圾回收的时候,就会将 引用次数为0的进行回收,释放对应的内存。
    3. 垃圾回收的缺陷:其他语言一样,javascript的GC策略也无法避免一个问题:GC时,停止响应其他操作,这是为了安全考虑。

    4. 优化GC:

      1. 分代回收(Generation GC):与Java回收策略思想是一致的。目的是通过区分“临时”与“持久”对象;多回收“临时对象”区(young generation),少回收“持久对象”区(tenured generation),减少每次需遍历的对象,从而减少每次GC的耗时。
      2. 增量GC:这个方案的思想很简单,就是“每次处理一点,下次再处理一点,如此类推。

    ==总结==

    1. 内存没有释放或释放及时会造成内存泄漏。

    2. 垃圾回收机制的常用方式是标记清除和引用计数。

    3. 查看内存泄漏可以通过浏览器和命令行的方式。

  • 宏任务和微任务的执行逻辑

    **原因:**既然JS是单线程的,只能在一条线程上执行,又是如何实现的异步呢? 是通过的事件循环(event loop),理解了event loop机制,就理解了JS的执行机制

    宏任务:script 标签中的整体代码、setTimeout、setInterval、setImmediate、I/0、UI渲染

    微任务:process.nextTick(Node.js)、Promise、Object.observe(不常用)、MutationObserver(Node.js)

    任务优先级:process.nextTick > Promise.then > setTimeout > setImmediate

    ==微任务跟在当前宏任务后面,执行完当前宏任务,微任务就跟上,然后再执行下一个宏任务。==

    keys:

    • JavaScript 事件循环总是从一个宏任务开始执行;
    • 一个事件循环过程中,只执行一个宏任务,但是可能执行多个微任务;
    • 执行栈中的任务产生的微任务会在当前事件循环内执行;
    • 执行栈中的任务产生的宏任务要在下一次事件循环才会执行。

    如果有大量的宏任务和微任务 具体会怎么进行执行呢?

  • async await关键字的原理

牛客模拟面试

说一说进程调度算法有哪些

  • 先来先去服务   
  • 时间片轮转法   
  • 多级反馈队列算法  
  • 最短进程优先   
  • 最短剩余时间优先   
  • 最高响应比优先

map和farEach的区别

forEach和map都是遍历一个数组,但它们的返回值不同。forEach的返回值为undefined,不可以链式调用,而map回调函数的返回值会组成一个新数组,新数组的索引结构和原数组一致,原数组不变。

filter会返回原数组的一个子集,回调函数用于逻辑判断,返回 true则将当前元素添加到返回数组中,否则排除当前元素,原数组不变。

map和filter方法不会改变原数组。

这三个方法都不会对空数组进行检测或执行回调函数。

没有办法终止或者跳出forEach()循环,除非抛出异常,所以想执行一个数组是否满足什么条件,返回布尔值,可以用一般的for循环实现,或者用Array.every()或者Array.some()

从输入页面URL到页面渲染完成大致流程为:

从输入页面URL到页面渲染完成大致流程为:

  • 解析URL
  • 浏览器本地缓存
  • DNS解析
  • 建立TCP/IP连接
  • 发送HTTP请求
  • 服务器处理请求并返回HTTP报文
  • 浏览器根据深度遍历的方式把html节点遍历构建DOM树
  • 遇到CSS外链,异步加载解析CSS,构建CSS规则树
  • 遇到script标签,如果是普通JS标签则同步加载并执行,阻塞页面渲染,如果标签上有defer / async属性则异步加载JS资源
  • 将dom树和CSS DOM树构造成render树
  • 渲染render树

滴滴面试

  1. JS 基本数据类型

Number、String、Boolean、undefined、、Null、Symbol 、bigInt

==重点==Symbol 和 Bigint 简单介绍

Symbol 是一种在ES6 中新添加的数据类型,本质上是一种唯一标识符,可用作对象的唯一属性名,这样其他人就不会改写或覆盖你设置的属性值。

  • 唯一性

  • 隐藏性:for…in,object.keys() 不能访问

    Object.getOwnPropertySymbols(对象)
    返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值

    Symbol.for()
    全局注册并登记,使得相同参数注册的值symbol相等

    Symbol.keyFor()
    通过symbol对象获取到参数值

BigInt可以表示任意大的整数,解决了Number类型只能安全的支持(-(2^53-1)) 和 (2^53-1)之间的整数,超过这个范围就会出现精度缺失的问题。

使用方法:

1.在整数的末尾追加n

2.调用BigInt()构造函数

undefined 和 null 的区别

undefined是访问一个未初始化的变量时返回的值,而null是访问一个尚未存在的对象时所返回的值。因此,可以把undefined看作是空的变量,而null看作是空的对象。

typeof(null) 为什么返回的是 ‘object’

== 和 === 的区别?

这么理解: 当进行双等号比较时候,先检查两个操作数数据类型,如果相同, 则进行 == 比较。 如果不同, 则愿意为你进行一次类型转换, 转换成相同类型后再进行比较。而 === 比较时,如果类型不同,直接就是false。

判断数据类型的方法

Instanceof 的原理

其实 instanceof 主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上即可。 因此, instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype ,如果查找失败,则会返回 false,告诉我们左边变量并非是右边变量的实例。

typeof(NaN) 返回number

Class 和 new

对 Map 和 Set 的理解

Set:

  • 没有重复的key
  • 或者说只有键值,没有键名。
  • 可以遍历,方法有:add、delete、has。

Map:

  • 本质上是键值对的集合,类似集合。
  • 可以遍历,方法很多可以跟各种数据格式转换。
  • 方法有set、get、delete、has

Promise 的理解

Promise 新增的一个方法

Object 和 Map 相互转换

Map.entries是什么样的?

事件循环机制

实际上是存在多个宏任务的, 然而每一次 loop 的时候只会并且先执行”最前面”的宏任务, 然后执行当前 loop 下所有的微任务, 所有微任务完毕之后, 进入下一次 loop, 执行接下来的宏任务, 重复上述过程

  1. let 和 const 的区别

    let与const都是只在声明所在的块级作用域内有效。

    let声明的变量可以改变,值和类型都可以改变,没有限制。

    const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

    对于复合类型的变量,如数组和对象,变量名不指向数据,而是指向数据所在的地址。const命令只是保证变量名指向的地址不变,并不保证该地址的数据不变,所以将一个对象声明为常量必须非常小心。

  2. const 定义的值一定是不能改变的吗?

    const定义的基本数据类型的变量确实不能修改。

    const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。
    对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

  3. 基本数据类型和引用数据类型的根本区别?

    因为基本数据类型保存的是值,而引用类型保存的是地址。

  4. 比较常用的数组方法 map() reduce() find() findIndex() push() …. 哪些可以改变原数组,那些不可以改变

    不会改变原数组

    • concat() 连接两个或更多数组,返回结果
    • every() 检测数组中每个元素是否都符合要求
    • some() 检测数组中是否有元素符合要求
    • filter() 挑选数组中符合条件的并返回符合要求的数组
    • join() 把数组的所有元素放到一个字符串
    • toString() 把数组转成字符串
    • slice() 截取一段数组,返回新数组
    • indexOf 搜索数组中的元素,并返回他所在的位置

    会发生改变

    • push() 把元素添加到数组尾部
    • unshift() 在数组头部添加元素
    • pop() 移除数组最后一个元素
    • shift() 删除数组第一个元素
    • reverse() 在原数组中颠倒元素的顺序
    • splice(index, howmany, item1…intemx) 插入、删除、替换数组元素(返回被删除的数组)
    • sort() 对数组元素进行排序
  5. 数组中的方法如何实现 break

    我们都知道for循环里要跳出整个循环是使用break,但在数组中用forEach循环如要退出整个循环使用break会报错,使用return也不能跳出循环。

    所以我们采用some和every循环进行遍历,some — return true的时候跳出循环,every — return false的时候跳出循环。

  6. arguments 类数组,如何遍历类数组

    1. 普通的for循环来进行遍历
    2. 使用 Array.prototype.call(arrLike,(item)=>{return item.includes(“i”)}) 来进行遍历,换绑this
    3. for-of遍历
  7. 浏览器事件循环机制 和 node 事件循环机制

// 给定有序数组array和数字n,找出n在array中的出现次数
  1. Http 常见状态码

    1xx表示客户端应该继续发送请求

    2xx表示成功的请求

    • 200表示OK,正常返回信息
    • 201表示请求成功且服务器创建了新的资源
    • 202表示服务器已经接受了请求,但还未处理

    3xx表示重定向

    • 301表示永久重定向,请求的网页已经永久移动到新位置
    • 302表示临时重定向
    • 304表示自从上一次请求以来,页面的内容没有改变过

    4xx表示客户端错误

    • 401表示服务器无法理解请求的格式
    • 402表示请求未授权
    • 403表示禁止访问
    • 404表示请求的资源不存在,一般是路径写错了

    5xx表示服务器错误

    • 500表示最常见的服务器错误
    • 503表示服务器暂时无法处理请求
  2. cookie session token 的认证机制

原型链

JavaScript中每个实例对象都有一个私有属性指向它的构造函数的原型对象。该原型对象也是对象,所以他也有一个自己的原型对象,层层向上直到一个对象的原型对象是null

prototype和 __ proto__ 区别

    1. 每个对象都有__proto__属性
    2. 每个构造函数都有一个名为prototype的方法,既然是方法,那么就是一个对象,所以prototype同样带有__proto__属性
    3. 每个对象的__proto__指向它构造函数的prototype

深度赋智

0.1+0.2!=0.3怎么处理

把需要计算的数字升级(乘以10的n次幂)成计算机能够精确识别的整数,等计算完成后再进行降级(除以10的n次幂),即:

(0.1*10 + 0.2*10)/10 == 0.3 //true

大数相加

let a = "9007199254740991";
let b = "1234567899999999999";

function add(a ,b){
   //取两个数字的最大长度
   let maxLength = Math.max(a.length, b.length);
   //用0去补齐长度
   a = a.padStart(maxLength , 0);//"0009007199254740991"
   b = b.padStart(maxLength , 0);//"1234567899999999999"
   //定义加法过程中需要用到的变量
   let t = 0;
   let f = 0;   //"进位"
   let sum = "";
   for(let i=maxLength-1 ; i>=0 ; i--){
      t = parseInt(a[i]) + parseInt(b[i]) + f;
      f = Math.floor(t/10);
      sum = t%10 + sum;
   }
   if(f == 1){
      sum = "1" + sum;
   }
   return sum;
}

数据转换为树型

function buildTree2(array, id_key, parentId_key, isRoot) {
    if (!!!array) return [];
    let idsObj = array.reduce((pre, cur) => Object.assign(pre, {[cur[id_key]]: cur}), {});
    return Object.values(idsObj).reduce((pre, cur) => {
        let parent = idsObj[cur[parentId_key]];
        if (!isRoot(cur, parent)) {
            !!!parent.children && (parent.children = []);
            let children = parent.children;
            !children.includes(cur) && children.push(cur);
        } else {
            pre.push(cur);
        }
        return pre
    }, []);
}
let tree2 = buildTree2(arr, 'id', 'parentId', item => item.parentId === -1);

线程进程区别

进程共享资源

webpack你了解吗?它的打包过程是怎样的?你听过happy-track这个工具吗

diff算法有了解吗?它是怎么工作的?为什么它是操作虚拟DOM?

虚拟dom虚拟DOM

虚拟DOM技术是一个很流行的东西,现代前端开发框架vue和react都是基于虚拟DOM来实现的。

虚拟DOM技术是为了解决一个很重要的问题:浏览器进行DOM操作会带来较大的开销。

1、要知道js本身运行速度是很快的,

2、而js对象又可以很准确地描述出类似DOM的树形结构,

通过使用js描述出一个假的DOM结构,每次数据变化时候,在假的DOM上分析数据变化前后结构差别,找出这个最小差别并且在真实DOM上只更新这个最小的变化内容,这样就极大程度上降低了对DOM的操作带来的性能开销。

上面的假的DOM结构就是虚拟DOM,比对的算法成为diff算法,这是实现虚拟DOM技术的关键。

1、在vue初始化时,首先用JS对象描述出DOM树的结构,

2、用这个描述树去构建真实DOM,并实际展现到页面中,

3、一旦有数据状态变更,需要重新构建一个新的JS的DOM树,

4、对比两棵树差别,找出最小更新内容,

5、并将最小差异内容更新到真实DOM上。

有了虚拟DOM,下面一个问题就是,什么时候会触发更新,接下来要介绍的,就是vue中最具特色的功能–数据响应系统及实现。

列表组件的key可以使用index吗?

讲一下ssr

线程进程区别

进程共享资源

cdn

手写问题

手写promise.all 相关参考

function PromiseAll(promiseArray) {    //返回一个Promise对象
     return new Promise((resolve, reject) => {
        if (!Array.isArray(promiseArray)) {                        //传入的参数是否为数组
            return reject(new Error('传入的参数不是数组!'))
        }
        const res = []
        let counter = 0                         //设置一个计数器
        for (let i = 0; i < promiseArr.length; i++) {
            Promise.resolve(promiseArr[i]).then(value => {
                counter++                  //使用计数器返回 必须使用counter
                res[i] = value
                if (counter === promiseArr.length) {
                    resolve(res)
                }
            }).catch(e => reject(e))
        }
    })
}


手写call www.cnblogs.com/Renyi-Fan/p…

Function.prototype.myCall = function (context, ...args) {
    context = context || window
    args = args ? args : []
    const key = Symbol()
    context[key] = this
    const result = context[key](...args)
    delete context[key]
    return result
}

手写深拷贝blog.csdn.net/Bule_daze/a…

JSON.parse(JSON.stringify(params))可以实现深拷贝

function deepCopy(obj){
    //判断是否是简单数据类型,
    if(typeof obj == "object"){
        //复杂数据类型
        var result = obj.constructor == Array ? [] : {};
        for(let i in obj){
            result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];
        }
    }else {
        //简单数据类型 直接 == 赋值
        var result = obj;
    }
    return result;
}

手写 new juejin.cn/post/684490…

function _new(obj, ...rest){
  const newObj = Object.create(obj.prototype);
  const result = obj.apply(newObj, rest);
  return typeof result === 'object' ? result : newObj;
}


数组去重

function a(arr){
		var newArr = new Set(arr)
		return newArr
	}
	console.log(a([1,2,2,3]))

手写防抖

手写节流

其它新型技术

?有没有使用过mock.js

  • 是一款模拟数据生成器(确保前端独立开发的问题)
  • 生成随机数据,拦截 Ajax 请求。
  • Mock.js这个工具完全可以通过我们设置的规则,来提供数据,从而解决了前端不能独立开发的问题。
  • 而且当对接后端接口的时候,也不需要修改既有代码。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYU9wgc4' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片