跳到主要内容

三、JS基础

001 - new 操作符的实现原理

new操作符的执行过程:

  1. 首先创建了一个新的空对象(创建一个新的内存空间)
  2. 设置原型,将对象的原型设置为函数的 prototype 对象。
  3. 让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
  4. 返回新对象(所以构造函数不需要return)

002 - MapObject 的区别

  • 同名碰撞

    • 对象其实就是在堆开辟了一块内存,其实Map的键存的就是这块内存的地址。只要地址不一样,就是两个不同的键,这就解决了同名属性的碰撞问题,而传统的Object显然做不到这一点。

  • 键的类型

    • Map的键可以是任意值,包括函数、对象或任意基本类型。
    • Object 的键必须是 String 或是Symbol。
  • 键的顺序

    • Map 中的 key 是有序的。因此,当迭代的时候, Map 对象以插入的顺序返回键值。
    • Object 的键是无序的
  • Size

    • Map 的键值对个数可以轻易地通过size 属性获取
    • Object 的键值对个数只能手动计算
  • 迭代

    • Map 是 iterable 的,所以可以直接被迭代,可用for...of遍历

    • Object不是 iterable,不可以被迭代,不能用for...of遍历

003 - Map和weakMap的区别

Map

map本质上就是键值对的集合,但是普通的Object中的键值对中的键只能是字符串。而ES6提供的Map数据结构类似于对象,但是它的键不限制范围,可以是任意类型,是一种更加完善的Hash结构。如果Map的键是一个原始数据类型,只要两个键严格相同,就视为是同一个键。

  • Map数据结构有以下操作方法

    • size

      • map.size 返回Map结构的成员总数
    • set(key,value)

      • 设置键名key对应的键值value,然后返回整个Map结构,如果key是简单数据类型且已经有值,则键值会被更新,否则就新生成该键,若键为引用数据类型,则不会被更改。
    • get(key)

      • 该方法读取key对应的键值,如果找不到key,返回undefined
    • has(key)

      • 该方法返回一个布尔值,表示某个键是否在当前Map对象中
    • delete(key)

      • 该方法删除某个键,返回true,如果删除失败,返回false
    • clear()

      • map.clear()清除所有成员,没有返回值
  • Map结构原生提供三个遍历器生成函数和一个遍历方法

      • keys()

        • 返回键名的遍历器
      • values()

        • 返回键值的遍历器
      • entries()

        • 返回所有成员的遍历器
      • forEach()

        • 遍历Map的所有成员
  • weakMap

    • WeakMap 对象也是一组键值对的集合,其中的键是弱引用的。其键必须是对象,原始数据类型不能作为key值,而值可以是任意的

      • weakMap对象也有以下几种方法

        • set(key,value)

          • 设置键名key对应的键值value,然后返回整个Map结构
        • get(key)

          • 该方法读取key对应的键值,如果找不到key,返回undefined
        • has(key)

          • 该方法返回一个布尔值,表示某个键是否在当前Map对象中
        • delete(key)

          • 该方法删除某个键,返回true,如果删除失败,返回false

WeakMap的设计目的在于,有时想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。一旦不再需要这两个对象,就必须手动删除这个引用,否则垃圾回收机制就不会释放对象占用的内存。而WeakMap的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用

