javaScript基础(中)

80 阅读13分钟

11. 如果 new 一个箭头函数的会怎么样

箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用 arguments 参数,所以不能 New 一个箭头函数。

new 操作符的实现步骤如下:

1.创建一个对象

2.将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的 prototype 属性)

3.指向构造函数中的代码,构造函数中的 this 指向该对象(也就是为这个对象添加属性和方法)

4.返回新的对象

所以,上面的第二、三步,箭头函数都是没有办法执行的。

12. 箭头函数的 this 指向哪⾥?

箭头函数不同于传统 JavaScript 中的函数,箭头函数并没有属于⾃⼰的 this,它所谓的 this 是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的 this,所以是不会被 new 调⽤的,这个所谓的 this 也不会被改变。

可以⽤Babel 理解⼀下箭头函数:

image.png

转化后:

image.png

13. 扩展运算符的作用及使用场景

(1)对象扩展运算符

对象的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。

let obj = {a: 1, b: 2};
let copyObj = { ...obj }; // {a: 1, b: 2}

上述方法实际上等价于

let obj = {a: 1, b: 2};
let copyObj = Object.assign({},  obj);  // {a: 1, b: 2}

Object.assign 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。Object.assign 方法的第一个参数是目标对象,后面的参数都是源对象。(如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性)。

同样,如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。

let obj = {a: 1, b: 2};
let copyObj = {...obj, ...{a:2, b:4}} //{a:2, b:4}

利用上述特性就可以很方便的修改对象的部分属性。在 redux 中的reducer 函数规定必须是一个纯函数,reducer 中的 state 对象要求不能直接修改,可以通过扩展运算符把修改路径的对象都复制一遍,然后产生一个新的对象返回。

需要注意:扩展运算符对对象实例的拷贝属于浅拷贝。

(2)数组扩展运算符

数组扩展运算符可以将一个数组转为用逗号分隔的参数序列,且每次只能展开一层数组。

console.log(...[1,2,3]) // 1 2  3
console.log(...[1,[2,3,4], 5])  // 1 [2,3, 4] 5

数组扩展运算符的应用

  • 将数组转为参数序列
function add (x, y){
    return x + y;
}
const numbers = [2,3];
add(...numbers) // 5
  • 复制数组
const arr1 = [1, 2];
const arr2 = [...arr1];

要记住:扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中,这里参数对象是个数组,数组里面的所有对象都是基础数据类型,将所有基础数据类型重新拷贝到新的数组中。

  • 合并数组

如果想在数组内合并数组,可以这样:

const arr1 = [1,2]
const arr2 = [...arr1, 3,4,5]
// [1,2,3,4,5]
  • 扩展运算符与解构赋值结合起来,用于生成数组
const [first, ...rest] = [1,2,3,4,5]
// first 1
// rest  [2,3,4,5]

需要注意:如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

  • 将字符串转为真正的数组
[...'hello'] // ['h','e','l','l', 'o']

任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组比较常见的应用是可以将某些数据结构转为数组:

//arguments对象
function foo(){
    const args = [...arguments];
}

用于替换 es5 中的 Array.prototype.slice.call(arguments)写法。使用 Math 函数获取数组中特定的值

const numbers = [9,4,7,1];
Math.min(...numbers) // 1
Math.max(...numbers) // 9

14. Proxy 可以实现什么功能?

在 Vue3.0 中通过 Proxy 来替换原本的 Object.defineProperty来实现数据响应式。

Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。

let p = new Proxy(target, handler)

代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。

下面来通过 Proxy 来实现一个数据响应式:

let onWatch = (obj, setBind, getLogger) => {
  let handler = {
    get(target, property, receiver){
      getLogger(target, property)
      return Reflect.get(target, property, receiver)
    },
    set(target, property, value, receiver){
      setBind(value, property)
      return Reflect.set(target,property, value)
    }
  }
  return new Proxy(obj, handler)
}
let obj = { a: 1 }
let p = onWatch(
  obj,
  (v, property) => {
    console.log(`监听到属性${property}改变为${v}`)
  },
  (target, property) => {
    console.log(`'${property}' = ${target[property]}`)
  }
)

p.a = 2  // 监听到属性a 改变
p.a      // 'a' = 2

在上述代码中,通过自定义 set 和 get 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。

当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式 的数据改变,唯一缺陷就是浏览器的兼容性不好。

15. 对JSON的理解

JSON 是一种基于文本的轻量级的数据交换格式。它可以被任何的编程语言读取和作为数据格式来传递。

在项目开发中,使用 JSON 作为前后端数据交换的方式。在前端通过将一个符合 JSON 格式的数据结构序列化为 JSON 字符串,然后将它传递到后端,后端通过 JSON 格式的字符串解析后生成对应的数据结构,以此来实现前后端数据的一个传递。

因为 JSON 的语法是基于 js 的,因此很容易将 JSON 和 js 中的对象弄混,但是应该注意的是 JSON 和 js 中的对象不是一回事,JSON中对象格式更加严格,比如说在 JSON 中属性值不能为函数,不能出现 NaN 这样的属性值等,因此大多数的 js 对象是不符合 JSON 对象的格式的。

在 js 中提供了两个函数来实现 js 数据结构和 JSON 格式的转换处理,JSON.stringify 函数,通过传入一个符合 JSON 格式的数据结构,将其转换为一个 JSON 字符串。如果传入的数据结构不符合 JSON 格式,那么在序列化的时候会对这些值进行对应的特殊处理,使其符合规范。在前端向后端发送数据时,可以调用这个函数将数据对象转化 为 JSON 格式的字符串。

