前端面试八股文-杂谈

github大厂面试题精选

一、知道虚拟Dom吗?大致聊聊

1、什么是虚拟 DOM (vdom)

 虚拟DOM(Virtual DOM)是指一个虚拟的、内存中的DOM节点树,它是通过JavaScript对象来模拟真实的DOM结构,而不是直接操作真实的DOM。虚拟DOM通常会在每次页面渲染时被创建,通过对虚拟DOM的修改来实现对页面的更新。

2、实现原理

 虚拟DOM的实现原理是在JavaScript中对DOM树进行操作,然后将修改的结果映射到真实的DOM上。这种映射方式可以大大减少DOM操作的次数,从而提高页面性能。因为真实的DOM是非常庞大而复杂的,而且在每次更新时都要重新计算布局和样式,所以频繁地直接操作真实的DOM会非常消耗性能。

  通过使用虚拟DOM,开发者可以在JavaScript中对DOM进行修改,然后由虚拟DOM引擎自动计算出DOM树的差异,最后只需要更新差异部分的真实DOM,以此来实现高效的页面渲染和更新。

3、虚拟 DOM 的优缺点

优点:

降低浏览器性能消耗
因为Javascript的运算速度远大于DOM操作的执行速度,因此,运用patching算法来计算出真正需要更新的节点,最大限度地减少DOM操作,从而提高性能。

在vnode技术出现之前,我们要改变页面展示的内容只能通过遍历查询 dom 树的方式找到需要修改的 dom,然后修改样式行为或者结构,来达到更新 ui 的目的。这种方式相当消耗计算资源,因为每次查询 dom 几乎都需要遍历整颗 dom树。

在vnode技术出现之后,我们建立一个虚拟 dom 对象来对应真实的 dom 树,那么每次 dom 的更改就变成了 js 对象的属性的更改 ,这样一来就能查找 js 对象的属性变化要比查询 dom 树的 性能开销小。

diff算法,减少回流和重绘
通过diff算法,优化遍历,对真实dom进行打补丁式的新增、修改、删除,实现局部更新,减少回流和重绘。

vnode优化性能核心思想,就是每次更新 dom 都尽量避免刷新整个页面,而是有针对性的 去刷新那被更改的一部分 ,来释放掉被无效渲染占用的 gpu,cup性能。同时,也减少了大量的dom操作,减少了浏览器的回流和重绘。

跨平台
虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM ,可以进行更方便地跨平台操作,例如:服务器渲染、weex 开发等等

缺点

首次显示要慢些:
首次渲染大量DOM时,由于多了一层虚拟DOM的计算, 会比innerHTML插入慢

无法进行极致优化:
虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中 无法进行针对性的极致优化。

4、Virtual DOM 真的比操作原生 DOM 快吗

这是一个性能 vs. 可维护性的取舍。框架的意义在于为你掩盖底层的 DOM 操作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护。

没有任何框架可以比纯手动的优化 DOM 操作更快,因为框架的 DOM 操作层需要应对任何上层 API 可能产生的操作,它的实现必须是普适的。

二、理解xss、csrf、ddos攻击原理以及避免方式

1、XSS

XSS(Cross-Site Scripting,跨站脚本攻击)是一种代码注入攻击。攻击者在目标网站上注入恶意代码,当被攻击者登陆网站时就会执行这些恶意代码,这些脚本可以读取 cookie,session tokens,或者其它敏感的网站信息,对用户进行钓鱼欺诈,甚至发起蠕虫攻击等。

XSS避免方式

  • url参数使用encodeURIComponent方法转义
  • 尽量不是有InnerHtml插入HTML内容
  • 使用特殊符号、标签转义符。
2、CSRF

CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。

CSRF避免方式:

  • 添加验证码
  • 使用token
  • 服务端给用户生成一个token,加密后传递给用户
  • 用户在提交请求时,需要携带这个token
  • 服务端验证token是否正确
3、DDoS

DDoS又叫分布式拒绝服务,全称 Distributed Denial of Service,其原理就是利用大量的请求造成资源过载,导致服务不可用。

DDos避免方式

  • 限制单IP请求频率。
  • 防火墙等防护设置禁止ICMP包等
  • 检查特权端口的开放

三、聊聊你认识中的Web性能优化?有哪些点?

性能优化大全:

【前端面试题】10—21道关于性能优化的面试题(附答案) (qq.com)