004 - JavaScript有哪些内置对象

  • 值属性

    • 这些全局属性返回一个简单值,这些值没有自己的属性和方法。 例如 Infinity、NaN、undefined、null 字面量
  • 函数属性

    • 全局函数可以直接调用,不需要在调用时指定所属对象,执行结束后会将结果直接返回给调用者。 例如 eval()、parseFloat()、parseInt() 等
  • 基本对象

    • 基本对象是定义或使用其他对象的基础。基本对象包括一般对象、函数对象和错误对象。 例如 Object、Function、Boolean、Symbol、Error 等
  • 数字和日期对象

    • 用来表示数字、日期和执行数学计算的对象。 例如 Number、Math、Date
  • 字符串

    • 用来表示和操作字符串的对象。 例如 String、RegExp
  • 可索引的集合对象

    • 这些对象表示按照索引值来排序的数据集合,包括数组和类型数组,以及类数组结构的对象。例如 Array
  • 使用键的集合对象

    • 这些集合对象在存储数据时会使用到键,支持按照插入顺序来迭代元素。 例如 Map、Set、WeakMap、WeakSet
  • 矢量集合

    • SIMD 矢量集合中的数据会被组织为一个数据序列。 例如 SIMD 等
  • 结构化数据

    • 这些对象用来表示和操作结构化的缓冲区数据,或使用 JSON 编码的数据。 例如 JSON 等
  • 控制抽象对象

    • 例如 Promise、Generator 等
  • 反射

    • 例如 Reflect、Proxy
  • 国际化

    • 为了支持多语言处理而加入 ECMAScript 的对象。 例如 Intl、Intl.Collator 等
  • WebAssembly

  • 其他

    • 例如 arguments

005 - 常用的正则表达式有哪些?

006 - 对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 数据结构,以此来进行数据的访问

006 - JavaScript脚本延迟加载的方式有哪些?

  • defer 属性

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

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

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

    • 设置一个定时器来延迟加载js脚本文件
  • 让 JS 最后加载

    • 将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行

007 - JavaScript 类数组对象的定义?

  • 一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法。常见的类数组对象有 arguments 和 DOM 方法的返回结果,还有一个函数也可以被看作是类数组对象,因为它含有 length 属性值,代表可接收的参数个数

    • 常见的类数组转换为数组的方法

      • 通过 call 调用数组的 slice 方法来实现转换

        slice() 方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。

        • Array.prototype.slice.call(arrayLike);
      • 通过 call 调用数组的 splice 方法来实现转换

        splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。

        • Array.prototype.splice.call(arrayLike, 0);
      • 通过 apply 调用数组的 concat 方法来实现转换

        concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。

        • Array.prototype.concat.apply([], arrayLike);
      • 通过 Array.from 方法来实现转换

        Array.from() 方法对一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。

        • Array.from(arrayLike);

008 - 数组有哪些原生方法?

  • 数组和字符串的转换方法

    • toString()、toLocalString()、join()
  • 数组尾部操作的方法

    • pop() 和 push(),push 方法可以传入多个参数
  • 数组首部操作的方法

    • shift() 和 unshift() unshift方法可以传递多个参数,表示在数组开头增加
  • 重排序的方法

    • reverse() 和 sort(),sort() 方法可以传入一个函数来进行比较,传入前后两个值,如果返回值为正数,则交换两个参数的位置
  • 数组连接的方法

    • concat() ,返回的是拼接好的数组,不影响原数组
  • 数组截取(浅拷贝)办法

    • slice(begin【end】),用于截取数组中的一部分返回,不影响原数组。
  • 数组插入/删除/新增方法

    • array.splice(start[, deleteCount[, item1[, item2[, ...]]]]),改变原数组
  • 数组归并方法

    • reduce() 和 reduceRight() 方法

009 - Unicode、UTF-8、UTF-16、UTF-32的区别?

  • ● Unicode 是编码字符集(字符集),而UTF-8、UTF-16、UTF-32是字符集编码(编码规则); ● UTF-16 使用变长码元序列的编码方式,相较于定长码元序列的UTF-32算法更复杂,甚至比同样是变长码元序列的UTF-8也更为复杂,因为其引入了独特的代理对这样的代理机制; ● UTF-8需要判断每个字节中的开头标志信息,所以如果某个字节在传送过程中出错了,就会导致后面的字节也会解析出错;而UTF-16不会判断开头标志,即使错也只会错一个字符,所以容错能力教强; ● 如果字符内容全部英文或英文与其他文字混合,但英文占绝大部分,那么用UTF-8就比UTF-16节省了很多空间;而如果字符内容全部是中文这样类似的字符或者混合字符中中文占绝大多数,那么UTF-16就占优势了,可以节省很多空间;

