前端:每天5道面试题(JavaScript)
1. 说说JavaScript中的数据类型?
基本数据类型:
- Number:用于表示数字,可以是整数或浮点数,还包括 Infinity、-Infinity 和 NaN(Not a Number)。
- String:表示文本数据,可以是一个字符或一串字符,使用单引号、双引号或反引号来定义。
- Boolean:有两个值,true 和 false,用于进行逻辑操作。
- Undefined:当一个变量被声明了但没有被赋值时,它的值就是 undefined。
- Null:表示一个空值,通常用于表示某个对象变量现在没有指向任何对象。
- Symbol(ES6 新增):表示独一无二的值,主要用于创建对象的唯一属性名。
- BigInt(近期新增):用于表示大于 2^53 - 1 的整数。
引用数据类型:
- Object:JavaScript 中的对象是键值对的集合。
- Array:用于表示元素的有序集合。
- Function:函数实际上是一种特殊类型的对象,可以被调用。
- Date:用于处理日期和时间。
- RegExp:用于执行正则表达式匹配。
2. 说说你对闭包的理解?闭包使用场景
我的理解是 就是函数包函数 里面的函数引用外层函数的变量的组合就是闭包
闭包是指有权访问另一个函数作用域中变量的函数
最简单的例子
function A() { let count = 1 function B() { count++ } B() }
使用场景
-
数据封装和信息隐藏:通过闭包,可以创建私有变量,避免全局变量的污染。
function createCounter() { let count = 0; return { increment: function() { count++; return count; }, decrement: function() { count--; return count; } }; } const counter = createCounter(); console.log(counter.increment()); // 1 console.log(counter.increment()); // 2 console.log(counter.decrement()); // 1
-
在循环中创建局部作用域:解决循环中使用异步函数导致的变量作用域问题。
for (var i = 0; i
-
函数柯里化:通过闭包,可以实现函数的柯里化,预先设置一些参数。
function curriedAdd(a) { return function (b) { return a + b } } const addFive = curriedAdd(5) console.log(addFive(3)) // 输出:8
闭包的注意事项
虽然闭包非常有用,但它们也可能导致内存泄漏,因为闭包中的外部函数作用域的变量不会被垃圾回收器回收,直到闭包本身被销毁。因此,在使用闭包时应当注意不要过度使用,以避免占用过多内存。
3. JavaScript中的原型,原型链分别是什么?
一张图就能全部理解
__proto__: 隐式原型,是每个对象都具有的属性
prototype: 显式原型,是Function独有的属性
function Student(name, age) { this.name = name; this.age = age; } // 原型上挂载方法 Student.prototype.getName = function () { return this.name; }; const s1 = new Student("张三", 18); // 构造函数 // 这样的就可以访问到原型上的方法了 console.log("🚀 ~ s1:", s1.getName()); // 当实例对象,在自身找不到时,会向上一级原型(Student.prototype)对象上查找; // 如果还找不到,则 又会到原型对象上一级的原型对象(Object.prototype)上查找,这种链式查找机制,称为原型链
4. Javascript如何实现继承?
在 JavaScript 中实现继承主要有几种方式:
原型链继承
// 父类构造函数 function Animal(name) { this.name = name; this.colors = ["black", "white"]; this.sleep = function () { console.log(this.name + "正在睡觉!"); }; } // 子类构造函数 function Cat(name) { this.name = name; } // 原型指向 Animal Cat.prototype = new Animal(); // Cat就可以继承Animal的属性和方法了 var Cat1 = new Cat("Cat1"); Cat1.sleep(); // Cat1正在睡觉! console.log(Cat1.name); // Cat1 console.log(Cat1.colors); // [ 'black', 'white' ] var Cat2 = new Cat("Cat2"); // 因为两个实例使用的是同一个原型对象,内存空间是共享的 Cat2.colors.push("yellow"); console.log(Cat1.colors); // [ 'black', 'white', 'yellow' ] console.log(Cat2.colors); // [ 'black', 'white', 'yellow' ]
构造函数继承
构造函数继承的关键在于,在子类构造函数中调用父类构造函数,并将子类实例的this绑定到父类构造函数上,从而使得父类的属性成为子类实例的属性
// 父类构造函数 function Animal(name) { this.name = name; this.colors = ["black", "white"]; this.sleep = function () { console.log(this.name + "正在睡觉!"); }; } Animal.prototype.do = function () { console.log(this.name + "正在做动作!"); }; // 子类构造函数 function Cat(name) { // 在子类构造函数中调用父类构造函数 Animal.call(this, name); } var Cat1 = new Cat("Cat1"); console.log(Cat1.name); // Cat1 console.log(Cat1.colors); // [ 'black', 'white' ] // Cat1.do(); // 报错 cat.do is not a function var Cat2 = new Cat("Cat2"); Cat2.colors.push("yellow"); console.log(Cat1.colors); // ["black", "white"]; console.log(Cat2.colors); // ["black", "white", "yellow"]; // 相比第一种原型链继承方式,父类的引用属性不会被共享,优化了第一种继承方式的弊端 // 但是只能继承父类的实例属性和方法,不能继承原型属性或者方法
组合继承
结合了原型链继承和构造函数继承的优点,组合继承能够实现函数复用(通过原型链继承原型上的属性和方法)同时还能保证每个实例都有自己的属性(通过构造函数继承实例属性)
// 父类构造函数 function Animal(name) { this.name = name; this.colors = ["black", "white"]; this.sleep = function () { console.log(this.name + "正在睡觉!"); }; } Animal.prototype.do = function () { console.log(this.name + "正在做动作!"); }; // 子类构造函数 function Cat(name) { // 在子类构造函数中调用父类构造函数 Animal.call(this, name); } // 原型链继承,继承父类的原型方法 Cat.prototype = new Animal(); var Cat1 = new Cat("Cat1"); Cat1.sleep(); // Cat1正在睡觉! Cat1.do(); // Cat1正在做动作! 这样do方法可以被访问 var Cat2 = new Cat("Cat2"); Cat2.colors.push("yellow"); console.log(Cat1.colors); // [ 'black', 'white' ] console.log(Cat2.colors); // [ 'black', 'white', 'yellow' ] 这样不会被共享
原型式继承
Object.create方法实现普通对象的继承
var Animal = { name: "动物", colors: ["black", "white"], sleep: function () { console.log(this.name + "正在睡觉!"); }, }; var Cat = Object.create(Animal, { name: { value: "猫" } }); Cat.sleep(); // 猫正在睡觉! var Dog = Object.create(Animal, { name: { value: "狗" } }); Dog.sleep(); // 狗正在睡觉! Dog.colors.push("yellow"); console.log(Cat.colors); // [ 'black', 'white', 'yellow' ] console.log(Dog.colors); // [ 'black', 'white', 'yellow' ] 和原型链继承一样共享内存
寄生式继承
寄生式继承是一种使用函数来增强对象的继承方式。它基于原型式继承的基础上,通过创建一个仅用于封装继承过程的函数,在函数内部以某种方式来增强对象,最后返回对象。
var Animal = { name: "动物", colors: ["black", "white"], sleep: function () { console.log(this.name + "正在睡觉!"); }, }; // 封装一个函数 function clone(original) { // 通过调用函数创建一个新对象 var clone = Object.create(original); // 以某种方式来增强这个对象 clone.do = function () { console.log(this.name + "正在做动作!"); }; clone.updateName = function (name) { this.name = name; }; // 返回这个对象 return clone; } var dog = clone(Animal); dog.do(); // 动物正在睡觉! dog.updateName("小狗"); dog.do(); // 小狗正在睡觉! var cat = clone(Animal); cat.colors.push("yellow"); console.log(cat.colors); // [ 'black', 'white', 'yellow' ] console.log(dog.colors); // [ 'black', 'white', 'yellow' ] 共享内存了
寄生组合式继承
这个继承也是ES6 中的extends 关键字,实现 JavaScript 的继承原理,相对于其他的继承是最优的解法
// 父类构造函数 function Animal(name) { this.name = name; this.colors = ["black", "white"]; this.sleep = function () { console.log(this.name + "正在睡觉!"); }; } // 父类原型方法 Animal.prototype.do = function () { console.log(this.name + "正在做动作!"); }; // 寄生式继承,用于继承父类原型 function clone(subType, superType) { var prototype = Object.create(superType.prototype); // 创建对象 prototype.constructor = subType; // 增强对象 subType.prototype = prototype; // 指定对象 } // 子类构造函数 function Cat(name) { Animal.call(this, name); // 构造函数继承 } clone(Cat, Animal); var Cat1 = new Cat("Cat1"); Cat1.do(); // Cat1正在做动作! 可以访问父类原型上的方法 var Cat2 = new Cat("Cat2"); Cat2.colors.push("yellow"); console.log(Cat1.colors); // [ 'black', 'white' ] console.log(Cat2.colors); // [ 'black', 'white', 'yellow' ] 内存空间不共享
5. JavaScript事件循环机制?
JavaScript 在设计之初便是单线程,程序运行时,只有一个线程存在,同一时间只能做一件事。
为了解决单线程运行阻塞问题,JavaScript用到了计算机系统的一种运行机制,这种机制就叫做事件循环(Event Loop)
在JavaScript中,所有的任务都可以分为
- 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
- 异步任务:异步执行的任务,比如ajax网络请求,setTimeout 定时函数等
异步任务:
异步任务分为宏任务(macrotask)与 微任务 (microtask),不同的任务会依次进入自身对应的队列中,然后等待Event Loop将它们依次压入执行栈中执行。
微任务对应有:
- next tick queue:process.nextTick
- other queue:Promise的then回调、queueMicrotask
宏任务对应有:
- timer queue:setTimeout、setInterval
- poll queue:IO事件
- check queue:setImmediate
- close queue:close事件
async function async1() { console.log("1"); await async2(); console.log("2"); } async function async2() { console.log("3"); } console.log("4"); setTimeout(function () { console.log("5"); new Promise((resolve, reject) => { console.log("13"); resolve(); console.log("14"); }).then(function () { console.log("15"); }); }, 0); setTimeout(function () { console.log("6"); }, 3); setImmediate(() => console.log("7")); process.nextTick(() => console.log("8")); async1(); new Promise(function (resolve) { console.log("9"); resolve(); console.log("10"); }).then(function () { console.log("11"); }); console.log("12"); /** * 从上往下执行 执行同步任务 打印4 * 567是宏任务压入宏任务队列 * 8是微任务 * 执行async1 打印1 3 * 2在await后面 压入为微队列 * 执行new Promise 打印9 10 .then压入微队列 * 执行打印 12 * 主线程执行完成 打印4 1 3 9 10 12 * 处理微任务队列 打印 8 2 11 * 执行宏任务5 打印 5 13 14 压入微队列.then 15 执行微任务 打印 15 * 执行宏任务6 打印 6 * 执行微任务7 打印 7 * 4 1 3 9 10 12 8 2 12 11 5 13 14 15 6 7 */
还没有评论,来说两句吧...