前端面试-XX与XX的区别

2021/6/24 Interview

# 箭头函数和普通函数的区别

  • 箭头函数和普通函数的样式不同,箭头函数语法更加简洁、清晰,箭头函数是=>定义函数,普通函数是function定义函数。
  • 箭头函数会捕获其所在上下文的this值,作为自己的this值,定义的时候就确定并固定了。
  • 箭头函数不能作为构造函数使用,也不能使用new关键字(因为箭头函数没有自己的this,它的this其实是继承了外层执行环境中的this,且this指向永远不会改变,作为构造函数其的this要是指向创建的新对象)。
  • 箭头函数没有自己的arguments。在箭头函数中访问arguments实际上获得的是外层局部(函数)执行环境中的值。
  • callapplybind 并不会影响其this的指向。
  • 箭头函数没有原型prototype
  • 箭头函数不能当作Generator函数,不能使用yield关键字。

# var,let和const之间的区别

  • 变量提升方面

    • var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined
    • letconst不存在变量提升问题(注意这个‘问题’后缀,其实是有提升的,只不过是letconst具有一个暂时性死区的概念,即没有到其赋值时,之前就不能用),即它们所声明的变量一定要在声明后使用,否则报错。
  • 块级作用域方面

    • var不存在块级作用域
    • letconst存在块级作用域
  • 声明方面

    • var允许重复声明变量
    • letconst在同一作用域不允许重复声明变量。其中const声明一个只读的常量(因为如此,其声明时就一定要赋值,不然报错)。一旦声明,常量的值就不能改变。

TIPS

如果const声明了一个对象obj,对象里的属性是可以改变的。

因为const声明的obj只是保存着其对象的引用地址,只要地址不变,就不会出错。

使用Object.freeze(obj)冻结obj,就能使其内的属性不可变,但它有局限,就是obj对象中要是有属性是对象,该对象内属性还能改变,要全不可变,就需要使用递归等方式一层一层全部冻结。

  • var:遇到有var的作用域,在任何语句执行前都已经完成了声明和初始化,也就是变量提升而且拿到undefined的原因由来

  • function:声明、初始化、赋值一开始就全部完成,所以函数的变量提升优先级更高

  • let:解析器进入一个块级作用域,发现let关键字,变量只是先完成声明,并没有到初始化那一步。此时如果在此作用域提前访问,则报错xx is not defined,这就是暂时性死区的由来。等到解析到有let那一行的时候,才会进入初始化阶段。如果let的那一行是赋值操作,则初始化和赋值同时进行。const、class都是同let一样的道理

  • var,会直接在栈内存里预分配内存空间,然后等到实际语句执行的时候,再存储对应的变量,如果传的是引用类型,那么会在堆内存里开辟一个内存空间存储实际内容,栈内存会存储一个指向堆内存的指针

  • let,是不会在栈内存里预分配内存空间,而且在栈内存分配变量时,做一个检查,如果已经有相同变量名存在就会报错

  • const,也不会预分配内存空间,在栈内存分配变量时也会做同样的检查。不过const存储的变量是不可修改的,对于基本类型来说你无法修改定义的值,对于引用类型来说你无法修改栈内存里分配的指针,但是你可以修改指针指向的对象里面的属性

# Bigint和Number的区别

Number类型的数字有精度限制,数值的精度只能到 53 个二进制位(相当于 16 个十进制位, 正负9007199254740992),大于这个范围的整数,就无法精确表示了。

Bigint没有位数的限制,任何位数的整数都可以精确表示。但是其只能用于表示整数,且为了与Number进行区分,BigInt 类型的数据必须添加后缀n。BigInt 可以使用负号(-),但是不能使用正号(+)。

另外Number类型的数字和Bigint类型的数字不能混合计算。

12n+12; //报错

# 基本数据类型和引用数据类型的区别

  • 基本数据类型

    • 基本数据类型的值是不可变的,这里你就可以联想到,是不是所有关于字符串和数字的方法都是带有返回值的,而不是改变原字符串或数字
    • 基本数据类型不可以添加属性和方法,虽然不会报错,但也只是一瞬间转为了相应包装对象,操作完又转化回原基本数据类型,不会保存结果。
    • 基本数据类型的赋值是简单赋值,基本数据类型的比较是值的比较。
    • 基本数据类型是存放在栈区的
  • 引用数据类型

    • 引用类型的值是可以改变的,例如对象就可以通过修改对象属性值更改对象。
    • 引用类型可以添加属性和方法。
    • 引用类型的赋值是对象引用,即声明的变量标识符,存储的只是对象的指针地址。
    • 引用类型的比较是引用(指针地址)的比较。
    • 引用类型是同时保存在栈区和堆区中的,栈区保存变量标识符和指向堆内存的地址。

# defer和async的区别

大家应该都知道在script标签内有这两个属性asyncdefer,例如

<script type="text/javascript" src="xxx.js" async="async"></script>

<script type="text/javascript" src="xxx.js" defer="defer"></script>
1
2
3
  • defer:用于开启新的线程下载脚本文件,并使脚本在文档解析完成后执行。
  • async:HTML5新增属性,用于异步下载脚本文件,下载完毕立即解释执行代码。

defer:中文意思是延迟。用途是表示脚本会被延迟到整个页面都解析完毕后再运行。因此,在<script>元素中设置defer属性,相当于告诉浏览器立即下载,但延迟执行。

HTML5规范要求脚本按照它们出现的先后顺序执行,因此第一个延迟脚本会先于第二个延迟脚本执行,但执行脚本之间存在依赖,需要有执行的先后顺序时,就可以使用defer,延迟执行。我觉得把script脚本放在body底部和defer差不多。

async:中文意思是异步,这个属性与defer类似,都用于改变处理脚本的行为。同样与defer类似,async只适用于外部脚本文件,并告诉浏览器立即下载文件。但与defer不同的是,标记为async的脚本并不保证按照它们的先后顺序执行。

指定async属性的目的是不让页面等待两个脚本下载和执行,从而异步加载页面其他内容,这使用于之间互不依赖的各脚本。

看到这里,就能知道其的一些作用了

当网页交给浏览器的HTML解析器转变成一系列的词语(Token)。解释器根据词语构建节点(Node),形成DOM树。因为JavaScript代码可能会修改DOM树的结构,所以节点是JavaScript代码的话,就需要停止当前DOM树的创建,直到JavaScript的资源加载并被JavaScript引擎执行后才继续DOM树的创建。

这里就会产生阻塞,出现白屏问题(白屏问题优化有很多方面,这里就脚本阻塞这一小点),我们就可以使用asyncdefer属性来解决JavaScript脚本阻塞问题。

当然最稳妥的办法还是把script标签放置在body的底部,没有兼容性问题,不会因此产生白屏问题,没有执行顺序问题。

