面试题
# 箭头函数和普通函数的区别
# 1. 定义方式
# 2. 参数处理
一个参数可省略 (),没参数默认为 ()
# 3. 函数体
返回一个简单的表达式可以省略 {}
# 4. this 指向
箭头函数没有自己的 this 对象, 而是从其作用域链的上一层继承 this。 箭头函数中 this 的指向在它被定义的时候就已经确定了。
构造函数内部的 this 指向当前对象的实例。而箭头函数的 this 的指向是在定义时确定的,不是在调用时确定
# 5. 没有 call, apply, bind
箭头函数的 this 指向不能通过 call、apply、bind 等方法改变。
# 6. 不可当做构造函数
不可以对箭头函数使用 new 命令。
箭头函数不能作为构造函数的原因主要有以下几点:
this的绑定方式不同:箭头函数中的this是词法作用域,它会捕获定义时的this值。而普通函数中的this是动态的,取决于函数调用的方式。构造函数需要依赖this来初始化新对象,因此必须使用普通函数。没有
prototype属性:箭头函数没有自己的prototype属性,而构造函数需要通过prototype来为实例添加属性和方法。由于箭头函数缺少这一特性,它无法正确地创建和初始化新对象。new关键字不适用:尝试用new操作符调用箭头函数会导致错误,因为箭头函数不是设计用来与new一起使用的。使用new调用普通函数会创建一个新的空对象,并将this绑定到这个新对象上,但箭头函数不会这样做。
let fun = () => {console.log("是一个箭头函数")}
new fun(); // TypeError: fun is not a constructor
new 一个构造函数的步骤
假设我们有一个构造函数 Person:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
const person1 = new Person('Alice', 30);
会创建一个新的对象;
const obj = {};设置原型链,也就是将新对象的
__proto__属性指向prototype属性, 这使得新对象可以继承构造函数原型上的属性和方法。obj.__proto__ = Person.prototype;绑定
this将构造函数内部的this绑定到这个新创建的对象let result = Person.call(obj, 'Alice', 30);执行构造函数
// 构造函数内部的代码执行,将 name 和 age 属性添加到 obj 上 obj.name = 'Alice'; obj.age = 30;如果函数没有返回对象则返回 this
if(typeof result == 'object'){ return result; }else{ return obj }
代码实现
function myNew(constructor, ...args) {
if(typeof constructor !== 'function'){
throw new TypeError('Constructor is not a function');
}
// 1. 创建一个空对象
const obj = {};
// 2. 将对象的 __proto__ 属性指向构造函数的 prototype 属性
// obj.__proto__ = constructor.prototype;
Object.setPrototypeOf(obj, constructor.prototype);
// 3. 将构造函数内部的 this 绑定到新对象,并执行构造函数
const result = constructor.call(obj, args);
// 4. 如果构造函数返回一个对象,则返回该对象;否则返回新创建的对象
return (result !== null && typeof result === 'object') ? result : obj;
}
# 7. 箭头函数没有 arguments
# Symbol
ES6 中的 Symbol 是一种新的基本数据类型 , 表示独一无二的值。 Symbol 的值是唯一且不可变的,可以用作对象属性的键,以防止属性名冲突。
# 用作对象属性名
使用 Symbol 定义的对象属性不会出现在普通的遍历中,如 for...in、for...of 循环或 Object.keys()、Object.getOwnPropertyNames() 中。它们可以通过 Object.getOwnPropertySymbols() 或者 Reflect.ownKeys() 获取。
# 内置 Symbol
Symbol.iterator:为每个对象定义了默认的迭代器。该迭代器可以被for...of循环使用。Symbol.match:指定了匹配的是正则表达式而不是字符串, 还用于标识对象是否具有正则表达式的行为。Symbol.species:是个函数值属性,其被构造函数用以创建派生对象。Symbol.toPrimitive:用于将对象转换为基本值(如数字、字符串或布尔值)。
Symbol 可以用于自定义对象的迭代行为,如 Symbol.iterator,可以在任意对象上实现遍历数据能力。
let obj = {
[Symbol.iterator]: function* () {
yield 1;
yield 2;
yield 3;
}
};
for (let value of obj) {
console.log(value); // 分别输出 1, 2, 3
}
Symbol.match是一个特殊的符号(symbol),它定义了当一个对象被用作 String.prototype.match 的参数时的行为。简单来说,它决定了这个对象是否可以用于匹配字符串。
class ConditionalMatcher {
constructor(private target: string) {
}
[Symbol.match](string: string) {
console.log(`Matching string: ${ string }`);
if (string.includes(this.target)) {
return ['matched']; // 匹配成功
} else {
return null; // 匹配失败
}
}
}
const matcher = new ConditionalMatcher('hello');
const result1 = 'hello world'.match(matcher);
console.log(result1); // 输出: ["matched"]
const result2 = 'goodbye world'.match(matcher);
console.log(result2); // 输出: null
// 一个没有提供 Symbol.toPrimitive 属性的对象,参与运算时的输出结果。
const obj1 = {};
console.log(+obj1); // NaN
console.log(`${obj1}`); // "[object Object]"
console.log(obj1 + ""); // "[object Object]"
// 接下面声明一个对象,手动赋予了 Symbol.toPrimitive 属性,再来查看输出结果。
const obj2 = {
[Symbol.toPrimitive](hint) {
if (hint === "number") {
return 10;
}
if (hint === "string") {
return "hello";
}
return true;
},
};
console.log(+obj2); // 10 — hint 参数值是 "number"
console.log(`${obj2}`); // "hello" — hint 参数值是 "string"
console.log(obj2 + ""); // "true" — hint 参数值是 "default"
+obj2 使用的是 number 作为 hint,因为一元加号运算符明确要求将对象转换为数值。
${obj2} 使用的是 string 作为 hint,因为模板字符串插值明确要求将对象转换为字符串。
obj2 + "" 使用的是 default 作为 hint,默认情况下会先尝试 number,如果失败则尝试 string。
# ES Module 和 Commonjs 的区别
相同点:两者都是 JavaScript 的模块化规范,都可以用来导入导出模块。
不同点:
ES Module (ESM)是ES6引入的标准化模块系统,支持动态导入,在编译时加载,可以异步加载模块。CommonJS (CJS)出现在ES6之前,不支持动态导入,在运行时加载,采取的是同步加载模块的方式。
# 1. 导入导出
在 ESM 中,模块的导入和导出是通过 import 和 export 关键字来实现的。导出的是绑定的引用,模块外部能够实时观察到模块内部的变化。
在 CJS 中,模块的导入和导出是通过 require 和 module.exports/exports 来实现的。导出的是值的拷贝,当导入模块执行完毕,模块内部的变化不会影响这个值。
# 2. 加载机制
ES Modules在编译时加载, 采用异步加载模块方式,支持动态导入(按需加载导入模块),会更适合在浏览器环境中使用。
CommonJS 在运行时加载, 采用同步加载模块方式, 所以不支持动态导入; 同步加载是等模块加载完毕后才会执行代码。 这种加载方式通常在服务端使用, 因为服务端文件都在本地,不会引起明显的延迟问题, 但不适合客户端 (如浏览器) , 因为会导致浏览器在等待模块下载和解析期间暂停执行。
# 3. 运行环境
ES Modules 是 ES6 引入的官方标准化模块系统, 支持异步加载, 被现代浏览器广泛支持,Node.js 现在也支持 ES Modules。
CommonJS 主要用于 Node.js 环境。
# 4. 变量作用域
ES Modules 的顶层 this 是 undefined,因为 ESM 模块在严格模式下执行,并且每个模块都有自己的顶层作用域,模块内部的变量默认不会被全局共享。
CommonJS 每个文件都是其模块的顶层作用域,因为 CJS 不会使用严格模式,它们的导入导出基于对象的引用,所以可以共享模块内部的变量。
# 5. 使用场景
ES Modules更适合现代JavaScript开发,因为ESM是JavaScript官方的模块系统,现代浏览器原生支持,支持静态分析、异步加载、严格模式等特性,有利于前端代码拆分,以懒加载方式优化加载性能。ES Modules更适合需要利用静态分析来进行Tree Shaking(树摇优化)优化的项目:Tree Shaking是一种只打包必要模块代码的方法, 依赖于ESM的静态结构可以移除未被使用的代码。因为ESM的导入在顶层,且不是动态生成的,所以Tree Shaking可以在编译时进行静态分析从而确定哪些模块和函数被实际使用,哪些没有。,从而移除未使用的代码。CommonJS更适合Node.js服务端开发,模块加载是同步的,方便变量共享。- 混合场景:当需要在同一个项目中混合使用两种模块系统时,可以通过设置
Webpack或其他模块打包工具来处理。
# 扩展运算符使用场景
扩展运算符(…)允许一个可迭代的表达式如数组或者字符串被展开为一系列的参数。
常见的使用场景:
- 数组操作:合并数组、复制数组、将数组作为参数传递给函数等。
- 函数参数:将不定数量的参数收集为一个数组,或者将数组解构为函数的参数。
- 对象操作:合并对象、创建新的对象副本。
- 字符串操作:将字符串转换为字符数组,方便遍历和操作每个字符。
# ES6 的 Proxy 可以实现什么功能?
可以用来创建虚拟化的对象,通过自定义基本操作(如属性查找、赋值、枚举、函数调用等)的行为来实现各种高级功能。主要功能包括:
- 拦截和定义对象的基本操作,比如属性访问、赋值、删除、函数调用等。
- 实现自定义的行为,比如数据验证、属性保护、自动填充默认值等。
- 实现观察者模式,可以轻松实现对象的监听和变化追踪。
- 创建虚拟属性和方法,使得代码更灵活和动态。
- 代理外部接口,可以将 API 调用封装为本地对象的属性访问操作。
# 解构使用场景
- 数组
- 对象
- 函数参数
- 交换变量
- 解构时变量重命名
# rest 参数
rest 参数是 ES6 中新增的一项语法, 用于表示不确定数量的参数,允许你将不定数量的参数收集为一个数组。
将传入的多个参数包装成一个参数,在参数列表中,rest 参数必须是最后一个参数,并且只能有一个。
- 对象,数组,函数参数将剩余部分用
rest来接收 - 可以使代码更清晰