1.JavaScript 的数据类型有哪些?
- 基本类型:(
Number、String、Boolean、null、undefined、symbol) - 引用类型:主要(Object、Array、Function)
2.== 和 === 区别,分别在哪种情况下使用?
区别:
- 相等操作符(
==)会先进行类型转换,然后再进行值的比较,全等操作符(===)不会进行类型的转换。 null和undefined比较,相等操作符返回true,全等操作符返回false。
3.typeof 与 instanceof 的区别?
typeof操作符返回一个字符串,表示变量的类型。
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof null // 'object'
typeof [] // 'object'
typeof {} // 'object'
typeof console // 'object'
typeof console.log // 'function'
instanceof操作符用于检测变量是否出现在对象的原型链上面。
区别:
typeof返回的是变量类型的字符串,instanceof返回的是一个布尔值。instanceof可以判断复杂的引用类型,但是没办法正确判断基本数据类型。typeof可以判断基本数据类型(null除外),无法准确判断引用数据类型(function除外)。
如果需要检测变量的数据类型,可以通过 Object.prototype.toString.call 方法,调用该方法统一返回格式 [object Xxx] 的字符串。
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call('1') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(function(){}) // "[object Function]"
Object.prototype.toString.call(null) //"[object Null]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(/123/g) //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
Object.prototype.toString.call([]) //"[object Array]"
Object.prototype.toString.call(document) //"[object HTMLDocument]"
Object.prototype.toString.call(window) //"[object Window]"
使用此方法,我们可以封装一个全局通用的类型判断方法:
function getType(obj){
let type = typeof obj;
if (type !== "object") { // 先进行 typeof 判断,如果是基本数据类型直接返回
return type;
}
// 对于 typeof 返回的 object 的,在进行以下的判断,通过正则返回结果。
return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1');
}
4.JavaScript 原型和原型链?
总结:
- 一切对象都继承自
Object对象,Object对象直接继承根源对象null - 一切的函数对象(包括
Object对象),都是继承自Function对象 Object对象直接继承自Function对象Function对象的__proto__会指向自己的原型对象,最终还是继承自Object对象
5.说说你对作用域链的理解?
- 等待整理
6.谈谈你对 this 对象的理解?
- 全局对象中的指向:指向的是
window - 全局作用域或者普通函数中的
this:指向全局的window this永远指向最后调用它的那个对象,不是在箭头函数的情况下new关键词改变了this的指向apply、call、bind:改变this的指向,不是箭头函数的情况下- 箭头函数中的
this:箭头函数本身没有this,看外层是否有函数,有就是函数的this,没有就是window - 匿名函数中的
this:永远指向window,匿名函数的执行环境具有全局性,因此this指向window
7.说说 new 操作付具体干了什么?
- 创建一个新的对象
obj - 将对象与构造函数(
constructor)通过原型链连接起来 - 将构建函数中的
this绑定到新建的对象obj上 - 根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理
8.bind、call、apply 区别?
bind、call、apply 的调用方式
fun.call(thisArg, param1, param2, ...)
fun.apply(thisArg, [param1,param2,...])
fun.bind(thisArg, param1, param2, ...)
参数
thisArg(可选):
fun的this指向thisArg对象- 非严格模式下:
thisArg指定为null,undefined,fun中的this指向window对象. - 严格模式下:
fun的this为undefined - 值为原始值(数字,字符串,布尔值)的
this会指向该原始值的自动包装对象,如 String、Number、Boolean
param1,param2(可选): 传给fun的参数。
- 如果
param不传或为null/undefined,则表示不需要传入任何参数. apply第二个参数为数组,数组内的值为传给fun的参数。
call/apply与bind的区别
执行:
call/apply改变了函数的this上下文后马上执行该函数bind则是返回改变了上下文后的函数,不执行该函数
返回值:
call/apply返回fun的执行结果bind返回fun的拷贝,并指定了fun的this指向,保存了fun的参数。
call与apply的唯一区别
传给fun 的参数写法不同:
apply是第2个参数,这个参数是一个数组:传给fun参数都写在数组中。call从第2~n的参数都是传给fun的。
9.JavaScript 中执行上下文和执行栈是什么?
- 等待整理
10.说说 JavaScript 中的事件模型?
- 等待整理
11.解释下什么是事件代理?
- 等待整理
12.说说你对闭包的理解?
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围)这样的组合就是闭包(closure)
也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。
闭包的3个特性
- 函数嵌套函数
- 函数内部可以引用函数外部的参数和变量
- 参数和变量不会被垃圾回收机制回收
闭包的优点
- 保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
- 在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
- 匿名自执行函数可以减少内存消耗
闭包的缺点
- 其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null
- 其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响
闭包的使用场景
- 创建私有变量
- 延长变量的生命周期
13.谈谈 JavaScript 中的类型转换机制?
- 等待整理
14.深拷贝和浅拷贝的区别?
赋值与深/浅拷贝的区别
- 赋值操作符是把一个对象的引用赋值给一个变量,所以变量中存储的是对象的引用
- 浅拷贝是拷贝源对象的每个属性,但如果属性值是对象,那么拷贝的是这个对象的引用。所以源对象和拷贝对象之间共享嵌套对象。
- 深拷贝与浅拷贝不同的地方在于,如果属性值为对象,那么会拷贝该对象。源对象和拷贝对象之间不存在共享的内容。
浅拷贝
浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址。
浅拷贝的方法有哪些
Object.createObject.assignArray.prototype.slice()、Array.prototype.concat()- 拓展运算符(
...)
注意:拓展运算符(...)只有第一层是深拷贝,第二层及更深层是浅拷贝。
手写浅拷贝
function shallowCopy(obj) {
if (typeof obj !== 'object' || obj !== null) return;
let objCopy = obj instanceof Array ? [] : {};
for (let key in obj) { // for...in 可以用于对象中的遍历,遍历出来的是对象的 key 值,在数组中是数组的下标
if (obj.hasOwnProperty(key)) { // 不需要obj隐式原型上的属性
objCopy[key] = obj[key];
}
}
return objCopy;
}
深拷贝
深拷贝是开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。
深拷贝的方法有哪些
_.cloneDeep()(Lodash的方法)jQuery.extend()JSON.stringify(JSON.parse(xx))- 手写递归循环
注意:JSON.stringify 的方式存在弊端,会忽略 undefined、symbol、函数、RegExp 等
手写深拷贝
function deepCopy(obj) {
let objCopy = obj instanceof Array ? [] : {}; // 创建一个空对象,用于存放拷贝后的对象
for (let key in obj) {
// 遍历对象的所有属性
if (obj.hasOwnProperty(key)) {
// 使用 hasOwnProperty 方法确保只拷贝对象自身的属性
if (obj[key] instanceof Object || obj[key] instanceof Array) {
// 判断属性值是否为引用类型
objCopy[key] = deepCopy(obj[key]);
} else {
objCopy[key] = obj[key]; // 如果是原始类型,则直接赋值
}
}
}
return objCopy;
}
15.JavaScript 字符串的常用方法有哪些?
concat(str1, str2, /* …, */ strN)
slice(indexStart, indexEnd)
substr(start, length) // 已废弃
substring(indexStart, indexEnd)
trim()
trimLeft()
trimRight()
replace(pattern, replacement)
includes(searchString, position
split(separator, limit)
// ……
16.数组常用方法有哪些?
push(element0, element1, /* … ,*/ elementN)
unshift(element1, element2, /* …, */ elementN)
concat(value0, value1, /* … ,*/ valueN)
splice(start, deleteCount, item1, item2, itemN)
pop()
shift()
slice(start, end)
find(callbackFn, thisArg)
findIndex(callbackFn, thisArg)
includes(searchElement, fromIndex)
// ……
17.说说你对事件循环的理解?
在 JavaScript 中,所有任务都可以分为
- 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
- 异步任务:异步执行的任务,比如
Ajax网络请求,setTimeout定时函数等。
宏任务与微任务
在 JavaScript 中,异步任务还可以细分为 宏任务 与 微任务
微任务
一个 微任务 的执行时机是在主函数执行结束之后、当前宏任务结束之前
宏任务
宏任务 的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
常见的宏任务与微任务
| 宏任务(macrotask) | 微任务(microtask) | |
|---|---|---|
| 谁发起的 | 宿主(Node、浏览器) | JS引擎 |
| 具体事件 |
1. script (可以理解为外层同步代码)
2. setTimeout / setInterval
3. UI rendering / UI事件
4. postMessage,MessageChannel
5. setImmediate,I/O(Node.js)
6. requestAnimationFrame
7. ajax
8. click / mousedown
|
1. Promise.then / Async-Await
2. MutaionObserver
3. Object.observe(已废弃;Proxy 对象替代)
4. process.nextTick(Node.js)
|
| 谁先运行 | 后运行 | 先运行 |
| 会触发新一轮Tick吗 | 会 | 不会 |
JS中微任务和宏任务执行顺序
- 首先执行当前代码(同步任务),直到遇到第一个宏任务或微任务。
- 如果遇到微任务,则将它添加到微任务队列中,继续执行同步任务。
- 如果遇到宏任务,则将它添加到宏任务队列中,继续执行同步任务。
- 当前任务执行完毕后,JavaScript 引擎会先执行所有微任务队列中的任务,直到微任务队列为空。
- 然后执行宏任务队列中的第一个任务,直到宏任务队列为空。
- 重复步骤 4 和步骤 5,直到所有任务都被执行完毕。 需要注意的是,微任务比宏任务优先级要高,因此在同一个任务中,如果既有微任务又有宏任务,那么微任务会先执行完毕。而在不同的任务中,宏任务的执行优先级要高于微任务,因此在一个宏任务执行完毕后,它才会执行下一个宏任务和微任务队列中的任务。
举个例子,假设当前代码中有一个 setTimeout 和一个 Promise,它们分别对应一个宏任务和一个微任务。
那么执行顺序如下:
- 执行当前代码,将 setTimeout 和 Promise 添加到宏任务和微任务队列中。
- 当前任务执行完毕,JavaScript 引擎先执行微任务队列中的 Promise 回调函数。
- 微任务队列为空后,再执行宏任务队列中的 setTimeout 回调函数。
需要注意的是,在一些特殊情况下,微任务和宏任务的执行顺序可能会发生变化,比如在使用 MutationObserver 监听 DOM 变化时,它会被视为一个微任务,但是它的执行顺序可能会比其他微任务更靠后。因此,需要根据具体情况来理解和处理微任务和宏任务的执行顺序。
思考题
console.log(0);
function a() {
console.log(1);
new Promise((resolve, reject) => {
console.log(2);
resolve();
})
.then(() => {
console.log(3);
})
.then(() => {
console.log(4);
});
console.log(5);
setTimeout(() => {
console.log(6);
}, 50);
}
async function b() {
console.log(7);
await console.log(8);
new Promise((resolve, reject) => {
console.log(9);
resolve();
})
.then(() => {
console.log(10);
})
.then(() => {
console.log(11);
});
await console.log(12);
console.log(13);
setTimeout(() => {
console.log(14);
}, 10);
}
a();
console.log(15);
b();
console.log(16);
// 输出结果是什么?
18.JavaScript 本地存储的方式有哪些?
- cookie
- localStorage
- sessionStorage
- indexedDB
关于 cookie 、sessionStorage 、 localStorage 三者的区别
- 存储大小:
cookie数据大小不能超过4k,sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大 - 有效时间:
localstorage存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;sessionStorage数据在当前浏览器窗口关闭后自动删除;cooke设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭 - 数据与服务器之间的交互方式,
cookie的数据会自动的传递到服务器,服务器端也可以写cookie到客户端;sessionStorage和localstorage不会自动把数据发给服务器,仅在本地保存
19.ajax 原理是什么?
AJAX 全称(Async Javascript and XML)
即异步的 JavaScript 和 XML,是一种创建交互式网页应用的网页开发技术,可以在不重新加载整个网页的情况下,与服务器交换数据,并且更新部分网页
Ajax 的原理简单来说通过 XmlHttpRequest 对象来向服务器发异步请求,从服务器获得数据,然后用 JavaScript 来操作 DOM 而更新页面
20.什么是防抖和节流?
防抖和节流本质上是优化高频率执行代码的一种手段。
- 节流:
n秒内只运行一次,若在n秒内重复触发,只有一次生效 - 防抖:
n秒后在执行该事件,若在n秒内被重复触发,则重新计时
节流的代码实现
function throttled(fn, delay) {
let timer = null;
let starttime = Date.now();
return function () {
let curTime = Date.now(); // 当前时间
let remaining = 0;
if (curTime - starttime > delay) {
remaining = delay;
starttime = curTime; // 重置开始时间
} else {
remaining = delay - (curTime - starttime); // 从上一次到现在,还剩多少多余时间
}
let context = this;
let args = arguments;
clearTimeout(timer);
if (remaining <= 0) {
fn.apply(context, args);
starttime = Date.now();
} else {
timer = setTimeout(fn, remaining);
}
};
}
防抖代码实现
function debounce(func, wait, immediate) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout); // timeout 不为 null
if (immediate) {
let callNow = !timeout; //第一次会立即执行,以后只有事件执行后才会再次触发
timeout = setTimeout(function () {
timeout = null;
}, wait);
if (callNow) {
func.apply(context, args);
}
} else {
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
}
};
}
区别
相同点:
- 都可以通过使用
setTimeout实现 - 目的都是,降低回调执行频率。节省计算资源
不同点:
函数防抖,在一段连续操作结束后,处理回调,利用clearTimeout和setTimeout实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能函数防抖关注一定时间连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次
例如,都设置时间频率为500ms,在2秒时间内,频繁触发函数,节流,每隔 500ms 就执行一次。防抖,则不管调动多少次方法,在2s后,只会执行一次
21.web 常见的攻击方式有哪些?
- 等待整理
22.说说 JavaScript 中内存泄漏的几种情况?
内存泄漏(Memoryleak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存
并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费
程序的运行需要内存。只要程序提出要求,操作系统或者运行时就必须供给内存
对于持续运行的服务进程,必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。
常见的可能导致内存泄漏的方式
- 闭包
- 定时器 (
setInterval) - 死循环
- 递归
- 未清理
DOM元素的引用
23.JavaScript 如何实现继承?
- 等待整理
24.说说 JavaScript 数字精度丢失问题,如何解决?
- 等待整理
25.举例说明你对尾递归的理解,有哪些应用场景?
- 等待整理