# async,await对比promise的优缺点

  • async/await优点:

    • 它做到了真正的串行的同步写法,代码阅读相对容易
    • 对于条件语句和其他流程语句比较友好,可以直接写到判断条件里面
    function a() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(222);
        }, 2222);
      });
    }
    async function f() {
      try {
        if (await a() === 222) {
          console.log('yes, it is!'); // 会打印
        }
      } catch (err) {
        // ...
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    • 处理复杂流程时,在代码清晰度方面有优势
  • async/await缺点:

    • 无法处理promise返回的reject对象,要借助try/catch
    • await可能会导致性能问题,因为await会阻塞代码,也许之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去了并发性。
    // promise
    Promise.all([ajax1(), ajax2()]);
    
    1
    2
    • try/catch内部的变量无法传递给下一个try/catch,Promisethen/catch内部定义的变量,能通过then链条的参数传递到下一个then/catch,但是async/awaittry内部的变量,如果用letconst定义则无法传递到下一个try/catch,只能在外层作用域先定义好。

    async/await确确实实是解决了promise一些问题的。更加灵活的处理异步

  • promise的一些问题:

    • 一旦执行,无法中途取消,链式调用多个then中间不能随便跳出来
    • 错误无法在外部被捕捉到,只能在内部进行预判处理,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
    • Promise内部如何执行,监测起来很难,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

# GET和POST的区别

  • GET 是将参数写在 URL 中 ? 的后面,并用 & 分隔不同参数;而 POST 是将信息存放在 Message Body 中传送,参数‘不会’显示在 URL 中(Restful规范中是这样,但POST在有需要时可以把参数放URL里)。GET方式需要使用Request.QueryString来取得变量的值,而POST方式通过Request.Form来获取变量的值。也就是说GET是通过地址栏来传值,而POST是通过提交表单来传值。

  • GET请求提交的数据有长度限制(HTTP 协议本身没有限制 URL 及正文长度,对 URL 的限制大多是浏览器和服务器的原因),POST请求没有内容长度限制。

  • GET请求返回的内容会被浏览器缓存起来。而每次提交POST请求,浏览器不会缓存POST请求返回的内容。

  • GET对数据进行查询,POST主要对数据进行增删改!简单说,GET是只读,POST是写。

  • 关于安全性,GET 请求方式从浏览器的 URL 地址就可以看到参数;所以POST更安全,其实无论是 GET 还是 POST 其实都是不安全的,因为 HTTP 协议是明文传输,只要拦截封包便能轻易获取重要资讯。想要安全传输资料,必须使用SSL/TLS来加密封包,也就是 HTTPS。

    那为什么推崇使用POST来处理敏感数据呢?

    因为GET的记录会保存在浏览器,上网日志中,而使用POST,因为数据不会记录存储在浏览器的记录和网址访问记录中,这样会有更大的安全性。

  • 一个误区 说GET产生一个TCP数据包;POST产生两个TCP数据包

    其说法:对于GET方式的请求,浏览器会把http header和data一并发送出去,服务端响应200,请求成功。

    对于POST方式的请求,浏览器会先发送http header给服务端,告诉服务端等一下会有数据过来,服务端响应100 continue,告诉浏览器我已经准备接收数据,浏览器再POST发送一个data给服务端,服务端响应200,请求成功。

    为其正名:上面所说的POST会比GET多一个TCP包其实不太严谨。多发的那个expect 100 continue header报文,是由客户端对http的POSTGET的请求策略决定的,目的是为了避免浪费资源,如带宽,数据传输消耗的时间等等。所以客户端会在发送header的时候添加expect 100去探探路,如果失败了就不用继续发送data,从而减少了资源的浪费。所以是否再发送一个包取决了客户端的实现策略,和GET/POST并没什么关系。有的客户端比如fireFox就只发送一个包。

扩展问题

  1. 请问GET请求能把参数传到body里面吗?

    答案是可以的。HTTP协议里说的是,GET是从服务器取回数据,POST是发送数据,HTTP请求有header,有body。但是实现怎么样,协议就不管了。HTTPClient,curl,postman,浏览器等这些都是实现,实现上并不保证能传输、接收GET的body数据。

    然而协议里确实说了哪些方法带body是没有意义并可能会产生问题,所以你硬给GET加一个body也是属于不遵循规范的,尽管可能成功但是后果自担。所以说GET不能带body是正确的说法。由于HTTP/1.1是基于文本的协议,所以头后空一行后的数据都是body,所以协议本身未作限制,但是有一个语义上的建议--不应在GET请求中放body。

    如果你尝试利用缓存,你可能会遇到问题。代理不会在GET正文中查看参数是否对响应产生影响。但是也可以使用ETag/Last-Modified头字段来辅助处理缓存。

    The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI. GET方法意味着检索由请求URI标识的任何信息(以实体的形式)。

  2. 在浏览器里如何让GET请求携带body呢?

    不行。从协议方面来说,GET是可以带body的,但是不赞成这么做,所以好多工具并没有去提供支持,例如postman选择GET请求时body选项禁用(新版本已经支持了)。基本上也不要用GET来携带body数据。XMLHttpRequest内部就把body给移除了,也就意味着ajax没戏。可以使用的好像有curl或者后端的某些请求。

# 用框架和不用框架的区别,vue和react的区别

首先说说用框架和不用框架的区别:(以使用框架的角度看)

框架好处:

  • 使用框架工具写项目,在浏览器中代码依然是原生的HTML CSS JS。而框架帮开发者做了很多事情,开发者只关注业务逻辑就可以,极大的加快了开发速度。例如前端框架根本上是解决了UI 与状态同步问题,频繁操作 DOM 性能低下,中间步骤过多,易产生 bug且不易维护,而且心智要求较高不利于开发效率的一系列阻碍
  • 组件化: 其中以 React 的组件化最为彻底,甚至可以到函数级别的原子组件,高度的组件化可以是我们的工程易于维护、易于组合拓展。
  • 天然分层: JQuery 时代的代码大部分情况下是面条代码,耦合严重,现代框架不管是 MVC、MVP还是MVVM 模式都能帮助我们进行分层,代码解耦更易于读写。
  • 生态: 现在主流前端框架都自带生态,不管是数据流管理架构还是 UI 库都有成熟的解决方案

框架缺点:

  • 代码臃肿,使用者使用框架的时候会将整个框架引入,而框架封装了很多功能和组件,使用者必须按照它的规则使用,而实际开发中很多功能和组件是用不到的。
  • 框架迭代更新速度非常快,需要时间熟悉它。

说说Vue和React的区别:说说 Vue 和 React 的区别 (opens new window)

相同点

  • 都有组件化思想。
  • 都支持服务器端渲染。
  • 都有Virtual DOM(虚拟dom)。
  • 数据驱动视图。
  • 都有支持native的方案:Vue的weex、React的React native。
  • 都有自己的构建工具:Vue的vue-cli、React的Create React App。

区别

  • Vue双向绑定,修改数据自动更新视图,而React单向数据流,需要手动setState
  • Vue template结构表现分离,React用jsx结构表现融合,html/css都可以写到js里。
  • 都可以通过props进行父子组件数据传递,只是Vue props要声明,React不用声明可能直接使用。
  • Vue可以用插槽,React是万物皆可props。
  • Vue2利用基本都是Mixin,React可以用高阶函数、自定义hook实现。
  • React hook是根据调用顺序来确定下一次重新渲染时的state是来源于哪个,所以有一些限制,比如不能在循环/条件判断/嵌套函数里使用,而且必须在函数最顶层调用hook。
  • Vue3 hook是基于响应式实现的,它是声明在setup里,一次组件实例化只调用一次setup,而React每次重新渲染都要重新调用,而且可以在循环/条件判断/嵌套函数里使用,并且正因为是基于响应式实现的,还自动实现了依赖收集,而React需要手动传入依赖。

响应式:

  • Vue2响应式的特点就是依赖收集,数据可变,自动派发更新,初始化时通过Object.defineProperty递归劫持data所有属性添加getter/setter,触发getter的时候进行依赖收集,修改时触发setter自动派发更新找到引用组件重新渲染。
  • Vue3响应式使用原生Proxy重构了响应式,一是Proxy不存在响应式存在的缺陷,二是性能更好,不仅支持更多的数据结构,而且不再一开始递归劫持对象属性,而是代理第一层对象本身。运行时才递归,用到才代理,用effect副作用来代替Vue2里的watcher,用一个依赖管理中心trackMap来统一管理依赖代替Vue2中的Dep,这样也不需要维护特别多的依赖关系,性能上取得很大进步。
  • 相比Vue的自动化,React则是基于状态,单向数据流,数据不可变,需要手动setState来更新,而且当数据改变时会以组件根为目录,默认全部重新渲染整个组件树,只能额外用pureComponent/shouldComponentUpdate/useMemo/useCallback等方法来进行控制,更新粒度更大一些。

Diff算法:

vue和react的diff算法

# cookies和session的区别

  • 存储位置不同:cookie的数据信息存放在客户端浏览器上,session的数据信息存放在服务器上。
  • 存储容量不同:单个cookie保存的数据<=4KB,一个站点最多保存20个cookie,而对于session来说并没有上限,但出于对服务器端的性能考虑,session内不要存放过多的东西,并且设置session删除机制。
  • 存储方式不同:cookie中只能保管ASCII字符串,并需要通过编码方式存储为Unicode字符或者二进制数据。session中能够存储任何类型的数据,包括且不限于string,integer,list,map等。
  • 隐私策略不同:cookie对客户端是可见的,别有用心的人可以分析存放在本地的cookie并进行cookie欺骗,所以它是不安全的,而session存储在服务器上,对客户端是透明的,不存在敏感信息泄漏的风险。
  • 有效期上不同:开发可以通过设置cookie的属性,达到使cookie长期有效的效果。session依赖于名为JSESSIONID的cookie,而cookie JSESSIONID的过期时间默认为-1,只需关闭窗口该session就会失效,因而session不能达到长期有效的效果。
  • 服务器压力不同:cookie保管在客户端,不占用服务器资源。对于并发用户十分多的网站,cookie是很好的选择。session是保管在服务器端的,每个用户都会产生一个session。假如并发访问的用户十分多,会产生十分多的session,耗费大量的内存。
  • 跨域支持上不同:cookie支持跨域名访问。session不支持跨域名访问。

# 宏任务和微任务有什么区别

JavaScript中事件循环

# fetch,Ajax,axios区别

  • Ajax是什么:Ajax是(Asynchronous JavaScript and XML)的缩写。现在,允许浏览器与服务器通信而无须刷新当前页面的技术都被叫做Ajax。核心使用XMLHttpRequest对象。

  • axios是什么:axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范。

  • fetch是什么:Fetch被称为下一代Ajax技术,采用Promise方式来处理数据。是一种简洁明了的API,比XMLHttpRequest更加简单易用。fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象。

    • 语法简洁,更加语义化

    • 基于标准 Promise 实现,支持 async/await

    • 同构方便,使用 isomorphic-fetch (opens new window)

    • 更加底层,提供的API丰富(request, response)

    • 脱离了XHR,是ES规范里新的实现方式

    • fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。

    • fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: 'include'})

    • fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费

    • fetch没有办法原生监测请求的进度,而XHR可以

