JavaScript 部分
- 1.cookies,sessionStorage 和 localStorage 的区别?
- 2. JavaScript 有哪些数据类型,它们的区别?
- 3. 数据类型检测的方式有哪些
- 3. null 和 undefined 区别
- 4. intanceof 操作符的实现原理及实现
- 5. 如何获取安全的 undefined 值?
- 6. Object.is() 与比较操作符===、== 的区别?
- 7. 什么是 JavaScript 中的包装类型?
- 8. 为什么会有 BigInt 的提案?
- 9. 如何判断一个对象是空对象
- 10. const 对象的属性可以修改吗
- 11. 如果 new 一个箭头函数的会怎么样
- 12. 箭头函数的 this 指向哪⾥?
- 13. 扩展运算符的作用及使用场景
- 14. Proxy 可以实现什么功能?
- 15. 常用的正则表达式有哪些?
- 16. 对 JSON 的理解
- 17. JavaScript 脚本延迟加载的方式有哪些?
- 18. 什么是 DOM 和 BOM?
- 19. escape、encodeURI、encodeURIComponent 的区别
- 20. 对 AJAX 的理解,实现一个 AJAX 请求
- 21. 什么是尾调用,使用尾调用有什么好处?
- 22. ES6 模块与 CommonJS 模块有什么异同?
- 请你讲一下对于JS中模块的理解,Commonjs、 AMD和CMD分别有什么特点?
- 23. for...in 和 for...of 的区别
- 24. ajax、axios、fetch 的区别
- 26. 原型链的终点是什么?如何打印出原型链的终点?
- 27. 对作用域、作用域链的理解
- 28. 对 this 对象的理解
- 29. call() 和 apply() 的区别?
- 30. 异步编程的实现方式?
- 31. 对 Promise 的理解
- 32. Promise 解决了什么问题
- 33. 对 async/await 的理解
- 34. async/await 的优势
- 35. async/await 对比 Promise 的优势
- 36. 对象创建的方式有哪些?
- 37. 对象继承的方式有哪些?
- 38. 哪些情况会导致内存泄漏
- 39. 数组去重
- 40. 闭包
- 什么叫事件委托呢?
- 事件冒泡?
- 有了解堆栈吗?什么在栈上,什么在值上?
- eventLoop的宏任务与微任务的区别?promise与setTimeOut谁先执行?
- 浅拷贝与深拷贝的区别?
- window.onload
- 简述一下 src 与 href 的区别?
- jq实现查找插槽节点?
- 刷新页面,js请求一般会有哪些地方有缓存处理?
- 引用传递,值传递你有用过?
- 实践题
- 一串字符串,我要把它倒叙,怎么弄?
1.cookies,sessionStorage 和 localStorage 的区别?
cookies、sessionStorage 和 localStorage 都是用于在浏览器端存储数据的技术,但它们之间有以下几个主要区别:
- 存储数据的大小限制不同。
- cookies 最大存储量有 4KB 左右,一个网站最多只能存储 20 个 cookies。
- sessionStorage 和 localStorage 最大存储量可达到 5MB。
- 存储数据的有效期不同。
- cookies 可以设置一个失效时间,除非用户主动删除,否则在失效时间之前一直有效。
- sessionStorage 存储的数据只在当前会话中有效,关闭浏览器窗口或标签页后就会被清除。
- localStorage 存储的数据没有过期时间,除非被用户手动删除,否则一直保留。
- 存储数据的作用范围不同。
- cookies 存储在客户端,每次请求都会发送到服务器端,服务器端也可以读取 cookies 中的数据。
- sessionStorage 和 localStorage 都属于 HTML5 Web Storage API,存储在客户端,不会随每次请求发送到服务器端,只能被同源的页面访问。
- 存储数据的安全性不同。
- cookies 容易被劫持,可以被其他人窃取和操纵,造成隐私泄露和安全问题。
- sessionStorage 和 localStorage 都相对更为安全,不容易被窃取、篡改和跨域攻击。
综上所述,cookies 是一种比较老旧和不安全的存储技术,且只能存储少量数据,一般用于存储用户的登录状态信息等。sessionStorage 和 localStorage 是 HTML5 的新特性,可以存储更多的数据,且具有更好的安全性。它们通常被用于缓存应用程序数据、本地存储用户偏好设置等场景。
2. JavaScript 有哪些数据类型,它们的区别?
JavaScript 共有八种数据类型,分别是
Undefined、Null、Boolean、 Number、String、Object、Symbol、BigInt。
其中 Symbol 和 BigInt 是 ES6 中新增的数据类型:
●Symbol 代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。
●BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了
Number 能够表示的安全整数范围。
这些数据可以分为原始数据类型和引用数据类型:
●栈:原始数据类型(Undefined、Null、Boolean、Number、String)
●堆:引用数据类型(对象、数组和函数)
两种类型的区别在于存储位置的不同:
●原始数据类型直接存储在栈(stack)中的简单数据段,占据空间 小、大小固定,属于被频繁使用数据,所以放入栈中存储;
●引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固 定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈
中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引 用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:
●在数据结构中,栈中数据的存取方式为先进后出。
●堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。
在操作系统中,内存被分为栈区和堆区:
●栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的 值等。其操作方式类似于数据结构中的栈。
●堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可 能由垃圾回收机制回收。
3. 数据类型检测的方式有哪些
1)typeof
其中数组、对象、null 都会被判断为 object,其他判断都正确。
2)instanceof
instanceof 可以正确判断对象的类型,其内部运行机制是判断在其 原型链中能否找到该类型的原型。
可以看到,instanceof 只能正确判断引用数据类型,而不能判断基本数据类型。instanceof 运算符可以用来测试一个对象在其原型链
中是否存在一个构造函数的 prototype 属性。
3) constructor
constructor 有两个作用,一是判断数据的类型,二是对象实例通过 constrcutor 对象访问它的构造函数。需要注意,如果创建一个对象
来改变它的原型,constructor 就不能用来判断数据类型了:
4)Object.prototype.toString.call()
Object.prototype.toString.call() 使用 Object 对象的原型方法 toString 来判断数据类型:
同样是检测对象 obj 调用 toString 方法,
obj.toString()的结果和 Object.prototype.toString.call(obj) 的结果不一样,这是为什么?
这是因为 toString 是 Object 的原型方法,而 Array、function 等类型作为 Object 的实例,都重写了 toString 方法。不同的对象类型调
用 toString 方法时,根据原型链的知识,调用的是对应的重写之后的 toString 方法(function 类型返回内容为函数体的字符串,Array 类型返回元素组成的字符串…),而不会去调用 Object 上原型 toString 方法(返回对象的具体类型),所以采用 obj.toString()
不能得到其对象类型,只能将 obj 转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用 Object 原型上的 toString 方法。
3. null 和 undefined 区别
首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null 主要用于
赋值给一些可能会返回对象的变量,作为初始化。undefined 在 JavaScript 中不是一个保留字,这意味着可以使用
undefined 来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。我们可以通过一些方法获得安全的
undefined 值,比如说 void 0。 当对这两种类型使用 typeof 进行判断时,Null 类型化会返回 “object”,这是一个历史遗留的问题。当使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。
4. intanceof 操作符的实现原理及实现
instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置
5. 如何获取安全的 undefined 值?
因为 undefined 是一个标识符,所以可以被当作变量来使用和赋值, 但是这样会影响 undefined 的正常判断。表达式 void ___ 没有返
回值,因此返回结果是 undefined。void 并不改变表达式的结果, 只是让表达式不返回值。因此可以用 void 0 来获得 undefined。
6. Object.is() 与比较操作符===、== 的区别?
使用双等号()进行相等判断时,如果两边的类型不一致,则会进 行强制类型转化后再进行比较。 使用三等号(=)进行相等判断时,如果两边的类型不一致时,不会做强制类型准换,直接返回 false。 使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN是相等的。
7. 什么是 JavaScript 中的包装类型?
在 JavaScript 中,基本类型是没有属性和方法的,但是为了便于操 作基本类型的值,在调用基本类型的属性或方法时 JavaScript 会在
后台隐式地将基本类型的值转换为对象,如:
在 访 问 ‘abc’.length 时 , JavaScript 将 ‘abc’ 在 后 台 转 换 成 String(‘abc’),然后再访问其 length 属性。 JavaScript 也可以使用 Object 函数显式地将基本类型转换为包装类 型:
也可以使用 valueOf 方法将包装类型倒转成基本类型: 看看如下代码会打印出什么:
答案是什么都不会打印,因为虽然包裹的基本类型是 false,但是 false 被包裹成包装类型后就成了对象,所以其非值为 false,所以
循环体中的内容不会运行。
8. 为什么会有 BigInt 的提案?
JavaScript 中 Number.MAX_SAFE_INTEGER 表示最⼤安全数字,计算 结果是 9007199254740991,即在这个数范围内不会出现精度丢失(⼩
数除外)。但是⼀旦超过这个范围,js 就会出现计算不准确的情况, 这在⼤数计算的时候不得不依靠⼀些第三⽅库进⾏解决,因此官⽅提
出了 BigInt 来解决此问题。
9. 如何判断一个对象是空对象
使用 JSON 自带的.stringify 方法来判断:
使用 ES6 新增的方法 Object.keys()来判断:
10. const 对象的属性可以修改吗
const 保证的并不是变量的值不能改动,而是变量指向的那个内存地 址不能改动。对于基本类型的数据(数值、字符串、布尔值),其值 就保存在变量指向的那个内存地址,因此等同于常量。
但对于引用类型的数据(主要是对象和数组)来说,变量指向数据的 内存地址,保存的只是一个指针,const 只能保证这个指针是固定不 变的,至于它指向的数据结构是不是可变的,就完全不能控制了。
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 理解⼀下箭头函数:
转化后:
13. 扩展运算符的作用及使用场景
1)对象扩展运算符
对象的扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中
上述方法实际上等价于:
Object.assign 方法用于对象的合并,将源对象(source)的所有可 枚举属性,复制到目标对象(target)。Object.assign 方法的第一
个参数是目标对象,后面的参数都是源对象。(如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面
的属性)。 同样,如果用户自定义的属性,放在扩展运算符后面,则扩展运算符 内部的同名属性会被覆盖掉。
利用上述特性就可以很方便的修改对象的部分属性。在 redux 中的 reducer 函数规定必须是一个纯函数,reducer 中的 state 对象要求
不能直接修改,可以通过扩展运算符把修改路径的对象都复制一遍,然后产生一个新的对象返回。 需要注意:扩展运算符对对象实例的拷贝属于浅拷贝。
2)数组扩展运算符
数组的扩展运算符可以将一个数组转为用逗号分隔的参数序列,且每 次只能展开一层数组。
下面是数组的扩展运算符的应用:将数组转换为参数序列
复制数组
要记住:扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中,这里参数对象是个数组,数组里面的所有对象都
是基础数据类型,将所有基础数据类型重新拷贝到新的数组中。
合并数组 如果想在数组内合并数组,可以这样:
扩展运算符与解构赋值结合起来,用于生成数组
需要注意:如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
将字符串转为真正的数组
任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组
比较常见的应用是可以将某些数据结构转为数组:
用于替换 es5 中的 Array.prototype.slice.call(arguments)写法。 使用 Math 函数获取数组中特定的值
14. Proxy 可以实现什么功能?
在 Vue3.0 中通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式。
Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。
代表需要添加代理的对象,handler 用来自定义对象中的操作,比如
可以用来自定义 set 或者 get 函数。
下面来通过 Proxy 来实现一个数据响应式:
在上述代码中,通过自定义 set 和 get 函数的方式,在原本的逻辑 中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出
通知。
当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式, 需要在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用
Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属 性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现
有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式 的数据改变,唯一缺陷就是浏览器的兼容性不好。
15. 常用的正则表达式有哪些?
16. 对 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 数据结构,以此来进行数据的访 问。
17. JavaScript 脚本延迟加载的方式有哪些?
延迟加载就是等页面加载完成之后再加载 JavaScript 文件。js 延 迟加载有助于提高页面加载速度。
一般有以下几种方式:
defer 属性:给 js 脚本添加 defer 属性,这个属性会让脚本的加 载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文 件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是 这样。
async 属性:给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次 执行。
动态创建 DOM 方式:动态创建 DOM 标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入
js 脚本。
使用 setTimeout 延迟方法:设置一个定时器来延迟加载 js 脚本文件
让 JS 最后加载:将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。
18. 什么是 DOM 和 BOM?
DOM 指的是文档对象模型,它指的是把文档当做一个对象,这个对象 主要定义了处理网页内容的方法和接口。
BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待这个对象主要定义了与浏览器进行交互的法和接口。
BOM 的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器 窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页 中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window 对象含有 location 对象、navigator 对象、screen对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM的 window 对象的子对象。
19. escape、encodeURI、encodeURIComponent 的区别
encodeURI 是对整个 URI 进行转义,将 URI 中的非法字符转换为合法字符,所以对于一些在 URI 中有特殊意义的字符不会进行转义。
encodeURIComponent 是对 URI 的组成部分进行转义,所以一些特殊字符也会得到转义。
escape 和 encodeURI 的作用相同,不过它们对于 unicode 编码为0xff 之外字符的时候会有区别,escape 是直接在字符的 unicode
编码前加上 %u,而 encodeURI 首先会将字符转换为 UTF-8 的格式,再在每个字节前加上 %。
20. 对 AJAX 的理解,实现一个 AJAX 请求
AJAX 是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,
再更新当前网页的对应部分,而不用刷新整个网页。
创建 AJAX 请求的步骤:
创建一个 XMLHttpRequest 对象。
在这个对象上使用 open 方法创建一个 HTTP 请求,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。
在发起请求前,可以为这个对象添加一些信息和监听函数。比如说可 以通过 setRequestHeader 方法来为请求添加头信息。还可以为这个
对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变化时会触发 onreadystatechange 事件,可以
通过设置监听函数,来处理请求成功后的结果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接收完成,这个时候可以通
过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。 这个时候就可以通过 response 中的数据来对页面进行更新了。
当对象的属性和监听函数设置完成后,最后调用 sent 方法来向服务器发起请求,可以传入参数作为发送的数据体。
[图片]
使用 Promise 封装 AJAX:
[图片]
21. 什么是尾调用,使用尾调用有什么好处?
尾调用指的是函数的最后一步调用另一个函数。代码执行是基于执行栈的,所以当在一个函数里调用另一个函数时,会保留当前的执行上
下文,然后再新建另外一个执行上下文加入栈中。使用尾调用的话, 因为已经是函数的最后一步,所以这时可以不必再保留当前的执行上
下文,从而节省了内存,这就是尾调用优化。但是 ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。
22. ES6 模块与 CommonJS 模块有什么异同?
ES6 Module 和 CommonJS 模块的区别:
CommonJS 是对模块的浅拷⻉,ES6 Module 是对模块的引⽤,即 ES6 Module 只存只读,不能改变其值,也就是指针指向不能变,类似 const; import 的接⼝是 read-only(只读状态),不能修改其变量值。 即 不能修改其变量的指针指向,但可以改变变量内部指针指向,可以对 commonJS 对重新赋值(改变指针指向),但是对 ES6 Module 赋值会 编译报错。
ES6 Module 和 CommonJS 模块的共同点:
CommonJS 和 ES6 Module 都可以对引⼊的对象进⾏赋值,即对对象内部属性的值进⾏改变。
请你讲一下对于JS中模块的理解,Commonjs、 AMD和CMD分别有什么特点?
我的另外一篇关于javascript-AMD / CMD / UMD / CommonJS / ES Module/可选链
在 JavaScript 中,模块是一种组织代码的方式,可以将代码划分为独立的功能块,方便代码的复用和维护。常见的JavaScript模块化标准有CommonJS、AMD和CMD等。
- CommonJS
CommonJS 是 Node.js 最早提出的一种模块化规范,它的特点是同步加载模块,适用于服务端编程。它的基本思想是通过 require() 函数来同步地加载依赖的其他模块。通过 module.exports 来导出公共 API。
- AMD
AMD(Asynchronous Module Definition)规范采用异步方式加载模块,适用于浏览器端编程。它的基本思想是先定义好所有的模块,然后在需要使用的时候再使用 require() 函数进行异步获取,获取成功后再执行回调函数。它的主要特点是可以并行加载多个模块,有利于提高性能。
- CMD
CMD(Command)是一种类似于 AMD 的模块化规范,也是由 Sea.js 提出的。它的主要特点是延迟执行,只有在执行到需要使用模块的时候才会去加载依赖的模块。它的基本思想是通过 define() 函数来定义模块,然后使用 require() 函数来异步加载依赖的模块。
总之,CommonJS 规范适合服务端编程,AMD 规范适合浏览器端编程,CMD 则更适合大型项目和复杂的依赖管理。在实际开发中,我们需要根据具体需求选择适合的模块化规范来组织代码。同时,ES6 也引入了官方的模块标准,逐渐成为了前端模块化的主流标准。
23. 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 对象。
24. ajax、axios、fetch 的区别
1)AJAX
Ajax 即“AsynchronousJavascriptAndXML”(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。它是一种在
无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。
这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用 Ajax)如果需要更新内容,必须重载整
个网页页面。其缺点如下:
本身是针对 MVC 编程,不符合前端 MVVM 的浪潮基于原生 XHR 开发,XHR 本身的架构不清晰 不符合关注分离(Separation of Concerns)的原则 配置和调用方式非常混乱,而且基于事件的异步模型不友好。
2)Fetch
fetch 号称是 AJAX 的替代品,是在 ES6 出现的,使用了 ES6 中的 promise 对象。Fetch 是基于 promise 设计的。Fetch 的代码结构比
起 ajax 简单多。fetch 不是 ajax 的进一步封装,而是原生 js,没有使用 XMLHttpRequest 对象。
fetch 的优点:
语法简洁,更加语义化 基于标准 Promise 实现,支持 async/await 更加底层,提供的 API 丰富(request, response)
脱离了 XHR,是 ES 规范里新的实现方式
fetch 的缺点:
fetch 只对网络请求报错,对 400,500 都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请
求不能完成时,fetch 才会被 reject。
fetch 默 认 不 会 带 cookie , 需 要 添 加 配 置 项 : fetch(url, {credentials: ‘include’})
fetch 不 支 持 abort , 不 支 持 超 时 控 制 , 使 用 setTimeout 及 Promise.reject 的实现的超时控制并不能阻止请求过程继续在后台
运行,造成了流量的浪费
fetch 没有办法原生监测请求的进度,而 XHR 可以
3)Axios
Axios 是一种基于 Promise 封装的 HTTP 客户端,其特点如下:
浏览器端发起 XMLHttpRequests 请求
node 端发起 http 请求
支持 Promise API监听请求和返回
对请求和返回进行转化 取消请求
自动转换 json 数据
客户端支持抵御 XSRF 攻击
25. 对原型、原型链的理解
在 JavaScript 中是使用构造函数来新建一个对象的,每一个构造函 数的内部都有一个 prototype 属性,它的属性值是一个对象,这个
对象包含了可以由该构造函数的所有实例共享的属性和方法。当使用 构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个
指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针 被称为对象的原型。一般来说不应该能够获取到这个值的,但是现在
浏览器中都实现了 proto 属性来访问这个属性,但是最好不要 使用这个属性,因为它不是规范中规定的。
ES5 中新增Object.getPrototypeOf() 方法,可以通过这个方法来获取对象的原 型。
当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原
型,于是就这样一直找下去,也就是原型链的概念。
原型链的尽头一 般来说都是 Object.prototype 所以这就是新建的对象为什么能够使用 toString() 等方法的原因。
特点:JavaScript 对象是通过引用来传递的,创建的每个新对象实 体中并没有一份属于自己的原型副本。当修改原型时,与之相关的对
象也会继承这一改变。
[图片]
26. 原型链的终点是什么?如何打印出原型链的终点?
由于 Object 是构造函数,原型链终点 Object.prototype.proto, 而 Object.prototype.proto=== null // true,所以,原型链
的终点是 null。原型链上的所有原型都是对象,所有的对象最终都是由 Object 构造的,而 Object.prototype 的下一级是
Object.prototype.proto。
[图片]
原型链
[图片]
继承与原型链 参考MDN地址
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
27. 对作用域、作用域链的理解
1)全局作用域和函数作用域
1)全局作用域
最外层函数和最外层函数外面定义的变量拥有全局作用域
所有未定义直接赋值的变量自动声明为全局作用域
所有 window 对象的属性拥有全局作用域
全局作用域有很大的弊端,过多的全局作用域变量会污染全局命名空 间,容易引起命名冲突。
2)函数作用域
函数作用域声明在函数内部的变零,一般只有固定的代码片段可以访 问到 作用域是分层的,内层作用域可以访问外层作用域,反之不行
2)块级作用域
使用 ES6 中新增的 let 和 const 指令可以声明块级作用域,块级作用 域可以在函数中创建也可以在一个代码块中的创建(由{ }包裹的代 码片段)
let 和 const 声明的变量不会有变量提升,也不可以重复声明
在循环中比较适合绑定块级作用域,这样就可以把声明的计数器变量 限制在循环内部。
作用域链:
在当前作用域中查找所需变量,但是该作用域没有这个变量,那这个 变量就是自由变量。如果在自己作用域找不到该变量就去父级作用域
查找,依次向上级作用域查找,直到访问到 window 对象就被终止,这一层层的关系就是作用域链。
作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数。
作用域链的本质上是一个指向变量对象的指针列表。变量对象是一个 包含了执行环境中所有变量和函数的对象。作用域链的前端始终都是
当前执行上下文的变量对象。全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最后一个对象。
当查找一个变量时,如果当前执行环境中没有找到,可以沿着作用域链向后查找。
28. 对 this 对象的理解
this 是执行上下文中的一个属性,它指向最后一次调用这个方法的 对象。在实际开发中,this 的指向可以通过四种调用模式来判断。
第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象。
function fn() {
console.log(this)
}
fn() // this -> window
第二种是方法调用模式,如果一个函数作为一个对象的方法来调用时, this 指向这个对象。
const obj = {
name: 'xxx',
getName: function () {
console.log(this);
return this.name
}
}
obj.getName() // this-> obj
第三种是构造器调用模式,如果一个函数用 new 调用时,函数执行 前会新创建一个对象,this 指向这个新创建的对象。
第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。
使用apply或call调用 this 将会被显式设置为函数调用的第一个参数。bind也可,属于预处理this
其中 apply 方法接收两个参数:
一个是 this 绑定的对象,一个是参数数组。call 方法接收的参数, 第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。
也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。bind 方法通过传入一个对象,返回一个 this 绑定了传入对
象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。
这四种方式,使用构造器调用模式的优先级最高,然后是 apply、call 和 bind 调用模式,然后是方法调用模式,然后是函数调用模式。
29. call() 和 apply() 的区别?
它们的作用一模一样,区别仅在于传入参数的形式的不同。
apply 接受两个参数,第一个参数指定了函数体内 this 对象的指向, 第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类
数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数。
call 传入的参数数量不固定,跟 apply 相同的是,第一个参数也是 代表函数体内的 this 指向,从第二个参数开始往后,每个参数被依
次传入函数。
30. 异步编程的实现方式?
JavaScript 中的异步机制可以分为以下几种:
回调函数 的方式,使用回调函数的方式有一个缺点是,多个回调函数嵌套的时候会造成回调函数地狱,上下两层的回调函数间的代码耦
合度太高,不利于代码的可维护。
Promise 的方式,使用 Promise 的方式可以将嵌套的回调函数作为链式调用。但是使用这种方法,有时会造成多个 then 的链式调用, 可能会造成代码的语义不够明确。
generator 的方式,它可以在函数的执行过程中,将函数的执行权转 移出去,在函数外部还可以将执行权转移回来。当遇到异步函数执行
的时候,将函数执行权转移出去,当异步函数执行完毕时再将执行权 给转移回来。因此在 generator 内部对于异步操作的方式,可以以
同步的顺序来书写。使用这种方式需要考虑的问题是何时将函数的控 制权转移回来,因此需要有一个自动执行 generator 的机制,比如
说 co 模块等方式来实现 generator 的自动执行。
async 函数 的方式,async 函数是 generator 和 promise 实现的 一个自动执行的语法糖,它内部自带执行器,当函数内部执行到一个
await 语句的时候,如果语句返回一个 promise 对象,那么函数将 会等待 promise 对象的状态变为 resolve 后再继续向下执行。因此
可以将异步逻辑,转化为同步的顺序来书写,并且这个函数可以自动执行。
31. 对 Promise 的理解
Promise 是异步编程的一种解决方案,它是一个对象,可以获取异步 操作的消息,他的出现大大改善了异步编程的困境,避免了地狱回调,
它比传统的解决方案回调函数和事件更合理和更强大。 所谓 Promise,简单说就是一个容器,里面保存着某个未来才会结束
的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,
各种异步操作都可以用同样的方法进行处理。
1)Promise 的实例有三个状态:
Pending(进行中)
Resolved(已完成)
Rejected(已拒绝)
当把一件事情交给 promise 时,它的状态就是 Pending,任务完成了
状态就变成了 Resolved、没有完成失败了就变成了 Rejected。
2)Promise 的实例有两个过程:
pending -> fulfilled : Resolved(已完成)
pending -> rejected:Rejected(已拒绝)
注意:一旦从进行状态变成为其他状态就永远不能更改状态了。
Promise 的特点:
对象的状态不受外界影响。promise 对象代表一个异步操作,有三种 状态,pending(进行中)、fulfilled(已成功)、rejected(已失
败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是 promise 这个名字的由来——“承
诺”;
一旦状态改变就不会再变,任何时候都可以得到这个结果。promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled,从
pending 变为 rejected。这时就称为 resolved(已定型)。如果改变已经发生了,你再对 promise 对象添加回调函数,也会立即得到这
个结果。这与事件(event)完全不同,事件的特点是:如果你错过了它,再去监听是得不到结果的。
Promise 的缺点:
无法取消 Promise,一旦新建它就会立即执行,无法中途取消。 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
总结:
Promise 对象是异步编程的一种解决方案,最早由社区提出。
Promise
是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。 一个 Promise 实例有三种状态,分别是 pending、resolved 和
rejected,分别代表了进行中、已成功和已失败。实例的状态只能由pending 转变 resolved 或者 rejected 状态,并且状态一经改变,
就凝固了,无法再被改变了。
状态的改变是通过 resolve() 和 reject() 函数来实现的,可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原
型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的
末尾执行。
注意:在构造 Promise 的时候,构造函数内部的代码是立即执行的
32. Promise 解决了什么问题
在工作中经常会碰到这样一个需求,比如我使用 ajax 发一个 A 请求 后,成功后拿到数据,需要把数据传给 B 请求;那么需要如下编写代
码:
上面的代码有如下缺点:
后一个请求需要依赖于前一个请求成功后,将数据往下传递,会导致 多个 ajax 请求嵌套的情况,代码不够直观。
如果前后两个请求不需要传递参数的情况下,那么后一个请求也需要前一个请求成功后再执行下一步操作,这种情况下,那么也需要如上
编写代码,导致代码不够直观。
Promise 出现之后,代码变成这样:
这样代码看起了就简洁了很多,解决了地狱回调的问题。
33. 对 async/await 的理解
async/await 其实是 Generator 的语法糖,它能实现的效果都能用 then 链来实现,它是为优化 then 链而开发出来的。
从字面上来看, async 是“异步”的简写,await 则为等待,所以很好理解 async 用 于申明一个 function 是异步的,而 await 用于等待一个异步方法 执行完成。
当然语法上强制规定 await 只能出现在 asnyc 函数中,先 来看看 async 函数返回了什么:
所以,async 函数返回的是一个 Promise 对象。async 函数(包含 函数语句、函数表达式、Lambda 表达式)会返回一个 Promise 对象,
如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。
async 函数返回的是一个 Promise 对象,所以在最外层不能用 await 获取其返回值的情况下,当然应该用原来的方式:then() 链
来处理这个 Promise 对象,就像这样:
那如果 async 函数没有返回值,又该如何?很容易想到,它会返回 Promise.resolve(undefined)。
联想一下 Promise 的特点——无等待,所以在没有 await 的情况下 执行 async 函数,它会立即执行,返回一个 Promise 对象,并且,
绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二 致。
注意:Promise.resolve(x) 可以看作是 new Promise(resolve => resolve(x)) 的简写,可以用于快速封装字面量对象或其他对象,将
其封装成 Promise 实例。
34. async/await 的优势
单一的 Promise 链并不能发现 async/await 的优势,但是,如果需 要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来
了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它)。
假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。仍然用 setTimeout 来模拟异步操作:
[图片]
现在用 Promise 方式来实现这三个步骤的处理:
[图片]
输出结果 result 是 step3() 的参数 700 + 200 = 900。doIt() 顺 序执行了三个步骤,一共用了 300 + 500 + 700 = 1500 毫秒,和
console.time()/console.timeEnd() 计算的结果一致。 如果用 async/await 来实现呢,会是这样:
[图片]
结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是 清晰得多,几乎跟同步代码一样
35. async/await 对比 Promise 的优势
代码读起来更加同步,Promise 虽然摆脱了回调地狱,但是 then 的 链式调⽤也会带来额外的阅读负担
Promise 传递中间值⾮常麻烦,⽽async/await⼏乎是同步的写法,⾮常优雅 错误处理友好,
async/await 可以⽤成熟的 try/catch,Promise 的 错误捕获⾮常冗余 调试友好,
Promise 的调试很差,由于没有代码块,你不能在⼀个返 回表达式的箭头函数中设置断点,如果你在⼀个.then 代码块中使⽤
调试器的步进(step-over)功能,调试器并不会进⼊后续的.then 代 码块,因为调试器只能跟踪同步代码的每⼀步。
36. 对象创建的方式有哪些?
一般使用字面量的形式直接创建对象,但是这种创建方式对于创建大量相似对象的时候,会产生大量的重复代码。但 js 和一般的面向对
象的语言不同,在 ES6 之前它没有类的概念。但是可以使用函数来进行模拟,从而产生出可复用的对象创建方式,常见的有以下几种:
1)第一种是工厂模式,工厂模式的主要工作原理是用函数来封装 创建对象的细节,从而通过调用函数来达到复用的目的。但是它有一
个很大的问题就是创建出来的对象无法和某个类型联系起来,它只是 简单的封装了复用代码,而没有建立起对象和类型间的关系。
2)第二种是构造函数模式。
js 中每一个函数都可以作为构造函数, 只要一个函数是通过 new 来调用的,那么就可以把它称为构造函数。
执行构造函数首先会创建一个对象,然后将对象的原型指向构造函数 的 prototype 属性,然后将执行上下文中的 this 指向这个对象,
最后再执行整个函数,如果返回值不是对象,则返回新建的对象。因 为 this 的值指向了新建的对象,因此可以使用 this 给对象赋值。
构造函数模式相对于工厂模式的优点是,所创建的对象和构造函数建 立起了联系,因此可以通过原型来识别对象的类型。但是构造函数存
在一个缺点就是,造成了不必要的函数对象的创建,因为在 js 中函 数也是一个对象,因此如果对象属性中如果包含函数的话,那么每次
都会新建一个函数对象,浪费了不必要的内存空间,因为函数是所有 的实例都可以通用的。
3)第三种模式是原型模式,因为每一个函数都有一个 prototype 属性,这个属性是一个对象,它包含了通过构造函数创建的所有实例
都能共享的属性和方法。因此可以使用原型对象来添加公用属性和方 法,从而实现代码的复用。这种方式相对于构造函数模式来说,解决
了函数对象的复用问题。但是这种模式也存在一些问题,一个是没有 办法通过传入参数来初始化值,另一个是如果存在一个引用类型如
Array 这样的值,那么所有的实例将共享一个对象,一个实例对引用 类型值的改变会影响所有的实例。
4)第四种模式是组合使用构造函数模式和原型模式,这是创建自定义类型的最常见方式。因为构造函数模式和原型模式分开使用都存
在一些问题,因此可以组合使用这两种模式,通过构造函数来初始化对象的属性,通过原型对象来实现函数方法的复用。这种方法很好的
解决了两种模式单独使用时的缺点,但是有一点不足的就是,因为使用了两种不同的模式,所以对于代码的封装性不够好。
5)第五种模式是动态原型模式,这一种模式将原型方法赋值的创建过程移动到了构造函数的内部,通过对属性是否存在的判断,可以
实现仅在第一次调用函数时对原型对象赋值一次的效果。这一种方式很好地对上面的混合模式进行了封装。
6)第六种模式是寄生构造函数模式,这一种模式和工厂模式的实 现基本相同,我对这个模式的理解是,它主要是基于一个已有的类型,
在实例化时对实例化的对象进行扩展。这样既不用修改原来的构造函 数,也达到了扩展对象的目的。它的一个缺点和工厂模式一样,无法
实现对象的识别。
37. 对象继承的方式有哪些?
1)第一种是以原型链的方式来实现继承,
但是这种实现方式存在 的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享, 容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。
2)第二种方式是使用借用构造函数的方式,这种方式是通过在子 类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。
3)第三种方式是组合继承,组合继承是将原型链和借用构造函数 组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属 性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构 造函数,造成了子类型的原型中多了很多不必要的属性。
4)第四种方式是原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。
5)第五种方式是寄生式继承,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是自定义类型时。缺点是没有办法实现函数的复用。
6)第六种方式是寄生式组合继承,组合继承的缺点就是使用超类 型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式 组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。
38. 哪些情况会导致内存泄漏
以下四种情况会造成内存的泄漏:
意外的全局变量:由于使用未声明的变量,而意外的创建了一个全局 变量,而使这个变量一直留在内存中无法被回收。
被遗忘的计时器或回调函数:设置了 setInterval 定时器,而忘记 取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被 一直留在内存中,而无法被回收。
脱离 DOM 的引用:获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。
闭包:不合理的使用闭包,从而导致某些变量一直被留在内存当中。
39. 数组去重
1、[...new Set(arr)]
2、Array.from(new Set(arr))
3、利用indexOf去重
4、利用includes
5、利用filter
40. 闭包
闭包:函数包裹函数,有权访问另一个函数作用域中的变量的函数,把内部函数return出去形成私有变量
用途:1.可以在函数外部读取函数内部成员
2.让函数内成员始终存活在内存中
缺点:1、需要手动释放(res = null,没有变量引用它,则会被当垃圾进行回收)资源;
2、过多的闭包,会造成程序变慢(操作不当容易造成内存溢出)
ps:手动释放:
function foo(){
var a = 1;
return function bar(){
++a;
console.log(a)
}
}
var bar = foo();
bar() // 2
bar = null; // 释放闭包
什么叫事件委托呢?
它还有一个名字叫事件代理
事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件
适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress;
不适合用事件委托的事件:focus,blur,mousemove;
事件冒泡?
子元素到父级到body到html到document
事件捕捉,document-html
有了解堆栈吗?什么在栈上,什么在值上?
堆栈是计算机中的一种数据结构,用于存储临时数据。栈(Stack)是一种后进先出(Last-In-First-Out,LIFO)的数据结构,而堆(Heap)是一个动态分配的内存区域。
在堆栈中,数据通常被存储在栈上或者堆上。栈上存储的是局部变量、参数、返回地址等,它们的生命周期随着函数的执行而改变。值类型的数据(如整型、浮点型、字符型等)通常存储在栈上,因为它们的空间大小是固定的。
堆上存储的是动态分配的内存,它的生命周期不受函数的限制,需要手动释放。引用类型的数据(如数组、类、接口等)通常存储在堆上,因为它们的大小是不确定的,需要在运行时分配内存。
总之,栈上存储的是临时数据,堆上存储的是动态分配的数据。在进行程序设计时,需要合理地使用堆栈,避免出现内存泄漏或者栈溢出等问题。
eventLoop的宏任务与微任务的区别?promise与setTimeOut谁先执行?
eventLoop是JavaScript中的一种机制,用于管理代码的执行顺序,主要包括宏任务和微任务。其中,宏任务是指一些较大的任务,如script代码块、setTimeout、setInterval、XHR等,而微任务则是指一些较小的任务,如Promise、Object.observe等。
宏任务和微任务的执行顺序存在区别。在JS引擎中,执行一个宏任务后,会检查是否有微任务需要执行,如果有则优先执行所有微任务,然后才执行下一个宏任务。
Promise与setTimeout的执行顺序是不同的。Promise是一种异步操作,它的回调函数被放在微任务中进行调度,即它的回调函数会在当前宏任务执行完毕后、下一个宏任务开始执行前执行。而setTimeout是一种宏任务,它的回调函数会被放在宏任务队列中,即它的回调函数会在当前宏任务执行完毕后、下一个宏任务开始执行时执行。
因此,Promise的回调函数通常会比setTimeout的回调函数先执行。但是,在一些特殊情况下,如果Promise的状态已经被resolve,而且在setTimeout的回调函数执行之前,这种情况下setTimeout的回调函数会先执行,因为它被放在宏任务队列中,而Promise的回调是被放在微任务队列中进行调度的。
浅拷贝与深拷贝的区别?
浅拷贝和深拷贝都是用于复制对象的。它们的区别在于复制过程中是否会复制对象的子对象。
浅拷贝会创建一个新的对象,这个新的对象和原对象的基本数据类型的值相同,但是它们在内存中的引用是独立的。但是,如果原对象中有引用类型的成员变量,复制后的对象中引用类型的成员变量仍然会保存着同一份内存地址,因此修改时会互相影响。简单来说,浅拷贝只会复制对象的第一层属性,而不会复制子对象。
而深拷贝则是创建一个完全独立的新对象,它的值和原对象完全一样,但是在内存中是完全独立的。即使原对象中有复杂的嵌套结构,深拷贝也会递归地复制子对象,直到所有子对象都是独立的为止。因此,深拷贝不会出现因子对象引用地址共享而带来的问题。
可以使用以下方法来实现对象的浅拷贝和深拷贝:
- 浅拷贝可以使用Object.assign()、扩展操作符(…)、Array.slice()等方法;
- 深拷贝可以使用JSON.parse(JSON.stringify(obj))、递归实现的深拷贝函数等方法。
需要注意,在实际应用中,深拷贝可能会消耗更多的内存空间和更长的时间。因此,在做复制操作时,需要考虑具体情况来选择使用哪种方式。
window.onload
window.onload 是在页面所有资源都加载完毕之后才会执行的,在此之前,如果有需要加载资源的元素,比如图片、视频、音频等,都必须要等待这些资源加载完成之后才能执行。这意味着,如果你的页面有很多资源需要加载,那么 window.onload 可能会比较晚执行。
与之类似的还有 DOMContentLoaded 事件,它在 DOM 树构建完成之后就会执行,不等待其他资源的加载。如果你只是想在 DOM 树构建完成之后马上执行某些代码,那么使用 DOMContentLoaded 事件会比较合适。
另外,如果你使用了 jQuery 等第三方库,它们通常都提供了自己的 ready() 方法,用于在 DOM 树构建完成之后执行代码,这种方式也比较方便。例如:
$(document).ready(function() {
// TODO: 在 DOM 树构建完成后执行的代码
})
简述一下 src 与 href 的区别?
src 用于替换当前元素,href 用于在当前文档和引用资源之间确立联系。
src 是 source 的缩写,指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在位置;在请求 src 资源时会将其指向的资源下载并应用到文档内,例如 js 脚本,img 图片和 frame 等元素。
当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等元素也如此,类似于将所指向资源嵌入当前标签内。这也是为什么将js 脚本放在底部而不是头部。
href 是 Hypertext Reference 的缩写,指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的链接,如果我们在文档中添加
alert(fn(1,2,3,4,5));
function fn(){
var n=0
for(var i=0;i<arguments.length;i++){
n+=arguments[i];
};
return n;
}
jq实现查找插槽节点?
在jQuery中,可以使用contents()
方法来查找元素节点的所有子节点,包括文本节点、注释节点和元素节点。这些节点都可以被通过选择器进行过滤,因此可以通过contents()
方法来查找插槽节点。
插槽节点是HTML5中的一种新特性,它用于动态加载HTML内容。可以通过以下方式来在jQuery中查找插槽节点:
// 通过ID查找元素节点
var element = $("#elementID");
// 查找插槽节点
var slotNodes = element.contents().filter(function() {
return this.nodeType === Node.ELEMENT_NODE && this.nodeName === "SLOT";
});
// 遍历插槽节点
slotNodes.each(function() {
console.log("插槽节点[%o]被找到", this);
});
在上述代码中,element.contents()
方法返回了element
元素的所有子节点,然后通过.filter()
方法对子节点进行过滤,只保留节点类型为元素节点(this.nodeType === Node.ELEMENT_NODE
)且节点名称为SLOT
的节点(this.nodeName === "SLOT"
)。
最后,可以通过.each()
方法对查找到的所有插槽节点进行遍历,做任何需要的操作。
刷新页面,js请求一般会有哪些地方有缓存处理?
DNS缓存:短时间内多次访问某个网站,在限定时间内,不用多次访问DNS服务器。
CDN缓存:内容分发网络(人们可以在就近的代售点取火车票了,不用非得到火车站去排队)
浏览器缓存:浏览器在用户磁盘上,对最新请求过的文档进行了存储。
服务器缓存:将需要频繁访问的Web页面和对象保存在离用户更近的系统中,当再次访问这些对象的时候加快了速度
引用传递,值传递你有用过?
JavaScript中的参数传递有两种方式:值传递和引用传递。
在值传递中,函数接收的是实参的一个副本,而不是实参自身,因此在函数内部对形参的修改不会影响到实参的值。JavaScript中的基本数据类型(如布尔型、数字型、字符串型等)都是按值传递的方式进行传递,例如:
var num = 10;
function changeNum(num) {
num = 20;
console.log("函数内部num为:", num); // 20
}
changeNum(num);
console.log("函数外部num为:", num); // 10
在引用传递中,函数接收的是实参的引用,即内存地址的复制,因此在函数内部对形参的修改会影响到实参的值。JavaScript中的对象(包括数组、函数等)都是按引用传递的方式进行传递,例如:
var arr = [1, 2, 3];
function pushNum(arr) {
arr.push(4);
console.log("函数内部arr为:", arr); // [1, 2, 3, 4]
}
pushNum(arr);
console.log("函数外部arr为:", arr); // [1, 2, 3, 4]
需要注意的是,虽然JavaScript中的对象是按引用传递的方式进行传递,但是如果在函数内部重新赋值该形参,则会断开形参和实参之间的联系,使得形参变成了局部变量,不再引用实参所在的内存地址。例如:
var obj = {name: "Tom", age: 18};
function changeObj(obj) {
obj = {name: "Jerry", age: 20};
console.log("函数内部obj为:", obj); // {name: "Jerry", age: 20}
}
changeObj(obj);
console.log("函数外部obj为:", obj); // {name: "Tom", age: 18}
总的来说,不同的传递方式适用于不同的场景,需要根据具体情况来选择使用哪种方式。
实践题
一串字符串,我要把它倒叙,怎么弄?
使用reverse().数组的方法;
var name = “My city is WH”;
var resultStr = name.split(‘’).reverse().join(‘’);
console.log(resultStr); // HW si ytic yM