010 - 常见的位运算符有哪些?其计算规则是什么?

  • &---与

    • 两个位都为1时,结果才为1
  • |---或

    • 两个位都为0时,结果才为0
  • ^---异或

    • 两个相应位相同为0,相异为1
  • ~---取反

    • 0变1,1变0
  • <<---左移

    • 各二进制位全部左移若干位,高位丢弃,低位补0
  • >> ---右移

    • 各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃

011 - 为什么函数的 arguments 参数是类数组而不是数组?如何遍历类数组?

  • arguments是一个对象,它的属性是从 0 开始依次递增的数字,还有callee和length等属性,与数组相似;但是它却没有数组常见的方法属性,如forEach, reduce等,所以叫它们类数组。

  • 要遍历类数组的方法

    • 使用call和apply方法

    • Array.from方法将类数组转化成数组

    • 使用展开运算符将类数组转化成数组

012 - 什么是 DOM 和 BOM?

  • DOM 指的是文档对象模型,它指的是把文档当做一个对象,这个对象主要定义了处理网页内容的方法和接口。
  • BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的方法和接口。BOM的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window 对象含有 location 对象、navigator 对象、screen 对象、history对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对象的子对象。

013 - 对AJAX的理解,实现一个AJAX请求

  • 指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。

    创建AJAX请求的步骤:

    ● 创建一个 XMLHttpRequest 对象。

    ● 在这个对象上使用 open 方法创建一个 HTTP 请求,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。

    ● 在发起请求前,可以为这个对象添加一些信息和监听函数。比如说可以通过 setRequestHeader 方法来为请求添加头信息。还可以为这个对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变化时会触发onreadystatechange 事件,可以通过设置监听函数,来处理请求成功后的结果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接收完成,这个时候可以通过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。这个时候就可以通过 response 中的数据来对页面进行更新了。

    ● 当对象的属性和监听函数设置完成后,最后调用 sent 方法来向服务器发起请求,可以传入参数作为发送的数据体。

      • readyState属性的五个状态

014 - ajax、axios、fetch的区别

AJAX

异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。它是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。其缺点如下:

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

Axios

Axios 是一种基于Promise封装的HTTP客户端,支持浏览器端和 Node.js 端其特点如下

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

Fetch

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

  • 优点

    • 语法简洁,更加语义化
    • 基于标准 Promise 实现,支持 async/await
    • 更加底层,提供的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可以

015 - JavaScript为什么要进行变量提升,它导致了什么问题?

  • 为什么要进行变量提升

    • 解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间

    • 声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行

  • 变量提升导致的问题

016 - ES6模块与CommonJS模块有什么异同?

不同点

  1. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  2. CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载
  3. CommonJS是对模块的浅拷贝,ES6 Module是对模块的引入,即ES6 Module只存只读,不能改变其值,具体点就是指针指向不能变,类似const 。
  4. import的接口是read-only(只读状态),不能修改其变量值。 即不能修改其变量的指针指向,但可以改变变量内部指针指向。可以对commonJS重新赋值(改变指针指向),但是对ES6 Module赋值会编译报错。

相同点

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

