一、变量相关
1.var、let、const 的差异?
var、let、const 都是用来声明变量的关键字
1. var
var的作用域是函数级别的,即声明变量的作用域在整个函数内部有效
2. let
let的作用域更加严格,它的作用域是块级别的,即只在声明所在的代码块内有效,
- const
const用来定义常量,也是在块级别作用域内有效。常量使用const声明后不可更改,但是可以通过修改对象或数组的属性值或元素来改变它们
- 谈谈作用域?
JavaScript中有两种作用域:
- 全局作用域(Global Scope)
全局作用域中定义的变量用户后面所有的函数都可以直接访问,而在函数内定义的变量只能在该函数内部使用。
- 局部作用域(Local Scope)
局部作用域是在函数内部定义的变量作用的范围,只有函数内部才能访问到。在函数内部定义的变量不能被在函数外部的其他函数或代码块所访问,称为局部变量。(例如let,const)
Static算不算全局变量?
静态变量的作用域是全局的,也可以说是类级别的。尽管静态变量的作用域是全局的,但是静态变量与全局变量不同,不会直接暴露在全局对象中,而是通过类来进行访问。通过(类名+“.”)来访问
3.什么是变量提升?
变量提升(Hoisting)是 JavaScript 中的一个特性,它允许在声明变量之前就可以使用变量。
console.log(foo); // 输出:undefined
var foo = 'hello';
在上面的代码中,我们先使用了变量foo并尝试输出其值,但foo在这一行代码前并没有定义。但由于变量提升的特性,foo的声明会被提升到作用域顶部,因此foo仍然是定义的,只是值为undefined,所以该代码输出为undefined。
二、数据类型
4.JavaScript 数据类型有哪些?
1. 数值类型(Number):表示数字,包含整数和浮点数。
2. 字符串类型(String):表示一串文本字符
3. 布尔类型(Boolean):两个值,true or false
4. undefined类型:表示变量声明了但未初始化时的默认值。
5. null类型:表示一个空对象。
6. 对象类型(Object):用于复杂数据结构。
7. 数组类型(Array):表示一组有序的数据集合
8. Symbol类型:用于唯一标识对象属性。
5.原始数据类型和引用数据类型的区别?
JavaScript 中的数据类型可以分为两类:原始数据类型(Primitive Types)和引用数据类型(Reference Types)。
原始数据类型具有不可变性(immutable),它们的值在使用过程中保持不变。包括:Number、String、Boolean、null、undefined 和 Symbol。如果对原始数据进行复制,修改一个变量不会影响另一个变量
引用数据类型,值可以发生改变,因为它们存储在内存的堆中,按引用访问。包括:Object、Array 和 Function 等。变量会指向同一个对象,通过一个变量修改对象将会影响到另一个对象。
6.谈谈 undefined 和 null ?
undefined 表示一个变量声明了但并没有初始化,或者是一个对象属性为空时的默认值
null 通常被用于表示意图清晰的空值,而 undefined 更多的是表示一个错误或者是一个未知值。
7.type of null 的结果是什么?
type of null 的结果是 "object"。
8.JavaScript 如何做类型转换?
1. 显式类型转换(强制类型转换):通过调用类型转换函数来进行类型转换,比如使用 String() 方法将一个值转换成字符串。
2. 隐式类型转换(自动类型转换):在 JavaScript 中进行运算时,如果遇到不同类型的值,JavaScript 会自动对其中一个或多个值进行类型转换。比如在将一个字符串和一个数字相加时,JavaScript 会将数字转换为字符串然后进行拼接。
3. 装箱和拆箱:对于基本类型和对象类型之间的转换,JavaScript 会自动地进行装箱和拆箱操作。装箱(Boxing)指的是将基本类型转换为对应的对象类型,比如将数字转换为 Number 对象;拆箱(Unboxing)则是将对象类型转换为基本类型,比如将 Number 对象转换为数字。
9.==、 === 和 Object.is() 的区别是什么?
严格相等比较,使用 ===
抽象相等比较,使用 ==
10.JavaScript 创建对象有哪些方式?
1. 对象字面量(Object Literal)
```
const person = {
name: 'John',
age: 30,
gender: 'male'
};
```
2.构造函数(Constructor Function)
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
const person = new Person('John', 30, 'male');
```
3.原型
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
Person.prototype.getDetails = function() {
return `${this.name}, ${this.age} years old, ${this.gender}.`;
};
const person = new Person('John', 30, 'male');
person.getDetails(); // 返回 "John, 30 years old, male."
4. Object.create
```
const person = Object.create(null);
person.name = 'John';
person.age = 30;
person.gender = 'male';
```
5. ES6 类(ES6 Class)
```
class Person {
constructor(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
getDetails() {
return `${this.name}, ${this.age} years old, ${this.gender}.`;
}
}
const person = new Person('John', 30, 'male');
person.getDetails(); // 返回 "John, 30 years old, male."
```
11.如何理解继承和原型链?
继承是面向对象编程中非常重要的一个概念,它是指一个对象可以从另一个对象(称为父对象或超类)继承属性和方法的行为。
原型链是JavaScript中实现继承的机制之一,通过原型链我们可以实现基于原有对象的继承。每个JavaScript对象都有一个内部属性 [[Prototype]],它指向该对象的原型对象。如果该原型对象自己还有 [[Prototype]],那么就形成了一个链式结构,我们称之为原型链。
在原型链的结构中,当我们访问一个对象的属性时,JavaScript会首先在该对象本身的属性中查找,如果找不到,就会在该对象的原型对象中查找,一直找到 Object.prototype,如果在 Object.prototype 中还是找不到该属性,则返回 undefined。
通过原型链机制,在某个对象的原型对象中定义的属性和方法就可以被该对象继承,子类就能够使用到父类的属性和方法,从而实现了继承。当子类想要访问父类中定义的属性或方法时,只需要往原型链中向上查找即可。
12.如何判断一个对象属于某个类?
1. instanceof 操作符 检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。
2. constructor 属性
constructor 属性包含一个指向创建该对象的构造函数的引用,可以根据此属性判断当前对象的类型。
3. Object.prototype.toString.call 方法
toString()
方法返回一个表示该对象的字符串。可以使用它检查对象类。
13.Map 和 WeakMap 有什么区别?
它们之间的区别在于其键和值的引用类型和引用计数方式。
WeakMap
对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。
14.如何实现深拷贝和浅拷贝?
如果要拷贝一个副本,副本中的对象更改后,不影响到原对象(或修改原对象不影响副本),即为深拷贝,否则,为浅拷贝。
如果将一个对象赋值给另一个对象,也是浅拷贝。
扩展运算符 ...
可以很方便地浅拷贝对象和数组:
.slice()
.assign()
Array.from()
深拷贝(deep copy)
我们希望的深拷贝是无论是嵌套多少层数组和对象,拷贝的数组、对象和原来的数组、对象互不受影响。
如果数组和对象中还包含其他数组和对象,拷贝这些对象、数组就需要使用深拷贝,否则,改变嵌套的引用将会影响到嵌套的原数组和对象。
使用JSON.stringify() 进行深拷贝
也可使用lodash
、rfdc
第三方库进行拷贝
深拷贝和浅拷贝的区别在于,浅拷贝对于数组和对象仅仅包含原始值时表现良好,但是数组和对象中嵌套其他数组和对象便不能正确拷贝。
15.什么是闭包?
当一个函数被定义时,它可以访问自身函数作用域内的变量和参数,也可以访问其外部函数作用域内的变量和参数,当这个函数作为其他函数的返回值返回时,由于该函数仍然保留对其外部引用的访问,所以就形成了闭包。
闭包的一个非常重要的特性是,它可以在函数外部访问函数内部的变量,而不需要将这些变量作为参数传递到函数外部,因此非常适合用于实现高级的编程技巧
16.this 的指向有哪些?
"this" 是 JavaScript 中的一个关键字,指向当前函数执行的上下文对象。具体来说,它可以指向以下几个对象:
1. 全局对象:如果 "this" 出现在函数外部,则指向全局对象。
2. 调用函数的对象:如果 "this" 出现在方法中,则指向调用该方法的对象。
3. new 关键字创建的对象:如果 "this" 出现在构造函数中,则指向 new 关键字创建的新对象。
4. 使用 apply、call 或 bind 方法调用函数时指定的对象:可以在调用函数时通过这些方法指定 "this" 指向的对象。
值得注意的是,如果有嵌套函数的情况,"this" 的指向可能会发生变化,需要根据具体情况进行判断。
17.类数组的转化方式有哪些?
类数组对象是具有类似数组的属性和长度的对象,但没有数组原型方法,对类数组对象使用数组方法通常会出现错误。
1. Array.from() 方法
该方法可以将类数组对象转换为一个真正的数组。
2. Array.prototype.slice 方法
该方法可以将数组的一部分(slice)作为新数组返回,因为类数组对象具有 length 属性,我们可以借用 slice 方法将类数组对象转换为真正的数组。
3. 借助 spread 运算符
在 ES6 中,我们可以使用 ... 操作符(spread 运算符)将一个可遍历对象转换为数组。
18.如何模拟实现函数方法:call()、apply()、bind()?
call()、apply()和bind()是JavaScript中的三个重要的函数方法,它们常常被用来实现继承、函数柯里化、函数借调等高级的函数操作。
call()方法用于将当前函数的this指向另一个对象,同时可以传入一些参数。
apply()方法和call()方法类似,区别在于后者接受一个参数列表,前者接受一个数组作为参数。apply()方法的模拟实现只需对数组进行处理传递给指定函数即可:
bind()方法用于实现函数柯里化、this指向修改、函数参数预设等操作。模拟实现需要借助于闭包保存参数,并返回一个新函数以实现惰性调用。
19.立即调用函数表达式(IIFE)有什么特点?
IIFE,全称为 Immediately Invoked Function Expression,即立即调用函数表达式,是一种可立即执行的匿名函数,用于将变量封装在一个作用域内,避免变量名冲突或全局变量污染的问题。是JavaScript闭包的常见应用。
1. IIFE 是一个匿名函数表达式,所以不会污染全局命名空间;
2. IIFE 在定义后会立即执行,可以拥有自己独立的作用域;
20.箭头函数有什么特点?
箭头函数是ES6中的一种语法,它可以让我们更简洁地编写函数,并具有以下几个特点:
1. 更简洁的语法
箭头函数可以让我们更简洁地编写函数,省略了ES5中的function关键字和大括号等语法。
2. 没有this绑定
箭头函数没有自己的this绑定,而是会捕获其外层函数的this值,这也被称为“词法作用域”。
3.没有arguments对象
由于箭头函数没有自己的作用域,所以它没有arguments对象,但是可以通过剩余参数(rest parameter)来获取函数的所有参数。
4.不能用作构造函数
箭头函数没有自己的构造函数,不能使用new来实例化对象,否则会抛出错误。
21.如何实现防抖和节流?
在实际的业务开发中,会遇到一些频繁触发的事件,比如浏览器窗口的 resize、srcoll 等,处于性能的考虑,需要减少触发数量,或者延迟触发事件的时间等,因此就用到了防抖(debounce)和节流(throttle)。
防抖的原理就是:用户可以尽管触发事件,但是一定在事件触发 n 秒后才执行。如果在此期间又触发了这个事件,那么就从新触发的时间点算起,n 秒后才执行。
防抖是根据延迟的时间点触发事件的。
节流
节流是以时间段为节点,如果事件触发在这个时间段内,那么就只触发一次。
22.如何模拟实现 Promise?
Promise是JavaScript中常用的异步解决方案之一,它可以优雅地处理异步操作,避免了回调函数嵌套的问题。
随着调用嵌套的增加,代码层次变得更深,维护难度也随之增加,这就被称为“回调地狱”。此时 Promise 闪亮登场了,它可以很好地解决回调地狱问题,且代码简单易读
Promise 对象用于表示一个异步操作的最终完成(或失败)及其结果值
如果成功则调用 resolve
,如果出现 error
则调用 reject
。
Promise 支持链式调用,.then、.catch 和 .finally 方法均支持链式调用。其中,.then 方法接收两个参数,第一个参数处理已决议状态(fulfilled)的回调函数,第二个参数则处理已经拒绝(rejected)的回调函数。每一个 .then() 方法返回的是一个新生成的 Promise 对象,这个对象可被用作链式调用