1.get 请求和 post 请求的理解
1.数据提交方式。
get将数据附加在URL后面,post将数据包含在请求的正文中。
2.数据传递量。
因为地址栏长度存在限制,所以get请求长度也就有了限制,而post将数据包含在请求的正文中,
get适用于传递小量数据,post适用于传递大量数据
3.安全性。
因为数据在URL中可见,也可以在浏览器缓存,POST因为数据在请求正文中,相对于GET请求更安全
但是不加密,随便一个监听器都可以把所有的数据监听到
4.缓存。
因为浏览器的文件,如js文件、图片资源基本上都是通过get请求拿到的,浏览器为了节约服务器资源,会对这部分做缓存,
IE对这方面的优化不够好,所以在IE中get请求也可能被缓存下来,解决方式就是?后面加一个时间戳
5.与服务器的握手次数不同
get请求,浏览器会把http header一并发送出去,服务器响应200(返回数据)
post请求多了正文,浏览器先发送http header,服务器响应100,浏览器再发送data,服务器响应200(返回数据)
6.本质都是HTTP协议,
所以post请求地址上?后面也可以拼接参数,服务也能够拿到
2.深浅拷贝的区别,如何深拷贝及注意点
浅拷贝创建一个新的对象,然后将原始对象中的元素复制到新对象中。
然而,如果原始对象中的元素是引用类型,则浅拷贝只会复制这些元素的引用,
而不是创建这些引用指向的对象的副本。
1.JSON.parse(JSON.stringify())
注意点:function丢失,Undefined丢失、正则表达式变为{}、Data变成String、Set类型变Array,Map类型变Array
2.通过递归
注意点:AB互相引用时,会造成一个死循环,例如:
var a = {"aa": 1, "newB": null};
var b = {"bb": 1, "newA": null};
a.newB = b;
b.newA = a;
这种情况下JSON.stringify()会报错
3.箭头函数和普通函数的区别,普通函数 this 指向如何的改变
1.写法不同
2.this的指向,箭头函数不可改变,如果执行call和apply,不生效
3.arguments对象,箭头函数没有arguments对象
4.箭头函数没有原型对象,即prototype,获取为undefined
5.构造函数,即new操作,箭头函数执行new会报错
4.call 和 apply 的区别和使用场景
区别是:传参不同
用法:
1.改变普通函数的this指向
2.可以将类数组转为数组,例:Array.prototype.slice.call(arguments)
2.1 问:为什么这么写就能将类数组转为数组
2.1 答:因为类数组也有length属性,而此方法是浏览器提供的,此方法内部有一个for循环,
方法内部通过this获得for循环对象,而我们改变了this指向,for循环对象就变成了类数组
其实就是对类数组做了一个for循环
5.什么是原型链
1.在JavaScript中,每个对象(除了一些特殊的对象,如null)都有一个原型(prototype),
它是一个指向另一个对象的引用。对象可以通过原型继承属性和方法,这样就形成了原型链
2.具体来说,当我们访问一个对象的属性或方法时,JavaScript首先会检查自身是否有该属性或方法。
如果没有,它会沿着原型链向上查找。当找到该属性或方法时,JavaScript会停止查找并返回该属性或方法的值或引用
6.如何扩展数组的原型对象,使[].abcFn()可以执行
1.低级的写法
Array.prototype.abcFn = function(){
return "可以执行"
}
2.高级的写法,通过Object.defineProperty
Object.defineProperty(Array.prototype, "abcFn", {
writable: false,
enumerable: false,
configurable: false,
value: function () {
return "可以执行"
}
})
提醒:低级的写法可以被 for(var k in a) 循环到abcFn;
问题:如何过滤掉abcFn
答案:通过a.hasOwnProperty方法
7.如何通过原型对象实现 JS 的继承,一共有几种方法,他们的优缺点是什么
1.原型链继承
2.借用构造函数
3.组合继承
4.寄⽣组合继承
8.new 具体做了什么
例如 :
var fn = function () { };
var fnObj = new fn();
过程:
1.创建一个新的空对象 var obj = new object();
2.设置空对象原型 obj.__proto__ = fn.prototype;
3.执行函数,使其this指向为空对象 var result = fn.call(obj);
4.判断返回值类型,如果是值类型,返回创建对象。如果是引用类型,就返回这个引用类型
typeof(result) === "object" ? result : obj
结论:
前端的new操作和java的不一样,虽然行为很类似,但是前端是通过一些模拟方法来实现的。
9.如何判断某个对象是否是另一个对象的实例
通过 instanceof
var aFn = function(){ }
var bFn = function(){ }
var aObj = new aFn();
aObj instanceof aFn --> true
aObj instanceof bFn --> false
var slist = [];
slist instanceof aFn --> false
slist instanceof Array --> true
slist instanceof Object --> true
10.浏览器兼容性问题的理解
兼容性的问题本质就是浏览器识别不了你写的代码,然后无法处理其中的逻辑
java也有兼容性问题,比如低版本的JDK执行不了高版本的java代码,只不过java的环境是开发者部署的,
不像前端,运行环境是由客户决定的。
理解了本质,所以IE兼容也就好处理了,不要使用高版本的语言写法就行了
vue脚手架可以用ES6的语法兼容IE,是因为脚手架是有一个代码编译过程,将ES6语法编译成ES5的语法了
11.什么是闭包
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行
12.Promise 的优点是什么,它和 async/await 的关系是什么
问题1:
1.使异步操作更易于理解、直观和处理,消除了回调地狱的问题
2.支持链式调用,使得多个异步操作可以串行或并行处理,代码可读性更好
3.错误处理更方便
问题2:
async/await 是ES7中的语法糖,用于更优雅地处理异步操作,它本质上是基于Promise的。
它使用async关键字修饰函数,函数内部可以使用await关键字暂停代码执行,等待一个Promise对象被解决后再继续执行。
await表达式会返回Promise的结果。
async修饰函数的函数会返回一个Promise对象
13.什么是变量提升
变量提升是JavaScript中的一种特性,它指的是在代码执行阶段之前,
JavaScript引擎会将变量声明(var)和函数声明(function)提升到当前作用域的顶部,
也就是在代码执行前就会对它们进行处理。
这意味着可以在变量或函数声明之前访问这些变量或函数
14.var a = function(){} 和 function a(){ } 这 2 中写法有什么区别
var a = function() { } 是使用函数表达式来创建一个匿名函数,并将其赋值给变量a
function a(){ } 是使用函数声明来创建一个命名函数,此命名函数会经过变量提升后,赋值给变量a
所以第二种写法,执行方法可以在命名函数上面执行
15.解释下 JavaScript 中的同源策略及如何进行跨域请求
同源策略是一种浏览器安全策略,它用于限制从一个源加载的文档或脚本如何与另一个源的资源进行交互。
源由协议、域名和端口组成,只有在三者完全相同的情况下,才被认为是同源。
同源策略的原则是,如果两个页面的源不同,那么该页面的JavaScript脚本将无法通过常规方式访问或操作另一个源的资源。
这是为了防止恶意网站利用用户的敏感信息进行非法操作。
解决:
1.JSONP
JSONP是一种利用script标签跨域加载资源的方法。
它通过动态创建script标签,并将要请求的数据作为参数传递给目标域上的一个回调函数。
这个回调函数将返回请求的数据,并通过JavaScript的方式进行处理。
因为是通过script处理的,所以这种方式只能使用get请求,而无法使用post。
2.CORS
因为数据是浏览器拦截的,服务可以告诉浏览器那个域不需要拦截。
所以可以通过在服务器端设置响应头来控制跨域请求的机制。
服务器可以通过设置Access-Control-Allow-Origin头来允许特定域的请求访问资源
3.反向代理
因为数据是浏览器拦截的,对于后台如java或者nodejs来说,不存在这么一说,
所以可以设置一个与目标域同源的服务器,用于中转请求。这是目前常用的一种解决方式,
这也是为什么nginx和vue脚手架需要配置代理服务器地址的原因
16.解释一下什么是 html 的事件委托
事件委托是指将事件处理程序绑定到一个共同的父元素上,而不是将事件处理程序绑定到每个子元素上。
通过事件冒泡,父元素可以捕获到子元素触发的事件,从而减少事件处理程序的数量,提高性能。
17.什么是事件冒泡和事件捕获
事件冒泡指的是事件从最具体的元素(事件目标)开始逐级向上传播到最不具体的元素(文档)的过程。
当一个具体元素上的事件被触发时,该事件会按照从内到外的顺序在 DOM 树上进行传播,直到传播到最顶层的父元素或文档。
这意味着如果你在一个元素上设置了事件监听器,那么当这个元素的子元素触发相同的事件时,父元素上的事件处理程序也会被调用。
事件捕获则是相反的过程,事件从最不具体的元素(文档)开始,逐级向下传播到最具体的元素(事件目标)。
在事件捕获阶段,事件会从文档的根节点向下传播至目标元素,然后才会在目标元素上触发。
这个过程在事件冒泡之前执行。
在 DOM 树中,事件的传播过程包含三个阶段
捕获阶段(Capturing Phase): 事件从文档根节点开始向下传播至目标元素。
目标阶段(Target Phase): 事件在目标元素上触发。
冒泡阶段(Bubbling Phase): 事件从目标元素开始向上传播至文档根节点。
18.如果允许客户同时选择 100 张图片,你会怎么去做,或者说考虑的点在哪里
19.有这么一个场景,我需要在一个页面上展示 10 万条数据,客户要求不进行分页展示,你会怎么去做
HTML+CSS
- 介绍下粘性布局(sticky)
该元素并不脱离文档流,仍然保留元素原本在文档流中的位置。
当元素在容器中被滚动超过指定的偏移值时,元素在容器内固定在指定位置。亦即如果你设置了top: 50px,那么在sticky元素到达距离相对定位的元素顶部50px的位置时固定,不再向上移动。
元素固定的相对偏移是相对于离它最近的具有滚动框的祖先元素,如果祖先元素都不可以滚动,那么是相对于viewport来计算元素的偏移量
- 分析比较 opacity: 0、visibility: hidden、display: none 优劣和适用场景
display: none (不占空间,不能点击)(场景,显示出原来这里不存在的结构)
visibility: hidden(占据空间,不能点击)(场景:显示不会导致页面结构发生变动,不会撑开)
opacity: 0(占据空间,可以点击)(场景:可以跟transition搭配)
- 如何用 CSS 实现一个三角形
div {
width: 0;
height: 0;
border: 10px solid red;
border-top-color: transparent;
border-left-color: transparent;
border-right-color: transparent;
}
- 如何实现一个自适应的正方形
.square {
width: 10vw;
height: 10vw;
background: red;
}
.square {
width: 10%;
padding-bottom: 10%;
height: 0;
background: red;
}
- less 和 sass 区别
相同点:
LESS和SCSS都是css的预处理器,可以拥有变量,运算,继承,嵌套的功能,使用两者可以使代码更加的便于阅读和维护。
都可以通过自带的插件,转成相对应的css文件。
都可以参数混入,可以传递参数的class,就像函数一样
嵌套的规则相同,都是class嵌套class
不同点:
声明和使用变量:LESS用@符号,SCSS用$符号表示
变量插值:LESS采用@{XXXX}的形式,SCSS采用${XXXX}的形式
scss支持条件语句:SCSS可以使用if{}else,for循环等等,LESS不支持
应用外部css文件方式:SCSS应用的css文件名必须以‘_’开头(下划线),文件名如果以下划线开头的话,sass会认为改文件是一个应用文件,不会将它转成css文件
颜色函数:调整色相的话,LESS使用spin()的函数;SCSS使用名为adjust_hue()的函数
引用父选择器&符号:LESS和SCSS都可以使用&符号表示父选择器,但是SCSS的&符号只能出现在一个组合选择器的开始位置,LESS则没有这个限制
VUE 相关题
- 介绍下 Vue 的特点
数据驱动
Vue 是一个构建数据驱动的 Web 界面的渐进式框架。
Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。核心是一个响应的数据绑定系统。
关于 Vue 的优点,主要有响应式编程、组件化开发、虚拟 DOM
响应式编程
这里的响应式不是 @media 媒体查询中的响应式布局,而是指 Vue 会自动对页面中某些数据的变化做出响应。这也就是 Vue 最大的优点,通过 MVVM 思想实现数据的双向绑定,让开发者不用再操作 DOM 对象,有更多的时间去思考业务逻辑。
组件化开发
Vue 通过组件,把一个单页应用中的各种模块拆分到一个一个单独的组件(component)中,我们只要先在父级应用中写好各种组件标签(占坑),并且在组件标签中写好要传入组件的参数(就像给函数传入参数一样,这个参数叫做组件的属性),然后再分别写好各种组件的实现(填坑),然后整个应用就算做完了。
组件化开发的优点:提高开发效率、方便重复使用、简化调试步骤、提升整个项目的可维护性、便于协同开发。
虚拟 DOM
在传统开发中,用 JQuery 或者原生的 JavaScript DOM 操作函数对 DOM 进行频繁操作的时候,浏览器要不停的渲染新的 DOM 树,导致在性能上面的开销特别的高。
而 Virtual DOM 则是虚拟 DOM 的英文,简单来说,他就是一种可以预先通过 JavaScript 进行各种计算,把最终的 DOM 操作计算出来并优化,由于这个 DOM 操作属于预处理操作,并没有真实的操作 DOM,所以叫做虚拟 DOM。最后在计算完毕才真正将 DOM 操作提交,将 DOM 操作变化反映到 DOM 树上。
- Proxy 相比 defineProperty 的优势在哪里
Vue3.x 改用 Proxy 替代 Object.defineProperty
原因在于 Object.defineProperty 本身存在的一些问题:
Object.defineProperty 只能劫持对象属性的 getter 和 setter 方法。
Object.definedProperty 不支持数组(可以监听数组,不过数组方法无法监听自己重写),更准确的说是不支持数组的各种 API(所以 Vue 重写了数组方法。
而相比 Object.defineProperty,Proxy 的优点在于:
Proxy 是直接代理劫持整个对象。
Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。
目前,Object.definedProperty 唯一比 Proxy 好的一点就是兼容性,不过 Proxy 新标准也受到浏览器厂商重点持续的性能优化当中。
- vue3 中的 setup 含义
setup 是 Vue3 中引入的一个新的选项,用于替代 Vue2 中的 beforeCreate 和 created 钩子函数。
setup 选项是一个函数,它在组件实例被创建之前执行,并返回一个包含状态和方法等配置信息的对象。
具体来说,setup 选项的作用包括:
响应式数据:使用 Vue3 的 reactive、ref、computed 等函数,可以定义组件的响应式数据,并将其暴露给模板或其他组件进行使用;
普通数据:通过 JavaScript 对象或数组等常规方式定义组件的非响应式数据,也可以在 setup 函数中进行初始化;
方法:定义组件的方法并将其暴露给模板或其他组件进行使用;
生命周期钩子:定义组件的生命周期钩子函数,并在合适的时机执行相关操作;
事件处理:定义组件的事件处理函数,并将其传递给模板或其他组件进行绑定。
需要注意的是,在 setup 函数内部,不能直接访问 this,因为此时组件实例尚未创建完毕。如果你需要在 setup 函数中访问组件实例或在模板中访问 props、data 等属性,可以使用Vue3 提供的 getCurrentInstance() 函数和相关 API 进行操作。
-
vue2 和 vue3 生命周期
Vue2 生命周期 Vue3 生命周期 描述 beforeCreate setup 组件创建之前,执行初始化任务 created setup 组件创建完成,访问数据、获取接口数据 beforeMount onBeforeMount 组件挂载之前 mounted onMounted 组件挂载完成,DOM 已创建,访问数据或 DOM 元素,访问子组件 beforeUpdate onBeforeUpdate 未更新,获取更新前所有状态 updated onUpdated 组件挂载完成,DOM 已创建,访问数据或 DOM 元素,访问子组件 beforeDestroy onBeforeUnmount 未更新,获取更新前所有状态 destroyedonUnmounted 组件销毁之后 -
vue2 和 vue3 父子组件传值
Vue2 父子组件传值 Vue3 父子组件传值 描述 props defineProps 子组件接收父组件传来的值 $emit defineEmits 子组件接收父组件传来的方法值 defineExpose 子组件暴露自己的属性或方法 vue3 如何获取 this?
可以使用getCurrentInstance函数来获取当前组件的实例
- v-if 和 v-for 哪个优先级更高?
vue2中v-for的优先级> v-if。先执行循环再判断条件,浪费性能
vue3中v-if的优先级>v-for。v-if执行时,调用的变量还不存在,导致异常
- vue-router 中的 router 与 route 的区别
router是vue-router的实例,是一个全局的路由对象 ,包含了路由的跳转的方法、钩子函数等等。
route是一个局部的对象, 当前活跃的, 包含了 path 、 params 、query 、 matched 、 name 等路由信息参数
- 数据量很大的列表,如何优化性能?
- 虚拟列表
- 懒加载
性能优化
- 减少 DOM 操作
- 减少 HTTP 请求:缓存
- 减少重绘重排
- 分离读写操作:即在使用 offset 相关属性进行读操作之前,完成所有改变 DOM 的写操作。
- 集中改变样式:将样式统一写在一个类名上,再将该类名加到属性上。
- 离线修改 dom:在要操作 dom 之前,通过 display 隐藏 dom,当操作完成之后,才将元素的 display 属性为可见;或者通过使用 DocumentFragment 创建一个 dom 碎片,在它上面批量操作 dom,操作完成之后,再添加到文档中,这样只会触发一次重排
- 设置 position 属性为 absolute 或 fixed
- 启用 gpu 加速:transform: translate3d(10px, 10px, 0);
-
减少内存占用:
- 及时释放不需要的引用和资源。例如,移除无用的 DOM 元素、取消事件监听器、清除定时器等。
- 减少全局变量的使用。全局变量会一直存在于内存中,增加了内存占用的风险。可以将变量封装在函数内部或使用模块化开发的方式。
- 使用对象池技术。对象池可以缓存已经创建的对象,避免重复创建和销毁,从而减少内存占用。
- 减少不必要的数据复制。例如,使用引用传递而不是值传递。
- 使用轻量级的库和框架。大型库和框架往往包含了大量的功能和代码,容易导致内存占用过高。
- 避免循环引用。如果两个对象相互引用,且它们都没有被引用时,它们将无法被垃圾回收器回收,导致内存泄漏。
- 避免不必要的引用:确保在不再需要对象时将其引用设置为 null。这将使垃圾回收机制能够将其释放。
- 合理使用缓存。在某些情况下,缓存可以提高性能,但是也需要注意缓存的过期时间,避免过度缓存。
- 优化 DOM 操作:频繁的 DOM 操作会导致内存问题。建议使用 document fragments 或虚拟 DOM 来减少 DOM 操作次数。
- 使用事件委托:使用事件委托可以减少事件处理程序的数量,并减少内存使用。
- 垃圾回收机制的手动干预:尽管浏览器通常会自动进行垃圾回收,但是在某些情况下,手动干预可以帮助解决内存问题。可以使用 window.performance.memory API 或 Chrome 开发者工具的 Memory 面板来检查内存使用情况,并调用 window.gc() 手动触发垃圾回收。
减少 CPU、GPU 计算:缓存
-
减少代码体积:
- 利用 TerserPlugin 插件压缩和混淆代码。
- 使用 Webpack 的代码分割功能,将代码分割为更小的块,并在需要时按需加载。
- 使用 url-loader 和 image-webpack-loader 插件来优化和压缩图片,减小图片的体积。
- 通过配置 Webpack 的 optimization 选项中的 usedExports 为 true,启用 Tree Shaking 功能,消除未使用的代码。
- 使用 cache-loader 或 hard-source-webpack-plugin 插件,配置 Webpack 的缓存功能,避免重复的文件处理操作,提高构建速度。
- 使用 webpack-bundle-analyzer 插件分析打包后的代码体积和模块依赖关系,找出体积较大的模块,并进行优化。
- 对于稳定的第三方库或框架,使用 DLLPlugin 将它们提前打包成单独的文件,避免每次构建都重新打包这些库,提高打包速度。
- 使用 HappyPack 插件将模块的转译任务分解给多个子进程并行处理,以加快编译速度。
- 使用 DefinePlugin 插件将环境变量注入到 Webpack 的构建过程中,以消除开发和生产环境下的无用代码。
- 配置 Webpack 的 NoEmitOnErrorsPlugin 插件,以在发生错误时跳过输出阶段,减少不必要的输出。
算法题
-
你和你的朋友,两个人一起玩游戏:桌子上有一堆石头,每次你们轮流拿掉 1 - 3 块石头。 拿掉最后一块石头的人就是获胜者。你作为先手。 编写一个函数,来判断你是否可以在给定石头数量的情况下赢得游戏。
答案:如果堆中石头的数量 nn 不能被 4 整除,那么你总是可以赢得 Nim 游戏的胜利。
推理
让我们考虑一些小例子。显而易见的是,如果石头堆中只有一块、两块、或是三块石头,那么在你的回合,你就可以把 全部石子拿走,从而在游戏中取胜。而如果就像题目描述那样,堆中恰好有四块石头,你就会失败。因为在这种情况下 不管你取走多少石头,总会为你的对手留下几块,使得他可以在游戏中打败你。因此,要想获胜,在你的回合中,必须 避免石头堆中的石子数为 4 的情况。
同样地,如果有五块、六块、或是七块石头,你可以控制自己拿取的石头数,总是恰好给你的对手留下四块石头,使他 输掉这场比赛。但是如果石头堆里有八块石头,你就不可避免地会输掉,因为不管你从一堆石头中挑出一块、两块还是 三块,你的对手都可以选择三块、两块或一块,以确保在再一次轮到你的时候,你会面对四块石头。
显然,它以相同的模式不断重复 n=4,8,12,16,\dotsn=4,8,12,16,…,基本可以看出是 4 的倍数var canWinNim = function (n) { return n % 4 !== 0; };