017 - 常见的DOM操作有哪些

  • DOM 节点的获取

    • 通过id获取

      • document.getElementById("id名”)
    • 通过标签名获取

      • document.getElementById("标签名”)
    • 通过类名获取

      • document.getElementByClassName('.类名‘)
    • querySelector获取

      • document.querySelector('#id名/.类名/标签名’)
    • querySelectorAll获取

      • document.querySelectorAll('#id名/.类名/标签名’)
    • 特殊元素获取

      • 获取body元素

        • document.body
      • 获取html元素

        • document.documentElement
  • DOM 节点的创建

    • document.createElement('标签名‘)
  • DOM 节点的添加

    • 后面添加

      • 父元素名.appendChild(要添加的元素)
    • 指定元素前面添加

      • 父元素名.insertBefore(要添加的元素,添加到哪个元素前面)
  • DOM 节点的删除

    • 父节点名.removeChild(子节点名)
  • DOM 节点的克隆

    • 要克隆的节点名.cloneNode(true/false)

018 - use strict是什么意思 ? 使用它区别是什么?

use strict 是一种 ECMAScript5 添加的(严格模式)运行模式,这种模式使得 Javascript 在更严格的条件下运行。

目的

  • 消除 Javascript 语法的不合理、不严谨之处,减少怪异行为;
  • 消除代码运行的不安全之处,保证代码运行的安全;
  • 提高编译器效率,增加运行速度;
  • 为未来新版本的 Javascript 做好铺垫

区别

  • 禁止使用 with 语句
  • 禁止 this 关键字指向全局对象
  • 对象不能有重名的属性

019 - 如何判断一个对象是否属于某个类

  • 第一种方式,使用 instanceof 运算符来判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
  • 第二种方式,如果需要判断的是某个内置的引用类型的话,可以使用 Object.prototype.toString() 方法来判断

020 - 强类型语言和弱类型语言的区别

强语言类型

强类型语言也称为强类型定义语言,是一种总是强制类型定义的语言,要求变量的使用要严格符合定义,所有变量都必须先定义后使用。Java和C++等语言都是强制类型定义的,也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了。例如你有一个整数,如果不显式地进行转换,你不能将其视为一个字符串

弱语言类型

弱类型语言也称为弱类型定义语言,与强类型定义相反。JavaScript语言就属于弱类型语言。简单理解就是一种变量类型可以被忽略的语言。比如JavaScript是弱类型定义的,在JavaScript中就可以将字符串'12' 和整数3进行连接得到字符串'123',在相加的时候会进行强制类型转换

021 - 解释性语言和编译型语言的区别

解释型语言

使用专门的解释器对源程序逐行解释成特定平台的机器码并立即执行。是代码在执行时才被解释器一行行动态翻译和执行,而不是在执行之前就完成翻译。解释型语言不需要事先编译,其直接将源代码解释成机器码并立即执行,所以只要某一平台提供了相应的解释器即可运行该程序。其特点总结如下

  • 解释型语言每次运行都需要将源代码解释称机器码并执行,效率较低
  • 只要平台提供相应的解释器,就可以运行源代码,所以可以方便源程序移植
  • JavaScript、Python等属于解释型语言

编译型语言

使用专门的编译器,针对特定的平台,将高级语言源代码一次性的编译成可被该平台硬件执行的机器码,并包装成该平台所能识别的可执行性程序的格式。在编译型语言写的程序执行之前,需要一个专门的编译过程,把源代码编译成机器语言的文件,如exe格式的文件,以后要再运行时,直接使用编译结果即可,如直接运行exe文件。因为只需编译一次,以后运行时不需要编译,所以编译型语言执行效率高。其特点总结如下

  • 一次性的编译成平台相关的机器语言文件,运行时脱离开发环境,运行效率高
  • 与特定平台相关,一般无法移植到其他平台
  • C、C++等属于编译型语言

022 - for...infor...of的区别

for…of 是ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、字符串、Map、Set、类数组对象【伪数组】等,注意:对象没有iterator)并且返回各项的值

  • 区别

    • for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链
    • 对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值

023 - 如何使用for...of遍历对象

  • for…of是作为ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,普通的对象用for..of遍历是会报错的。

    • 如果需要遍历的对象是类数组对象,用Array.from转成数组即可

      • 输出的为键值
    • 如果不是类数组对象,就给对象添加一个[Symbol.iterator]属性,并指向一个迭代器即可

024 - 数组的遍历方法有哪些

  • for...of

    • 不改变原数组

      • for...of遍历具有Iterator迭代器的对象的属性,返回的是数组的元素、对象的属性值,不能遍历普通的obj对象
  • forEach()

    • 视情况是否改变原数组

      • 没有返回值
  • filter()

    • 不改变原数组

      • 数组方法,不改变原数组,有返回值,返回一个符合筛选规则的新数组
  • every() 和 some()

    • 不改变原数组

      • 数组方法,some()只要有一个是true,便返回true;而every()只要有一个是false,便返回false.
  • map()

    • 不改变原数组

      • 数组方法,不改变原数组,有返回值,生成一个一一对应的新数组
  • find() 和 findIndex()

    • 不改变原数组

      • 数组方法,find()返回的是第一个符合条件的值;findIndex()返回的是第一个返回条件的值的索引值
  • reduce() 和 reduceRight()

    • 不改变原数组

      • 数组方法,reduce()对数组正序操作;reduceRight()对数组逆序操作

025 - forEach和map方法有什么区别

  • forEach()方法会针对每一个元素执行提供的函数,对数据的操作会改变原数组,该方法没有返回值
  • map()方法不会改变原数组的值,有返回值,返回一个新数组,新数组中的值为原数组调用函数处理之后的值

026 - addEventListener()方法的参数和使用

EventTarget.addEventListener() 方法将指定的监听器注册到 EventTarget 上,当该对象触发指定的事件时,指定的回调函数就会被执行。 事件目标可以是一个文档上的元素 Element,Document和Window或者任何其他支持事件的对象。

  • 原理

    • 将实现EventListener的函数或对象添加到调用它的EventTarget上的指定事件类型的事件侦听器列表中。

语法

  • type

    • 表示监听事件类型的字符串
  • listener

    • 当所监听的事件类型触发时,会接收到一个事件通知(实现了 Event 接口的对象)对象。listener 必须是一个实现了 EventListener 接口的对象,或者是一个函数。
  • options 可选

    • 一个指定有关 listener 属性的可选参数对象。可用的选项如下: ● capture: Boolean,表示 listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发。 ● once: Boolean,表示 listener 在添加之后最多只调用一次。如果是 true, listener 会在其被调用之后自动移除。 ● passive: Boolean,设置为true时,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。 ● signal:AbortSignal,该 AbortSignal 的 abort() 方法被调用时,监听器会被移除。
  • useCapture 可选

    • Boolean,在DOM树中,注册了listener的元素, 是否要先于它下面的EventTarget,调用该listener。 当useCapture(设为true) 时,沿着DOM树向上冒泡的事件,不会触发listener。当一个元素嵌套了另一个元素,并且两个元素都对同一事件注册了一个处理函数时,所发生的事件冒泡和事件捕获是两种不同的事件传播方式。事件传播模式决定了元素以哪个顺序接收事件。如果没有指定, useCapture 默认为 false 。
  • wantsUntrusted

    • 如果为 true , 则事件处理程序会接收网页自定义的事件。此参数只适用于 Gecko(chrome的默认值为true,其他常规网页的默认值为false),主要用于附加组件的代码和浏览器本身。

027 - 如何实现深拷贝?

什么是拷贝?复制

基本类型是按照值访问的

引用类型都是按照引用传递的

浅拷贝

只复制一层对象,当对象的属性是引用类型时,实质上复制的是其引用,当引用指向的值方式变化的时候,原对象的属性值也会跟着变化,互相影响

Object.assign(target, ...sources)target目标对象,sources元对象,返回目标对象

深拷贝

在拷贝的时候创建新的对象,并把原对象所有的属性都拷贝到新对象,原属性如果是对象,也会重新创建新的对象并拷贝到新对象属性中,原对象和新对象都是相互独立的,互不影响

方式一:let newObj = JSON.parse(JSON.stringify(obj))

JSON.parse(JSON.stringify(obj))是目前比较常用的深拷贝方法之一,它的原理就是利用JSON.stringify 将js对象序列化(JSON字符串),再使用JSON.parse来反序列化( 还原)js对象。

这个方法可以简单粗暴的实现深拷贝,但是还存在问题,拷贝的对象中如果有函数,undefined,symbol,当使用过JSON.stringify()进行处理之后,都会消失。

方式二:递归

let obj = {
a: 10,
b: {
c: 20
}
}

function deepCopy(obj) {
let o = {}
if (typeof obj === 'object') {
for (let k in obj) {
if (obj.hasOwnProperty(k)) {
if (typeof obj[k] === 'object') {
o[k] = deepCopy(obj[k])
} else {
o[k] = obj[k]
}
}
}
}
return o
}

let newObj = deepCopy(obj)

方式三:函数库lodash的_.cloneDeep方法