JSON.parse() 函数,这个函数用来将 JSON 格式的字符串转换为一个 js 数据结构,如果传入的字符串不是标准的 JSON 格式的字符串的话,将会抛出错误。当从后端接收到 JSON 格式的字符串时,可以通过这个方法来将其解析为一个 js 数据结构,以此来进行数据的访问。

16. JavaScript 脚本延迟加载的方式有哪些?

延迟加载就是等页面加载完成之后再加载 JavaScript 文件。js 延迟加载有助于提高页面加载速度。

一般有以下几种方式:

defer 属性:给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。

async 属性:给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。

动态创建 DOM 方式:动态创建 DOM 标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入js 脚本。

使用 setTimeout 延迟方法:设置一个定时器来延迟加载 js 脚本文件,让 JS 最后加载:将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。

17. 什么是 DOM 和 BOM?

DOM 指的是文档对象模型,它指的是把文档当做一个对象,这个对象主要定义了处理网页内容的方法和接口。

BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的方法和接口。

BOM 的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方 法存在。

window 对象含有 location 对象、navigator 对象、screen对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM的 window 对象的子对象。

18. escape、encodeURI、encodeURIComponent 的区别

encodeURI 是对整个 URI 进行转义,将 URI 中的非法字符转换为合法字符,所以对于一些在 URI 中有特殊意义的字符不会进行转义。

encodeURIComponent 是对 URI 的组成部分进行转义,所以一些特殊字符也会得到转义。

escape 和 encodeURI 的作用相同,不过它们对于 unicode 编码为0xff 之外字符的时候会有区别,escape 是直接在字符的 unicode编码前加上 %u,而 encodeURI 首先会将字符转换为 UTF-8 的格式,再在每个字节前加上 %。

19.什么是尾调用,使用尾调用有什么好处?

尾调用指的是函数的最后一步调用另一个函数。代码执行是基于执行栈的,所以当在一个函数里调用另一个函数时,会保留当前的执行上下文,然后再新建另外一个执行上下文加入栈中。使用尾调用的话,因为已经是函数的最后一步,所以这时可以不必再保留当前的执行上下文,从而节省了内存,这就是尾调用优化。但是 ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。

20.ES6 模块与 CommonJS 模块有什么异同?

ES6 Module 和 CommonJS 模块的区别:

CommonJS 是对模块的浅拷⻉,ES6 Module 是对模块的引⽤,即 ES6 Module 只存只读,不能改变其值,也就是指针指向不能变,类似 const;import 的接⼝是 read-only(只读状态),不能修改其变量值。 即不能修改其变量的指针指向,但可以改变变量内部指针指向,可以对commonJS 对重新赋值(改变指针指向),但是对 ES6 Module 赋值会编译报错。

ES6 Module 和 CommonJS 模块的共同点:

CommonJS 和 ES6 Module 都可以对引⼊的对象进⾏赋值,即对对象内部属性的值进⾏改变。

21. for...in 和 for...of 的区别

for…of 是 ES6 新增的遍历方式,允许遍历一个含有 iterator 接口的数据结构(数组、对象等)并且返回各项的值,和 ES3 中的 for…in 的区别如下

for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名;

for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而for … of 只遍历当前对象不会遍历原型链;

对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值;

总结: for...in 循环主要是为了遍历对象而生,不适用于遍历数组;

for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。

22. call() 和 apply() 的区别?

它们的作用一模一样,区别仅在于传入参数的形式的不同。

apply 接受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数。

call 传入的参数数量不固定,跟 apply 相同的是,第一个参数也是代表函数体内的 this 指向,从第二个参数开始往后,每个参数被依次传入函数。

23. ajax、axios、fetch 的区别

  • AJAX

Ajax 即“AsynchronousJavascriptAndXML”(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。它是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。

这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。

其缺点如下:

  1. 本身是针对 MVC 编程,不符合前端 MVVM 的浪潮
  2. 基于原生 XHR 开发,XHR 本身的架构不清晰
  3. 不符合关注分离(Separation of Concerns)的原则配置和调用方式非常混乱,而且基于事件的异步模型不友好。
  • Fetch

fetch 号称是 AJAX 的替代品,是在 ES6 出现的,使用了 ES6 中的 promise 对象。Fetch 是基于 promise 设计的。Fetch 的代码结构比起 ajax 简单多。fetch 不是 ajax 的进一步封装,而是原生 js,没有使用 XMLHttpRequest 对象。

fetch 的优点:

  1. 语法简洁,更加语义化
  2. 基于标准 Promise 实现,支持 async/await
  3. 更加底层,提供的 API 丰富(request, response)脱离了 XHR,是 ES 规范里新的实现方式

fetch 的缺点:

  1. fetch 只对网络请求报错,对 400,500 都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
  2. fetch 默 认 不 会 带 cookie , 需 要 添 加 配 置 项 : fetch(url,{credentials: 'include'})
  3. fetch 不 支 持 abort , 不 支 持 超 时 控 制 , 使 用 setTimeout 及Promise.reject 的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
  4. fetch 没有办法原生监测请求的进度,而 XHR 可以
  • Axios

Axios 是一种基于 Promise 封装的 HTTP 客户端,其特点如下:

  1. 浏览器端发起 XMLHttpRequests 请求
  2. node 端发起 http 请求
  3. 支持 Promise API监听请求和返回
  4. 对请求和返回进行转化
  5. 取消请求
  6. 自动转换 json 数据
  7. 客户端支持抵御 XSRF 攻击