前端:每天5道面试题(JavaScript)

前端:每天5道面试题(JavaScript)

1. 说说JavaScript中的数据类型?
基本数据类型:
  1. Number:用于表示数字,可以是整数或浮点数,还包括 Infinity-InfinityNaN(Not a Number)。
  2. String:表示文本数据,可以是一个字符或一串字符,使用单引号、双引号或反引号来定义。
  3. Boolean:有两个值,truefalse,用于进行逻辑操作。
  4. Undefined:当一个变量被声明了但没有被赋值时,它的值就是 undefined
  5. Null:表示一个空值,通常用于表示某个对象变量现在没有指向任何对象。
  6. Symbol(ES6 新增):表示独一无二的值,主要用于创建对象的唯一属性名。
  7. BigInt(近期新增):用于表示大于 2^53 - 1 的整数。
引用数据类型:
  1. Object:JavaScript 中的对象是键值对的集合。
  2. Array:用于表示元素的有序集合。
  3. Function:函数实际上是一种特殊类型的对象,可以被调用。
  4. Date:用于处理日期和时间。
  5. RegExp:用于执行正则表达式匹配。
2. 说说你对闭包的理解?闭包使用场景

我的理解是 就是函数包函数 里面的函数引用外层函数的变量的组合就是闭包

闭包是指有权访问另一个函数作用域中变量的函数

最简单的例子
function A() {
  let count = 1
  function B() {
    count++
  }
  B()
}
使用场景
  1. 数据封装和信息隐藏:通过闭包,可以创建私有变量,避免全局变量的污染。

    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
    
  2. 在循环中创建局部作用域:解决循环中使用异步函数导致的变量作用域问题。

    for (var i = 0; i < 5; i++) {
      (function(j) {
        setTimeout(function() {
          console.log(j);
        }, j * 1000);
      })(i);
    }
    
  3. 函数柯里化:通过闭包,可以实现函数的柯里化,预先设置一些参数。

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
 */
转载请说明出处内容投诉
CSS教程_站长资源网 » 前端:每天5道面试题(JavaScript)

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买