四、浏览器是如何渲染UI的?

  • 浏览器获取HTML文件,然后对文件进行解析,形成DOM Tree
  • 与此同时,进行CSS解析,生成Style Rules
  • 接着将DOM Tree与Style Rules合成为 Render Tree
  • 接着进入布局(Layout)阶段,也就是为每个节点分配一个应出现在屏幕上的确切坐标
  • 随后调用GPU进行绘制(Paint),遍历Render Tree的节点,并将元素呈现出来

五、浏览器重绘与重排的区别?

重排/回流(Reflow)

当DOM的变化影响了元素的几何信息,浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。表现为重新生成布局,重新排列元素。

重绘(Repaint):

当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。表现为某些元素的外观被改变
单单改变元素的外观,肯定不会引起网页重新生成布局,但当浏览器完成重排之后,将会重新绘制受到此次重排影响的部分

代价

重排和重绘代价是高昂的,它们会破坏用户体验,并且让UI展示非常迟缓,而相比之下重排的性能影响更大,在两者无法避免的情况下,一般我们宁可选择代价更小的重绘。

划重点

『重绘』不一定会出现『重排』,『重排』必然会出现『重绘』。

六、谈谈对vue生命周期的理解?

  • create阶段:vue实例被创建
    beforeCreate: 最初调用触发,创建前,此时data和methods中的数据都还没有初始化,data和events都不能用
    created 创建完毕,data中有值,未挂载,data和events已经初始化好,data已经具有响应式;在这里可以发送请求
  • mount阶段: vue实例被挂载到真实DOM节点
    beforeMount在模版编译之后,渲染之前触发,可以发起服务端请求,去数据,ssr中不可用,基本用不上这个hook
    mounted: 在渲染之后触发,此时可以操作DOM,并能访问组件中的DOM以及$ref,SSR中不可用
  • update阶段:当vue实例里面的data数据变化时,触发组件的重新渲染
    beforeUpdate :更新前,在数据变化后,模版改变前触发,切勿使用它监听数据变化
    updated更新后,在数据改变后,模版改变后触发,常用于重渲染案后的打点,性能检测或触发vue组件中非vue组件的更新
  • destroy阶段:vue实例被销毁
    beforeDestroy:实例被销毁前,组件卸载前触发,此时可以手动销毁一些方法,可以在此时清理事件、计时器或者取消订阅操作
    destroyed: 卸载完毕后触发,销毁后,可以做最后的打点或事件触发操作

七、为什么v-for和v-if不建议用在一起?

渲染顺序问题:v-if 和 v-for 在渲染顺序上存在冲突。Vue.js 的渲染顺序规定,v-for 的优先级高于 v-if。也就是说,在每次循环迭代时,v-if 的条件判断都会执行一次。这可能会导致一些意外的结果,特别是在有大量数据和复杂条件逻辑的情况下。

可读性和维护性:同时使用 v-if 和 v-for 可能会增加代码的复杂性,降低代码的可读性和维护性。两个指令的同时使用可能导致逻辑难以理解和排查错误,特别是在较大的代码库中。

如何解决这个问题:
使用计算属性:将需要根据条件进行过滤的数据处理逻辑放到计算属性中,并在模板中使用该计算属性进行循环渲染。这样可以避免在模板中使用 v-if 和 v-for 同时存在的问题。

数据预处理:在数据传递给模板之前,先对数据进行处理,根据条件将需要渲染的数据进行筛选或过滤,然后再进行循环渲染。

使用 <template> 元素:如果你确实需要在同一个元素上同时使用 v-if 和 v-for,可以使用 <template> 元素来包裹它们。<template> 元素在渲染时不会被保留,可以作为一个容器来使用这两个指令,避免同时使用 v-if 和 v-for 时可能导致的问题。
 

八、v-if和v-show的区别?

共同点

都能控制元素的显示和隐藏;

不同点

实现本质方法不同,v-show本质就是通过控制css中的display设置为none,控制隐藏,只会编译一次;v-if是动态的向DOM树内添加或者删除DOM元素,若初始值为false,就不会编译了。而且v-if不停的销毁和创建比较消耗性能。

如果要频繁切换某节点,使用v-show(切换开销比较小,初始开销较大)。如果不需要频繁切换某节点使用v-if(初始渲染开销较小,切换开销比较大)。

九、React/Vue 项目中 key 的作用

作用

key的作用是为了在diff算法执行时更快的找到对应的节点,提高diff速度,更高效的更新虚拟DOM;
vue和react都是采用diff算法来对比新旧虚拟节点,从而更新节点。在vue的diff函数中,会根据新节点的key去对比旧节点数组中的key,从而找到相应旧节点。如果没找到就认为是一个新增节点。而如果没有key,那么就会采用遍历查找的方式去找到对应的旧节点。一种一个map映射,另一种是遍历查找。相比而言。map映射的速度更快。