主要区别是 axios、fetch请求后都支持Promise对象API,ajax只能用回调函数。

// axios
axios({
  method: 'post',
  url: '/user/12345',
  data: {
    firstName: 'Fred',
    lastName: 'Flintstone',
  },
})
  .then((response) => {
    console.log(response);
  })
  .catch((error) => {
    console.log(error);
  });

// fetch
try {
  let response = await fetch(url);
  let data = response.json();
  console.log(data);
} catch (e) {
  console.log("Oops, error", e);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# TCP和UDP的区别

  • TCP 是面向连接的,UDP 是无连接的即发送数据前不需要先建立链接。
  • TCP 提供可靠的服务。也就是说,通过 TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达; UDP 尽最大努力交付,即不保证可靠交付。并且因为 TCP 可靠, 面向连接,不会丢失数据因此适合大数据量的交换。
  • TCP 是面向字节流,UDP 面向报文,并且网络出现拥塞不会使得发送速率降低(因此会出现丢包,对实时的应用比如 IP 电话和视频会议等)。
  • TCP 只能是 1 对 1 的,而UDP 支持 1 对 1,1 对多。
  • TCP 的首部较大为 20 字节,而 UDP 只有 8 字节。
  • TCP 是面向连接的可靠性传输,而 UDP 是不可靠的。

# js中的堆和栈,栈和队列有什么区别

堆(heap)和栈(stack)的区别:

  • 堆:队列优先,先进先出;由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
  • 栈:先进后出;动态分配的空间 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。

栈和队列的区别:

  • 栈只允许在表尾一端进行插入和删除,队列只允许在表尾一端进行插入,在表头一端进行删除。
  • 栈是先进后出,队列是先进先出。

# WebSocket和HTTP有什么区别

相同点

  • 都是一样基于TCP的,都是可靠性传输协议。
  • 都是应用层协议。

不同点

  • WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息。HTTP是单向的。
  • WebSocket是需要握手进行建立连接的。
特性 HTTP WebSocket
协议类型 无状态的请求-响应协议 全双工协议
连接方式 每次请求建立新连接(HTTP/1.1支持持久连接) 一次握手后保持长连接
数据传输 传输的数据通常采用HTTP报文格式,包含请求行、请求头、响应头等,一般采用JSON或XML。头部信息较大 传输的数据是纯二进制或文本格式,没有额外的HTTP报文格式。头部信息较小
通信模式 客户端发起请求,服务器返回响应 客户端和服务器可以随时发送数据
性能 每次请求需要建立连接,开销较大 建立连接后开销较小
适用场景 传统Web应用,API调用 实时通信,如聊天、在线游戏
协议升级 不支持 通过HTTP握手升级为WebSocket
安全性 依赖SSL/TLS协议 本身支持SSL/TLS
  • HTTP适合传统的请求-响应场景,如网页浏览、API调用。
  • WebSocket适合实时通信场景,如聊天应用、在线游戏、实时数据推送。

WebSocket的工作流程可以分为两个阶段:握手阶段和通信阶段。

  • 握手阶段

    • 客户端发送一个特殊的HTTP请求(Upgrade请求),请求将现有的HTTP连接升级为WebSocket连接。
    • 服务器接收到请求后,会验证请求的合法性,并发送一个响应,接受将连接升级为WebSocket连接。
    • 成功握手后,原来的HTTP连接将被转换为WebSocket连接,此时可以开始双向通信。
  • 通信阶段

    • 一旦WebSocket连接建立,客户端和服务器可以随时发送数据,而不需要每次都重新建立连接。
    • 数据以帧的形式进行传输,每个帧包含一个操作码(Opcode),指示数据类型(如文本、二进制等),以及数据内容。
    • WebSocket连接可以持续一段时间,直到其中一方关闭连接。

# HTTP和HTTPS的区别

  • HTTP明文传输,数据都是未加密的,安全性较差,HTTPS(SSL+HTTP)数据传输过程是加密的,安全性较好。
  • 使用HTTPS协议需要到CA(Certificate Authority,数字证书认证机构)申请证书,一般免费证书较少,因而需要一定费用。
  • HTTP页面响应速度比HTTPS快,主要是因为HTTP使用TCP三次握手建立连接,客户端和服务器需要交换3个包,而HTTPS除了TCP的三个包,还要加上SSL握手需要的9个包,所以一共是12个包。
  • HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
  • HTTPS其实就是建构在SSL/TLS之上的HTTP协议,所以,要比较HTTPS比HTTP要更耗费服务器资源。

# px,em,rem,vw,vh区别

  • px: px就是pixel的缩写,意为像素。px就是一张图片最小的一个点,一张位图就是千千万万的这样的点构成的。
  • em: 参考物是父元素的font-size,具有继承的特点。如果自身定义了font-size按自身来计算(浏览器默认字体是16px),整个页面内1em不是一个固定的值。
  • rem: css3新单位,相对于**根元素html(网页)**的font-size,不会像em那样,依赖于父元素的字体大小,而造成混乱。
  • vw: css3新单位,viewpoint width的缩写,视窗宽度,1vw等于视窗宽度的1%。
  • vh: css3新单位,viewpoint height的缩写,视窗高度,1vh等于视窗高度的1%。

# bind,call,apply区别

  • 三者都可以改变函数的this对象指向。
  • 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window(node端指向global)。
  • 三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入。
  • bind改变this指向后不会立即执行,而是返回一个永久改变this指向的函数便于稍后调用;apply, call则是立即调用

# caller和callee的区别

  • Function.caller

    非标准: 该特性是非标准的,请尽量不要在生产环境中使用它!

    返回调用指定函数的函数。如果一个函数f是在全局作用域内被调用的,则f.callernull,相反,如果一个函数是在另外一个函数作用域内被调用的,则f.caller指向调用它的那个函数。

    function callerDemo() {
      if (callerDemo.caller) {
        const a = callerDemo.caller.toString();
        console.log(a);
      } else {
        console.log('this is a top function');
      }
    }
    function handleCaller() {
      callerDemo();
    }
    
    handleCaller(); // "function handleCaller() { callerDemo();}"
    callerDemo(); // this is a top function
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
  • arguments.callee

    calleearguments对象的一个属性。它可以用于引用该函数的函数体内当前正在执行的函数。这在函数的名称是未知时很有用,例如在没有名称的函数表达式(也称为“匿名函数”)内。

    警告:在严格模式下,第5版 ECMAScript (ES5) 禁止使用 arguments.callee()。当一个函数必须调用自身的时候, 避免使用 arguments.callee(), 通过要么给函数表达式一个名字,要么使用一个函数声明.

    function calleeDemo() {
      console.log(arguments.callee);
    }
    calleeDemo();
    // 返回函数 function calleeDemo() {console.log(arguments.callee);}
    
    1
    2
    3
    4
    5

# 301和302有什么区别

  • 301 Moved Permanently: 被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一。如果可能,拥有链接编辑功能的客户端应 当自动把请求的地址修改为从服务器反馈回来的地址。除非额外指定,否则这个响应也是可缓存的。
  • 302 Found: 请求的资源现在临时从不同的 URI 响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在 Cache-Control 或 Expires 中进行了指定的情况下,这个响应才是可缓存的。

字面上的区别就是 301 是永久重定向,而 302 是临时重定向。

301 比较常用的场景是使用域名跳转。302 用来做临时跳转, 比如未登陆的用户访问用户中心被重定向到登录页面

# 进程线程协程的区别

进程线程协程

# JavaScript和TypeScript的区别

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

# localstorage,sessionstorage,cookie的区别

  • 相同点是都是保存在浏览器端、且同源的
  • cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递,而sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下
  • 存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
  • 数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭之前有效;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭
  • 作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的
  • webStorage(webstorage是本地存储,存储在客户端,包括localStorage和sessionStorage)支持事件通知机制,可以将数据更新的通知发送给监听者
  • webStorage的api接口使用更方便

# HTTP1.0/1.1/2.0的不同

HTTP1.0,1.1,2.0的区别

# MongoDB和MySQL的区别

数据库 MongoDB MySQL
数据库模型 非关系型 关系型
存储方式 以类JSON的文档的格式存储 不同引擎有不同的存储方式
查询语句 MongoDB查询方式(类似JavaScript的函数) SQL语句
数据处理方式 基于内存,将热数据存放在物理内存中,从而达到高速读写 不同引擎有自己的特点
成熟度 新兴数据库,成熟度较低 成熟度高
广泛度 NoSQL数据库中,比较完善且开源,使用人数在不断增长 开源数据库,市场份额不断增长
事务性 仅支持单文档事务操作,弱一致性 支持事务操作
占用空间 占用空间大 占用空间小
join操作 MongoDB没有join MySQL支持join

# width的100%与auto的区别

width: auto

  • 子元素(包括content+padding+border+margin)撑满整个父元素的content区域
  • 子元素有margin、border、padding时,会减去子元素content区域相对应的width值
  • 父元素的content = 子元素(content + padding + border + margin)

width: 100%

  • 强制将子元素的content区域 撑满 父元素的content区域
  • 子元素有margin、border、padding时,不改变子元素content区域的width,而是溢出父盒子,保持原有值
  • 父元素的content = 子元素的content

# 标准盒模型和怪异盒模型的区别

box-sizing: content-box;(默认值)

标准盒模型,又叫W3C标准盒模型:contentWidth = width,即此时的宽高是内容区的宽高

box-sizing: border-box;

怪异盒模型,又叫IE6混杂盒模型:contentWidth = width - 2*padding - 2*border,即此时的宽高包括了padding和border,内容区的宽高需减去两侧padding和border

# display:none和visibility:hidden和opacity:0的区别

  • display: none隐藏后的元素不占据任何空间,而visibility: hidden隐藏后的元素空间依旧保留,opacity: 0隐藏后也占空间
  • 只有opacity: 0依旧会监听事件,其余两个隐藏后无法监听事件
  • visibility具有继承性,给父元素设置visibility: hidden;子元素也会继承这个属性。但是如果重新给子元素设置visibility: visible,则子元素又会显示出来。display: noneopacity都不能继承,有株连性
  • visibility: hiddenopacity: 0不会影响计数器的计数,例如在一组ol,li列表中隐藏某一个li元素,visibility: hiddenopacity: 0虽然让一个元素不见了,但是其计数器仍在运行。这和display: none完全不一样
  • CSS3的transition支持visibilityopacity属性,但是并不支持display,由于transition可以延迟执行,因此可以配合visibility使用纯css实现hover延时显示效果。visibility会立即显示,隐藏时会延时,opacity可以延时显示和隐藏
  • display: none会触发reflowrepaint,但是visibility: hidden只会触发repaintopacity: 0会触发css3硬件加速,不触发回流重绘,大大减少性能消耗

# Sass和Less的区别

相同之处

  • 混入(Mixins)——class中的class;
  • 参数混入——可以传递参数的class,就像函数一样,Sass用逗号分隔参数,Less用分号;
  • 嵌套规则——class中嵌套class,从而减少重复的代码;
  • 运算——CSS中用上数学;
  • 颜色功能——可以编辑颜色;
  • 名字空间(namespace)——分组样式,从而可以被调用;
  • 作用域——局部修改样式;
  • JavaScript赋值——在CSS中使用JavaScript表达式赋值。

不同之处

  • 环境不同:Sass基于Ruby写的,服务端处理,Less基于Node写的,客户端处理
  • 变量命名:Sass使用$,Less使用@
  • 输出设置,Less没有输出设置,Sass提供4中输出选项:nested(嵌套,默认格式), compact(紧密型,每条样式占一行), compressed(压缩) 和 expanded(像是手写的样式)。
  • Sass支持条件语句,可以使用@if{}@else{}@for{}@each@while循环、@extend继承、@import引用等等,而Less不支持。
  • Sass支持数据结构:@list数组、@map对象,也包括stringnumberfunction

# Sass中@include和@extend的区别

  • @include是引入@mixin定义的混合,会直接把@mixin里面的代码拷贝到@include里面,可能会导致生成重复的样式,并没有起到提取公用的效果,导致生成的CSS过大
  • @extend是将一个选择器下的所有样式继承给另一个选择器,相同的样式编译后只会出现一次

扩展:%定义占位符选择器,不会被编译到CSS文件中

%log {
  display: block;
  color: black;
}

.log-debug {
  @extend %log;
  border: 1px solid blue;
}

.log-error {
  @extend %log;
  border: 3px dotted red;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

编译后

.log-error, .log-debug {
  display: block;
  color: black;
}

.log-debug {
  border: 1px solid blue;
}

.log-error {
  border: 3px dotted red;
}
1
2
3
4
5
6
7
8
9
10
11
12

# CSS中link和@import的区别

  • link是XHTML标签,无兼容问题;@import是在CSS2.1提出的,低版本的浏览器不支持(IE5以上才能识别)。
  • link是XHTML标签,除了加载CSS外,还可以定义RSS等其他事务;@import属于CSS范畴,只能加载CSS。
  • link引用CSS时,在页面载入时同时加载;@import需要页面网页完全载入以后加载。
  • 可以通过JS操作DOM,插入link标签来改变样式;由于DOM方法是基于文档的,无法使用@import的方式插入样式。
  • link最大限度支持并行下载,@import过多嵌套导致串行下载,出现FOUC(浏览器样式闪烁或者叫做无样式内存闪烁)
  • link方式的样式的权重高于@import的权重。

不太理解网上都说的这个link方式引入的样式权重更高。

  1. 如果是单纯的两个分开写,在head标签里面写两个引入,哪个在后面哪个生效,这个和普通的权重判断一样

    <head>
      <link rel="stylesheet" href="1.css" />
      <style>
        @import url(2.css);
      </style>
    </head>
    
    1
    2
    3
    4
    5
    6
  2. 如果是link的文件里面同时写了@import,此时的@import必须写在文件顶部才能生效,后面如果存在相同样式,会覆盖掉@import中引入的,即便它是后加载进来的,可能是因为这个有人说link的权重更高吧

@import最优写法

<style>
  /* Windows IE4/ NS4, Mac OS X IE5, Macintosh IE4/IE5/NS4不识别 */
  @import 'style.css';
  /* Windows IE4/ NS4, Macintosh IE4/NS4不识别 */
  @import "style.css";
  /* Windows NS4, Macintosh NS4不识别 */
  @import url(style.css);
  /* Windows NS4, Mac OS X IE5, Macintosh IE4/IE5/NS4不识别 */
  @import url('style.css');
  /* Windows NS4, Macintosh NS4不识别 */
  @import url("style.css");
</style>

1
2
3
4
5
6
7
8
9
10
11
12
13

由上分析知道,@import url(style.css)@import url("style.css")是最优的选择,兼容的浏览器最多。从字节优化的角度来看@import url(style.css)最值得推荐。

# 浏览器和Node事件循环的区别

参考文章:async/await 在chrome 环境和 node 环境的 执行结果不一致,求解? (opens new window)

其中一个主要的区别在于浏览器的event loopnodejsevent loop 在处理异步事件的顺序是不同的。nodejs中有micro event;其中Promise属于micro event 该异步事件的处理顺序就和浏览器不同。

nodejs V11.0以前,nodejs的事件队列会先处理的宏任务,之后的版本这两者之间的顺序就相同了,产生差异的根本原因是V8引擎的区别,而不是浏览器与Node的区别。

一般来说,当遇到Chrome和Node.js在JavaScript运行方面有差异,应以最新的Chrome的行为为准。虽然Chrome和Node.js都使用V8为其JavaScript引擎,但两者的V8更新策略不同。Chrome每次升级会同时更新到V8的最新版。而Node更新小版本时V8也只更新小版本,只有Node更新大版本时才会更新V8大版本。所以绝大部分时候Node的V8会比同时期的Chrome的V8要落后。

  • Node端,microtask 在事件循环的各个阶段之间执行。执行一个宏任务时会把里面的代码执行完之后,产生的微任务并不会在此轮事件循环中执行,而是会去执行下一个宏任务,宏任务执行完后再回到微任务队列执行微任务
  • 浏览器端,microtask 在事件循环的 macrotask 执行完之后执行,即每当一个宏任务执行完成后,在执行下一个宏任务之前会清空当前的微任务队列,即使再次产生了微任务,即必须保证在执行下一次宏任务时,微任务队列是空的

# gif,jpg,png,webp的区别

参考文章:聊一聊几种常用web图片格式:gif、jpg、png、webp (opens new window)

格式 优点 缺点 适用场景
gif 文件小,支持动画、透明,无兼容性问题 只支持256(2^8)种颜色 色彩简单的logo、icon、动图
jpg 色彩丰富,文件小 有损压缩,反复保存图片质量下降明显 色彩丰富的图片/渐变图像
png 无损压缩,支持透明,简单图片尺寸小 不支持动画,色彩丰富的图片尺寸大 logo/icon/透明图
webp 文件小,支持有损和无损压缩,支持动画、透明 浏览器兼容性不好 支持webp格式的app和webview

TIPS

JPG与JPEG的区别:

没有区别。全名、正式扩展名是JPEG。但因DOS、Windows 95等早期系统采用的8.3命名规则只支持最长3字符的扩展名,为了兼容采用了.jpg。也因历史习惯和兼容性考虑,.jpg目前更流行。类似html和htm的区别

# isNaN与Number.isNaN的区别

  • isNaN()是ES5的方法,Number.isNaN()是ES6的方法
  • 可能有人会认为isNaN()直译为“是不是NaN”,其本意不是,isNaN()本意是通过Number方法把参数转换成数字类型,如若转换成功,则返回false,反之返回true,它只是判断参数是否能转成数字,不能用来判断是否严格等于NaN。如果要判断某个值是否严格等于NaN不能用这个方法
  • ES6提供了Number.isNaN()方法用来判断一个值是否严格等于NaN,它会首先判断传入的值是否为数字类型,如不是,直接返回false。不会出现类型转换
  1. 为什么NaN !== NaN

    NaN只是Number上的一个静态属性。比如Number('echo')会得到NaN,它只是为了告诉你这个值不是一个数字,一种表示方法,而非一个精准有效的值,因此NaN不能参与计算,也无法与自身比较。

  2. 什么情况下产生NaN

    // 当Number提供的类型转换方法在解析一个值却无法返回数字时
    Number('echo'); // NaN
    
    parseInt('echo123'); // NaN
    parseInt('123echo'); // 123
    
    parseFloat('时间跳跃123.1'); // NaN
    parseFloat('123.1时间跳跃'); // 123.1
    
    // 计算中使用- / *运算符,参与计算的值转换类型失败时
    1 - '听风是风'; // NaN
    1 * '123时间跳跃'; // NaN
    1 / 'echo123'; // NaN
    
    // 两个数字0相除也会得到NaN
    0 / 0; // NaN
    1 / 0; // Infinity
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
  3. isNaN方法的含义,如何判断一个值严格等于NaN

    全局方法isNaN()会先把传入的参数调用一次Number(),只是判断这个参数能不能转换成数字,如果能返回false,不能返回true,比如传入非数字开头的字符串会返回true

    isNaN(NaN); // true
    Number.isNaN(NaN); // true
    
    isNaN('qweqwe'); // true
    Number.isNaN('qweqwe'); // false
    
    1
    2
    3
    4
    5
  4. 手动实现

    // MDN提供的Polyfill
    function isNaN1(value) {
      return typeof value === 'number' && isNaN(value);
    }
    
    // NaN是JS中唯一一个自身不相等的存在
    function isNaN2(value) {
      return value !== value;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

TIPS

相同的ES6提供的Number.isFinite(value)与全局isFinite()目的一样,是为了解决全局方法会对传入的参数进行转换

和全局的 isFinite() 函数相比,这个方法不会强制将一个非数值的参数转换成数值,这就意味着,只有数值类型的值,且是有穷的(finite),才返回 true。

Number.isFinite(Infinity); // false
Number.isFinite(NaN); // false
Number.isFinite(-Infinity); // false

Number.isFinite(0); // true
Number.isFinite(2e64); // true

Number.isFinite('0'); // false, would've been true with global isFinite('0')
Number.isFinite(null); // false, would've been true with global isFinite(null)
1
2
3
4
5
6
7
8
9

MDN提供的Polyfill

function isFinite1(value) {
  return typeof value === 'number' && isFinite(value);
}
1
2
3

# 观察者模式与发布订阅模式的区别

观察者模式(Observer pattern),属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主体对象在状态变化时,会通知所有的观察者对象。

发布-订阅模式(Publish–subscribe pattern),消息的发送方,叫做发布者(publishers),消息不会直接发送给特定的接收者,叫做订阅者。意思就是发布者和订阅者不知道对方的存在。需要一个第三方组件,叫做信息中介,它将订阅者和发布者串联起来,它过滤和分配所有输入的消息。换句话说,发布-订阅模式用来处理不同系统组件的信息交流,即使这些组件不知道对方的存在。需要一个第三方组件,叫做信息中介,它将订阅者和发布者串联起来。

# ESLint和Prettier的区别

参考文章:彻底搞清楚ESLint和Prettier的职责关系 (opens new window)

ESLint、StyleLint、TSLint(已废弃)都属于Linters,它们通过对代码的AST进行分析,并按照一系列可配置的规则,为用户提供代码校验的功能。他们的规则主要分为两大类:Formatting Rules和Code-quality Rules。通常,Linters的客户端还会提供修复功能(--fix),我们常用的IDE借助这些客户端便可以实现代码自动修复的功能

Linters的自动修复是基于上述两种规则:Formatting和Code-quality。在Code-quality上,Linters做的做够好了,像no-unused-vars、no-extra-bind、no-undef等。但是在Formatting上,Linters虽然能在一定程度上保证代码的格式,像max-len、no-mixed-spaces-and-tabs、keyword-spacing等, 但是在书写工业化代码的时候,我们会有更高的要求:保证团队的代码风格完全一致。

在不超出max-len的条件下,如下两种写法Linters都是允许的:

// 小明写的
const {a, b, c} = obj
// 小李写的
const {
  a,
  b,
  c,
} = obj
1
2
3
4
5
6
7
8

Prettier是一个多语言支持的代码格式化工具,它也是通过AST解析代码,然后以一个特定的格式输出格式化后的代码。相比Linters,Prettier没有那么多针语言语法的规则,而是一个纯粹的代码格式化工具,在Prettier看来,任何东西都是可以格式化的。

如何解决两者规则冲突问题

有两种思路:

  1. 先用Prettier格式化,再用Linters格式化
  2. 使用Linters按照Prettier的规则格式化(最佳实践)

对于第1种思路,一般是使用prettier-eslint这个库,按照code -> prettier -> eslint --fix的流程格式化代码,因此它不得不格式化两次才能完成操作,会有性能问题,所以目前已经不推荐这个方案了。

第2种,Prettier将会作为Linters中Formatting Rules的完全替代品参与到代码格式化的过程中。为了达成这个目标,我们有两件事需要做:

  • 禁用掉Linters中所有与Prettier冲突的Formatting Rules
  • 针对Prettier自身的规则,使用Prettier进行格式化

# 正向代理和反向代理

参考文章:什么是正向代理与反向代理 (opens new window)

正向代理是从客户端的角度出发,服务于特定用户(比如说一个局域网内的客户)以访问非特定的服务;反向代理正好与此相反,从服务端的角度出发,服务于非特定用户(通常是所有用户),已访问特定的服务。

为什么使用代理服务器?

  • 提高访问速度:由于目标主机返回的数据会存放在代理服务器的硬盘中,因此下一次客户再访问相同的站点数据时,会直接从代理服务器的硬盘中读取,起到了缓存的作用,尤其对于热门网站能明显提高访问速度。
  • 防火墙作用:由于所有的客户机请求都必须通过代理服务器访问远程站点,因此可以在代理服务器上设限,过滤掉某些不安全信息。同时正向代理中上网者可以隐藏自己的IP,免受攻击。
  • 突破访问限制:互联网上有许多开发的代理服务器,客户机在访问受限时,可通过不受限的代理服务器访问目标站点,通俗说,我们使用的翻墙浏览器就是利用了代理服务器,可以直接访问外网。

正向代理(forward proxy),一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并制定目标(原始服务器),然后代理向原始服务器转发请求并将获得的内容返回给客户端,客户端才能使用正向代理。我们平时说的代理就是指正向代理。

A向C借钱,由于一些情况不能直接向C借钱,于是A想了一个办法,他让B去向C借钱,这样B就代替A向C借钱,A就得到了C的钱,C并不知道A的存在,B就充当了A的代理人的角色。

反向代理(Reverse Proxy),以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求的客户端,此时代理服务器对外表现为一个反向代理服务器。

A向B借钱,B没有拿自己的钱,而是悄悄地向C借钱,拿到钱之后再交给A,A以为是B的钱,他并不知道C的存在。

正向代理和反向代理的区别

位置不同

  • 正向代理,架设在客户机和目标主机之间;
  • 反向代理,架设在服务器端;

代理对象不同

  • 正向代理,代理客户端,服务端不知道实际发起请求的客户端;
  • 反向代理,代理服务端,客户端不知道实际提供服务的服务端;

# CRLF,LF,CR的区别

  • CR:Carriage Return,对应ASCII中转义字符\r,表示回车

  • LF:Linefeed,对应ASCII中转义字符\n,表示换行

  • CRLF:Carriage Return & Linefeed,\r\n,表示回车并换行

  • Windows操作系统采用两个字符来进行换行,即CRLF;

  • Unix/Linux/Mac OS X操作系统采用单个字符LF来进行换行;

  • 另外,MacIntosh操作系统(即早期的Mac操作系统)采用单个字符CR来进行换行。

据野史记载,在很久以前的机械打字机时代,CR和LF分别具有不同的作用:LF会将打印纸张上移一行位置,但是保持当前打字的水平位置不变;CR则会将“Carriage”(打字机上的滚动托架)滚回到打印纸张的最左侧,但是保持当前打字的垂直位置不变,即还是在同一行。

当CR和LF组合使用时,则会将打印纸张上移一行,且下一个打字位置将回到该行的最左侧,也就是我们今天所理解的换行操作。

随着时间的推移,机械打字机渐渐地退出了历史舞台,当初的纸张变成了今天的显示器,打字机的按键也演变为了如今的键盘。在操作系统出现的年代,受限于内存和软盘空间的不足,一些操作系统的设计者决定采用单个字符来表示换行符,如Unix的LF、MacIntosh的CR。他们的意图都是为了进行换行操作,只是当初并没有一个国际标准(或者其他原因,鬼知道),所以才有这样字符上的不同。

LF和CRLF之间的不同经常会导致不同会导致使用不同系统的同事之间的代码冲突问题。在你使用git拉取代码的时候,git会自动将代码当中与你当前系统不同的换行方式转化成你当前系统的换行方式,从而造成这种冲突。

window系统解决办法:

  1. 修改git全局配置,禁止git自动将LF转换成CRLF, 命令:

    git config --global core.autocrlf false
    
    1
  2. 修改编辑器的用户配置,例如.vscode/setting.json

    "files.eol": "\n", // 文件换行使用LF方式
    
    1
  3. .editorconfig文件

    end_of_line = lf
    
    1
  4. .gitattributes 配置

    * text=auto eol=lf
    
    1

# Vue中computed与方法和watch的区别

官方文档:计算属性与侦听器 (opens new window)

计算属性与方法

export default {
  name: 'Temp',
  data() {
    return {
      message: 'Hello',
    };
  },
  computed: {
    reversedMessage1() {
      return this.message.split('').reverse().join('');
    },
  },
  methods: {
    reversedMessage2() {
      return this.message.split('').reverse().join('');
    },
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

reversedMessage1reversedMessage2()得到的结果都是一样的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要message还没有发生改变,多次访问 reversedMessage1计算属性会立即返回之前的计算结果,而不必再次执行函数。相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。

计算属性与侦听器

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么Vue通过watch选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

区别

  • computed是计算值,注重结果,并且会产生一个新的属性,与data里面的用法一样,watch是观察的动作,注重过程
  • 应用:computed就是简化tempalte里面{{}}计算和处理props$emit的传值,watch监听props$emit或本组件的值执行异步操作
  • computed具有缓存性,页面重新渲染值不变化,计算属性会立即返回之前的计算结果,而不必再次执行函数(getter),watch不存在缓存,数据变化即执行
  • computed其实只是纯数据操作,需要返回数据结果,不建议在里面去修改外部的某个值,虽然使用了也没问题(可能eslint报错),如果有需要请使用getset方法。但是watch就可以监测某个数据发生了变更进行一系列的回调操作,不仅仅局限于返回数据
  • computed会在vue实例化过程中执行一次(前提是这个计算属性在template有用到,如果没有用到,这个computed永远不会执行,包括vue初始化时);watch在vue初始化时不会执行(除非设置immediate: true),只会在监听的数据变化时执行
  • 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是多对一或者一对一,一般用computed

# 表单的readonly和disabled的区别

在表单元素中,readonlydisable有类似之处,因为它们都可以将一些表单元素设置为"不可用"状态,但还是有着明显的区别

  1. 设置两个属性的外观不一样,readonly外观没有变化,disabled会变灰。
  2. readonly只是将元素设置为只读,其他操作正常,用户仍然可以使用tab键切换到该字段,还可以选中或拷贝其文本,而disabled阻止对元素的一切操作,例如获取焦点,点击事件等等。
  3. readonly属性只对表单元素的<input><textarea>有效,但是input也只对输入框类型(text/password/email/search/url/number)、日期时间选择(date/month/week/time/datetime/datetime-local)有效,对按钮(button/radio/checkbox/submin/reset)、颜色(color)、滑块(range)、文件(file)、图片(image)都没有效果,依然可以操作。而disabled属性对所有的表单元素都会有效。
  4. 表单提交时,设置readonly的表单元素value值依然会被提交,而设置disabled的表单元素值不会被提交。

element-ui的表单组件很多都是自己另外实现的,例如:

  • el-color-picker:并不是用input[type="color"]实现的,而是div背景颜色渐变,所以可用readonly控制
  • el-input给定type="textarea"可以渲染成多行文本框,是内部判断渲染的textarea标签
  • el-radioel-checkbox虽说内部都是原生input实现,但是readonlydisabled时是通过v-if控制渲染的另外的元素

# ES5和ES6中继承的区别

  1. ES5里的构造函数就是一个普通的函数,可以使用new调用,也可以直接调用,而ES6的class不能当做普通函数直接调用,必须使用new操作符调用

  2. ES5的原型方法和静态方法默认是可枚举的,而class的静态方法默认不可枚举,静态属性是可枚举的,如果想要获取不可枚举的属性可以使用Object.getOwnPropertyNames方法

  3. class的所有方法(包括静态方法和实例方法)都没有原型对象prototype,所以也没有[[construct]],不能使用new来调用

  4. ES6子类可以直接通过__proto__找到父类,而ES5是指向Function.prototype

    • ES6:Sub.__proto__ === Sup
    • ES5:Sub.__proto__ === Function.prototype
  5. ES5的继承,实质是先创造子类的实例对象this,然后再执行父类的构造函数给它添加实例方法和属性(不执行也无所谓)。而ES6的继承机制完全不同,实质是先创造父类的实例对象this(当然它的__proto__指向的是子类的prototype),然后再用子类的构造函数修改this。这就是为啥使用class继承在constructor函数里必须调用super,因为子类压根没有自己的this,另外不能在super执行前访问this的原因也很明显了,因为调用了super后,this才有值。

  6. class不存在变量提升,所以父类必须在子类之前定义

  7. class声明内部会启用严格模式

# 以下3个判断数组的方法的区别和优劣

  • Object.prototype.toString.call()

    每一个继承Object的对象都有toString方法,如果toString方法没有重写的话,会返回[Object type],其中type为对象的类型。但当除了Object类型的对象外,其他类型直接使用toString方法时,会直接返回都是内容的字符串,所以我们需要使用call或者apply方法来改变toString方法的执行上下文。

    依赖于Object.prototype.toString并且Function.prototype.call没有被改变,并且Symbol.toStringTag没有被改变

  • instanceof

    instanceof的内部机制是通过判断对象的原型链中是不是能找到类型的prototype。

  • Array.isArray()

    • 当检测Array实例时,Array.isArray优于instanceof,因为Array.isArray可以检测出iframes
    • Array.isArray是ES5新增的方法,当不存在Array.isArray,可以用Object.prototype.toString.call实现。

如果是精心设计的假数组

const fakeArray = {
  length: 0,
  __proto__: Array.prototype,
  [Symbol.toStringTag]: 'Array',
};

console.log(Object.prototype.toString.call(fakeArray)); // [object Array]
console.log(fakeArray instanceof Array); // true
console.log(Array.isArray(fakeArray)); // false
1
2
3
4
5
6
7
8
9

如果是修改了真实数组的原型或者Symbol.toStringTag

const realArray = [];
realArray[Symbol.toStringTag] = 'Function';
realArray.__proto__ = Function.prototype;

console.log(Object.prototype.toString.call(realArray)); // [object Function]
console.log(realArray instanceof Array); // false
console.log(Array.isArray(realArray)); // true
1
2
3
4
5
6
7

另外还可以通过arr.constructor === Array来判断是不是数组,但是constructor会被修改,不能保证准确,所以非特殊情况首选Array.isArray

# Object.keys和Object.getOwnPropertyNames的区别

  • Object.keys():返回对象自身的可枚举的属性的数组
  • Object.getOwnPropertyNames():所有对象自身的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)的数组
  • Object.getOwnPropertySymbols():返回一个包含给定对象所有Symbol属性的数组
  • for...in:以任意顺序迭代一个对象的除Symbol以外的可枚举属性,包括继承的可枚举属性

# JS中0,正零(+0)和负零(-0)的区别

  • 0 === +0 === -0,三个中两两是严格相等的
  • 1/0 === 1/+0 === Infinity(正的),1/-0 === -Infinity(负的)

# Object.is,"===","=="的区别

JavaScript 中的相等性判断 (opens new window)

  • 使用==进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。
  • 使用===进行相等判断时,如果两边的类型不一致时,不会做强制类型准换,直接返回false
  • 使用Object.is来进行相等判断时,一般情况下和===的判断相同,它处理了一些特殊的情况,比如-0+0不再相等,两个NaN是相等的。
console.log(NaN == NaN); // false
console.log(+0 == +0); // true
console.log(-0 == -0); // true
console.log(+0 == -0); // true

console.log(NaN === NaN); // false
console.log(+0 === +0); // true
console.log(-0 === -0); // true
console.log(+0 === -0); // true

console.log(Object.is(NaN, NaN)); // true
console.log(Object.is(+0, +0)); // true
console.log(Object.is(-0, -0)); // true
console.log(Object.is(+0, -0)); // false
1
2
3
4
5
6
7
8
9
10
11
12
13
14

除了===之外,数组索引查找方法也使用严格相等,包括Array.prototype.indexOf()Array.prototype.lastIndexOf()TypedArray.prototype.index()TypedArray.prototype.lastIndexOf()case匹配。这意味着你不能使用indexOf(NaN)查找数组中NaN值的索引,也不能将NaN用作case值在switch语句中匹配任何内容。

console.log([NaN].indexOf(NaN)); // -1

switch (NaN) {
  case NaN:
    console.log('Surprise'); // 没有任何输出
}
1
2
3
4
5
6

同值相等和零值相等

  • 同值相等决定了两个值在所有上下文中是否在功能上相同。同值相等由Object.is方法提供。语言内部期望一个值等于另一个时,几乎所有地方都使用同值相等。
  • 零值相等,类似于同值相等,但+0-0被视为相等。零值相等不作为JavaScript API公开,但可以通过自定义代码实现:
function sameValueZero(x, y) {
  if (typeof x === 'number' && typeof y === 'number') {
    // x 和 y 相等(可能是 -0 和 0)或它们都是 NaN
    return x === y || (x !== x && y !== y);
  }
  return x === y;
}
1
2
3
4
5
6
7

零值相等与严格相等的区别在于其将NaN视作是相等的,与同值相等的区别在于其将-00视作相等的。这使得它在搜索期间通常具有最实用的行为,特别是在与NaN一起使用时。它被用于Array.prototype.includes()TypedArray.prototype.includes()MapSet方法用来比较键的相等性。

# setImmediate和setTimeout的区别

setImmediate是一个用于在Node.js中执行异步操作的函数,会在当前事件循环的末尾立即执行回调函数,而不是等待一定的延迟时间。将回调函数放置在当前事件循环的队列末尾,以确保它在下一个事件循环开始时尽快执行,而不会阻塞其他任务。setImmediate的执行优先级比setTimeout高,因为它是在当前事件循环的末尾执行的,而setTimeout则要等待一定的延迟时间。两者都属于Macrotask。

# EventSource和WebSocket

  1. EventSource是HTML5中新增的API,它提供了一种简单易用的方式来实现服务器向浏览器的即时推送。通过EventSource,我们可以建立一个持久连接,从而实现服务器端的事件推送到浏览器端。这使得我们能够轻松创建一个实时聊天室或实时数据展示页面。EventSource具有超高效、最好用的特点。它使用简单,只需要通过JavaScript代码创建一个EventSource对象,并指定服务器端的URL。然后,我们可以监听事件,处理服务器端发送的消息。最重要的是,EventSource基于HTTP协议,所以可以兼容大部分浏览器。

    优点:

    • 简单易用,与HTTP协议兼容。
    • 只需要一个长连接,服务器可以推送任意数量的事件。
    • 适用于服务端向客户端发送频率较低的数据。
    • 可以自动重连,并且在连接断开时会触发errorclose事件,方便处理异常情况。

    缺点:

    • 不支持双向通信。
    • 不支持二进制数据传输。
    • 兼容性存在问题,不支持IE浏览器。
  2. WebSocketEventSource不同,WebSocket是一种全双工通信协议,它能够在浏览器和服务器之间建立双向通信的连接。相比于EventSource的单向通信,WebSocket可以同时实现浏览器向服务器的推送和服务器向浏览器的推送,实现真正的双向通信。WebSocket具有极低的延迟,适用于实时游戏、聊天应用等场景。它使用wswss协议,能够在浏览器和服务器之间建立长时间的连接。通过WebSocket,我们可以发送和接收消息,实时更新数据,并处理各种事件。

    优点:

    • 支持双向通信,客户端和服务端都可以发送和接收消息。
    • 可以发送二进制数据,支持大文件传输。
    • 协议比较轻量级,能够节省网络带宽和服务器资源。
    • 兼容性较好,大部分现代浏览器都支持WebSocket

    缺点:

    • 需要在服务端实现WebSocket协议的支持。
    • 相对于HTTP请求来说,WebSocket连接需要占用更多的服务端资源。
    • 安全性问题:需要注意防止CSRF和XSS攻击,避免恶意用户利用WebSocket劫持会话或注入脚本等。

# 数组与类数组

数组(Array)和类数组(Like Array Object)都具有length属性和通过索引访问元素的能力,但是类数组不具备数组的方法(pushpopunshiftshift),数组继承自Array.prototype,类数组继承自Object.prototype。类数组可以通过Array.form()、展开运算符...或者Array.prototype.slice.call()转换成数组。

常见的类数组:

  • 函数的arguments对象。
  • document.getElementsByTagName()得到的元素集合HTMLCollection
  • document.querySelectorAll()document.getElementById("parent").childNodes得到的元素集合NodeList

常见的新版浏览器也在NodeList支持了一些遍历方法,比如forEach()entries()values()、和keys()。新版本的Chrome也在HTMLCollection上支持了forEach()

注意:HTMLCollection接口表示一个包含了元素(元素顺序为文档流中的顺序)的通用集合(与arguments相似的类数组对象),还提供了用来从该集合中选择元素的方法和属性。HTMLDOM中的HTMLCollection是即时更新的(live);当其所包含的文档结构发生改变时,它会自动更新。因此,最好是创建副本(例如,使用Array.from)后再迭代这个数组以添加、移动或删除DOM节点。但是NodeList有时是实时的,如果文档中的节点树发生变化,NodeList也会随之变化。例如查看父级节点的子元素时Node.childNodes,在其他情况下,NodeList是一个静态集合,如document.querySelectorAll()返回的NodeList

arguments拓展:在严格模式下,剩余参数、默认参数和解构赋值参数的存在不会改变arguments对象的行为,但是在非严格模式下就有所不同了。当非严格模式中的函数没有包含剩余参数、默认参数和解构赋值,那么arguments对象中的值会跟踪参数的值(反之亦然)。原文链接 (opens new window)

# 在window和document上注册事件的区别

特性 window document
事件捕获阶段 捕获所有阶段(捕获、目标、冒泡) 捕获目标阶段和冒泡阶段
事件目标范围 整个页面(包括<body><html> 文档内容(不包括<body><html>
事件委托性能 范围最大,性能较低 范围较小,性能较高
适用场景 全局事件(如loadresize DOM事件(如clickinput
最近更新: 2025年03月06日 15:35:02