副作用

为了在数据变化时强制更新组件,以避免“就地复用”带来的副作用。
当 Vue.js 用 v-for 更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。重复的key会造成渲染错误。

十、vue的路由模式及区别?

Router三种路由模式

hash:使用URL的hash值作为路由。
Vue的默认路由模式。
支持所有浏览器。
history:使用History API:pushState() 和 replaceState() 方法。
HTML5之后支持。
abstract:支持所有 JavaScript 运行环境(包括Node.js 服务器端)
如果发现没有浏览器的 API,路由会自动强制进入这个模式。

hash模式
 

        根据MDN:Location 接口的 hash 属性返回一个 USVString,包含URL标识中的 '#' 和 后面URL片段标识符,被称为 hash。

        例如:http://www.abc.com/#/article,hash 的值为 #/article。

第一个#后的所有字符,都会被浏览器解读为位置标识符,它只用来表示页面位置(一个锚点)。这些字符都不会被发送到服务器端。
单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页。
每次改变#后的部分,都会在浏览器的访问历史中增加一个记录。见:此文
window.location.hash 表示 hash 值。此属性可读可写。
使用 window.addEventListener("hashchange", fun) 可以监听 hash 的变化
原理

        Vue的Router的hash模式的原理是:使用 window.addEventListener("hashchange", fun) 监听 hash 的变化,hash变化之后,根据这个新的hash找到相应的页面,然后更新视图。

优点

后端不需要额外配置
原因:#及之后的字符不会被发到服务器
例如:http://www.abc.com/#/article在Vue Router中有对应的路由,而我直接输入了http://www.abc.com/#/article/id,Vue Router中没有对应路由。
此时并不会报错。
原因:只有http://www.abc.com会被发送到服务器。


缺点

不美观(url中带了个“#”)


history模式

window.history 提供了两类API:

跳到某个浏览记录:back(), forward(), go(),
添加/修改历史记录:pushState(), replaceState()
这些方法都只修改当前url,不会向后端发起请求。

原理

Vue监听url改变这个事件:window.addEventListener('popstate', fun);
Vue在切换页面时,使用pushState(), replaceState()来修改当前的url
切换页面之后,popstate事件被触发,调用相应的回调函数更新视图


优点

美观(url中不带“#”)


缺点

后端需要额外配置
原因:当直接访问一个url时会请求后端。例如:刷新页面、直接通过url访问
例如:http://www.abc.com/article在Vue Router中有对应的路由,而我直接输入了http://www.abc.com/article/id,Vue Router中没有对应路由。
此时会报错:404。
原因:后端没有相应的接口。
解决方案:
后端(Apache 或 Nginx)进行简单的路由配置
Vue配置路由的 404 页面。例如:
const router = new Router({
  mode: 'history',
  routes: [
    { path: '*', component: NotFoundComponent }
  ]
})


abstract模式

        abstract 模式针对的是没有浏览器环境的情况。

        比如 Weex 客户端开发,内部是没有浏览器 API 的,那么 Vue-Router 自身会对环境做校验,强制切换到 abstract 模式。

        如果在 Vue-Router 的配置项中不写 mode 的值,在浏览器环境下会默认启用 Hash 模式,在移动客户端下使用 abstract 模式。

原理

通过 stack 和 index 2个变量,模拟出浏览器的历史调用记录。

hash和history的区别

十一、var let const 的区别

  • var

    • ECMAScript6 增加了let 和 const 之后要尽可能少使用var。因为let 和 const 申明的变量有了更加明确的作用域、声明位置以及不变的值。
    • 优先使用const来声明变量,只在提前知道未来会修改时,再使用let。
  • let

    • 因为let作用域为块作用域!!!!【得要时刻记住这一点】
    • 不能进行条件式声明
    • for循环使用let来声明迭代变量不会导致迭代变量外渗透。
  • const

    • 声明时得直接初始化变量,且不能修改const声明的变量的值
    • 该限制只适用于它指向的变量的引用,如果它一个对象的,则可以修改这个对象的内部的属性。

十二、JavaScript中 数组去重你知道哪些方式?比较常用哪个?

一、利用ES6 Set去重(ES6中最常用)
function unique (arr) {
  return Array.from(new Set(arr))
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
 //[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]

不考虑兼容性,这种去重的方法代码最少。这种方法还无法去掉“{}”空对象,后面的高阶方法会添加去掉重复“{}”的方法。

二、利用for嵌套for,然后splice去重(ES5中最常用)
function unique(arr){            
        for(var i=0; i<arr.length; i++){
            for(var j=i+1; j<arr.length; j++){
                if(arr[i]==arr[j]){         //第一个等同于第二个,splice方法删除第二个
                    arr.splice(j,1);
                    j--;
                }
            }
        }
return arr;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr))
    //[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}]     //NaN和{}没有去重,两个null直接消失了

双层循环,外层循环元素,内层循环时比较值。值相同时,则删去这个值。
想快速学习更多常用的ES6语法,可以看我之前的文章《学习ES6笔记──工作中常用到的ES6语法》。

三、利用indexOf去重
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var array = [];
    for (var i = 0; i < arr.length; i++) {
        if (array .indexOf(arr[i]) === -1) {
            array .push(arr[i])
        }
    }
    return array;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
   // [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}]  //NaN、{}没有去重

新建一个空的结果数组,for 循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则push进数组。

四、利用sort()
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return;
    }
    arr = arr.sort()
    var arrry= [arr[0]];
    for (var i = 1; i < arr.length; i++) {
        if (arr[i] !== arr[i-1]) {
            arrry.push(arr[i]);
        }
    }
    return arrry;
}
     var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
        console.log(unique(arr))
// [0, 1, 15, "NaN", NaN, NaN, {…}, {…}, "a", false, null, true, "true", undefined]      //NaN、{}没有去重

利用sort()排序方法,然后根据排序后的结果进行遍历及相邻元素比对。

五、利用includes
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var array =[];
    for(var i = 0; i < arr.length; i++) {
            if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值
                    array.push(arr[i]);
              }
    }
    return array
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr))
    //[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]     //{}没有去重

六、利用hasOwnProperty
function unique(arr) {
    var obj = {};
    return arr.filter(function(item, index, arr){
        return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
    })
}
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
        console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}]   //所有的都去重了

利用hasOwnProperty 判断是否存在对象属性

七、利用filter
function unique(arr) {
  return arr.filter(function(item, index, arr) {
    //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
    return arr.indexOf(item, 0) === index;
  });
}
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
        console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {…}, {…}]

八、利用递归去重
function unique(arr) {
        var array= arr;
        var len = array.length;

    array.sort(function(a,b){   //排序后更加方便去重
        return a - b;
    })

    function loop(index){
        if(index >= 1){
            if(array[index] === array[index-1]){
                array.splice(index,1);
            }
            loop(index - 1);    //递归loop,然后数组去重
        }
    }
    loop(len-1);
    return array;
}
 var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]

九、利用Map数据结构去重
function arrayNonRepeatfy(arr) {
  let map = new Map();
  let array = new Array();  // 数组用于返回结果
  for (let i = 0; i < arr.length; i++) {
    if(map .has(arr[i])) {  // 如果有该key值
      map .set(arr[i], true); 
    } else { 
      map .set(arr[i], false);   // 如果没有该key值
      array .push(arr[i]);
    }
  } 
  return array ;
}
 var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr))
//[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]

创建一个空Map数据结构,遍历需要去重的数组,把数组的每一个元素作为key存到Map中。由于Map中不会出现相同的key值,所以最终得到的就是去重后的结果

十三、介绍节流防抖原理、区别以及应用

节流

事件触发后,规定时间内,事件处理函数不能再次被调用。也就是说在规定的时间内,函数只能被调用一次,且是最先被触发调用的那次。

防抖

多次触发事件,事件处理函数只能执行一次,并且是在触发操作结束时执行。也就是说,当一个事件被触发准备执行事件函数前,会等待一定的时间(这时间是码农自己去定义的,比如 1 秒),如果没有再次被触发,那么就执行,如果被触发了,那就本次作废,重新从新触发的时间开始计算,并再次等待 1 秒,直到能最终执行!

使用场景:

节流:滚动加载更多、搜索框搜的索联想功能、高频点击、表单重复提交……
防抖:搜索框搜索输入,并在输入完以后自动搜索、手机号,邮箱验证输入检测、窗口大小 resize 变化后,再重新渲染。

/**
 * 节流函数 一个函数执行一次后,只有大于设定的执行周期才会执行第二次。有个需要频繁触发的函数,出于优化性能的角度,在规定时间内,只让函数触发的第一次生效,后面的不生效。
 * @param fn要被节流的函数
 * @param delay规定的时间
 */
function throttle(fn, delay) {
    //记录上一次函数触发的时间
    var lastTime = 0;
    return function(){
        //记录当前函数触发的时间
        var nowTime = Date.now();
        if(nowTime - lastTime > delay){
            //修正this指向问题
            fn.call(this);
            //同步执行结束时间
            lastTime = nowTime;
        }
    }
}

document.onscroll = throttle(function () {
    console.log('scllor事件被触发了' + Date.now());
}, 200); 

/**
 * 防抖函数  一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效
 * @param fn要被节流的函数
 * @param delay规定的时间
 */
function debounce(fn, delay) {
    //记录上一次的延时器
    var timer = null;
    return function () {
       //清除上一次的演示器
        clearTimeout(timer);
        //重新设置新的延时器
        timer = setTimeout(function(){
            //修正this指向问题
            fn.apply(this);
        }, delay); 
    }
}
document.getElementById('btn').onclick = debounce(function () {
    console.log('按钮被点击了' + Date.now());
}, 1000);

 十四、JavaScript中的8种数据类型及区别?

类型

包括 :值类型(基本对象类型)和引用类型(复杂对象类型)

基本类型(值类型)Number(数字),String(字符串),Boolean(布尔),Symbol(符号),null(空),undefined(未定义)在内存中占据固定大小,保存在栈内存中

引用类型(复杂数据类型)Object(对象)、Function(函数)。其他还有Array(数组)、Date(日期)、RegExp(正则表达式)、特殊的基本包装类型(String、Number、Boolean) 以及单体内置对象(Global、Math)等 引用类型的值是对象 保存在堆内存中,栈内存存储的是对象的变量标识符以及对象在堆内存中的存储地址。

使用场景:

Symbol:使用Symbol来作为对象属性名(key) 利用该特性,把一些不需要对外操作和访问的属性使用Symbol来定义
BigInt:由于在 Number 与 BigInt 之间进行转换会损失精度,因而建议仅在值可能大于253 时使用 BigInt 类型,并且不在两种类型之间进行相互转换。

十五、在DOM操作中怎样创建、添加、移除、替换、插入和查找节点?

(1)通过以下代码创建新节点。

createDocument Fragment ()
//创建一个D0M片段
createElement ()
//创建一个具体的元素
createTextNode ()
//创建一个文本节点

(2)通过以下代码添加、移除、替换、插入节点

appendchild()
removechild()
eplacechild ()
insertBefore ()
//并没有 insertAfter()

(3)通过以下代码查找节点。

getElementsByTagName ()
//通过标签名称查找节点
getElementsByName ()
//通过元素的name属性的值查找节点(IE容错能力较强,会得到一个数//组,其中包括id等于name值的节点)
getElementById(
//通过元素Id查找节点,具有唯一性

 

 十六、JavaScript中的this?

  1. 普通函数调用:通过函数名()直接调用:this指向全局对象window(注意let定义的变量不是window属性,只有window.xxx定义的才是。即let a =’aaa’; this.a是undefined)
  2. 构造函数调用:函数作为构造函数,用new关键字调用时:this指向新new出的对象
  3. 对象函数调用:通过对象.函数名()调用的:this指向这个对象
  4. 箭头函数调用:箭头函数里面没有 this ,所以永远是上层作用域this(上下文)
  5. apply和call调用:函数体内 this 的指向的是 call/apply 方法第一个参数,若为空默认是指向全局对象window。
  6. 函数作为数组的一个元素,通过数组下标调用的:this指向这个数组
  7. 函数作为window内置函数的回调函数调用:this指向window(如setInterval setTimeout 等)

十七、GET 和 POST 的区别,你知道哪些?

  1. get是获取数据,post是修改数据

  2. get把请求的数据放在url上, 以?分割URL和传输数据,参数之间以&相连,所以get不太安全。而post把数据放在HTTP的包体内(request body 相对安全)

  3. get提交的数据最大是2k( 限制实际上取决于浏览器), post理论上没有限制。

  4. GET产生一个TCP数据包,浏览器会把http header和data一并发送出去,服务器响应200(返回数据); POST产生两个TCP数据包,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。

  5. GET请求会被浏览器主动缓存,而POST不会,除非手动设置。

  6. 本质区别:GET是幂等的,而POST不是幂等的

    这里的幂等性:幂等性是指一次和多次请求某一个资源应该具有同样的副作用。简单来说意味着对同一URL的多个请求应该返回同样的结果。

正因为它们有这样的区别,所以不应该且不能用get请求做数据的增删改这些有副作用的操作。因为get请求是幂等的,在网络不好的隧道中会尝试重试。如果用get请求增数据,会有重复操作的风险,而这种重复操作可能会导致副作用(浏览器和操作系统并不知道你会用get请求去做增操作)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值