📅  最后修改于: 2023-12-03 15:16:16.509000             🧑  作者: Mango
JavaScript 是当前最流行的编程语言之一,被广泛运用于 Web 开发、移动应用、桌面应用等领域。以下是一些常见的 JavaScript 面试问题及其答案。
JavaScript 中共有七个数据类型:number、string、boolean、null、undefined、symbol 和 object.
可以使用 typeof
操作符来判断变量的数据类型,例如:
typeof 3; // "number"
typeof "hello"; // "string"
typeof true; // "boolean"
typeof null; // "object" (这是一个历史遗留问题)
typeof undefined; // "undefined"
typeof Symbol(); // "symbol"
typeof {}; // "object"
可以使用 parseInt()
或 parseFloat()
函数将一个字符串转换为数字。其中,parseInt()
函数将字符串转换为整数,parseFloat()
函数将字符串转换为浮点数。例如:
parseInt("123"); // 123
parseFloat("3.14"); // 3.14
注意,如果字符串不能被转换为数字,那么结果将为 NaN
。
在 JavaScript 中,每个对象都有一个内部属性 [[Prototype]]
,它指向了对象的原型。原型可以被理解为一个对象的“模板”,它包含了该对象所拥有的属性和方法。如果访问一个对象的属性或方法时,发现该对象本身并没有这个属性或方法,那么 JavaScript 引擎会沿着原型链向上查找,直到找到该属性或方法为止。
可以使用 Object.create()
函数来创建一个对象的原型。该函数接受一个参数,即新对象的原型。例如:
const person = {
name: "Tom",
age: 30,
greet() {
console.log(`Hello, my name is ${this.name}, and I am ${this.age} years old.`);
}
};
const me = Object.create(person);
me.name = "Jerry";
me.age = 25;
me.greet(); // "Hello, my name is Jerry, and I am 25 years old."
在上面的例子中,我们通过 Object.create()
函数创建了一个名为 person
的对象,并将其作为 me
对象的原型。因此,me
对象继承了 person
对象中的属性和方法。
JavaScript 支持原型继承、构造函数继承和组合继承。其中,原型继承和构造函数继承都是比较简单直接的继承方式,但它们各自存在一些缺点。组合继承是一种将原型继承和构造函数继承结合起来的继承方式,较为灵活。例如:
function Animal(name) {
this.name = name;
}
Animal.prototype.greet = function() {
console.log(`Hello, my name is ${this.name}.`);
}
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log("Woof!");
}
const myDog = new Dog("Rover");
myDog.greet(); // "Hello, my name is Rover."
myDog.bark(); // "Woof!"
在上面的例子中,我们首先定义了一个 Animal
构造函数和一个 Dog
构造函数。Dog
构造函数继承了 Animal
构造函数,并添加了自己的方法 bark
。通过 Object.create()
函数,我们将 Dog
对象的原型指向了 Animal
对象的原型,实现了原型继承。
作用域链指的是 JavaScript 中一个函数嵌套另一个函数时,内部函数可以访问外部函数中定义的变量的机制。具体来说,函数在被创建时就会创建一个作用域链,其中包含了该函数被定义时所处的外部函数或全局作用域,以及外部函数或全局作用域的外部函数或全局作用域,以此类推,直到顶层作用域为止。
闭包是指一个函数可以“记住”并访问它所在的词法作用域,即使该函数在这个词法作用域之外执行。简单来说,闭包就是在一个函数中定义另一个函数,并返回这个函数,从而使得该函数可以访问它所在的词法作用域中的变量。例如:
function outer() {
const name = "Tom";
function inner() {
console.log(`Hello, ${name}!`);
}
return inner;
}
const sayHello = outer();
sayHello(); // "Hello, Tom!"
在上面的例子中,函数 outer
中定义了一个局部变量 name
和一个内部函数 inner
,并将 inner
函数返回。由于 inner
函数中使用了变量 name
,所以 inner
函数可以访问 outer
函数的词法作用域中的变量 name
。当我们执行 outer
函数并将其返回值赋给变量 sayHello
后,sayHello
就成为了一个闭包,可以在任何时候执行 inner
函数并访问变量 name
。
闭包可以将变量“隐藏”在函数的内部,防止外部代码直接访问和修改这些变量,从而提高了代码的安全性和可维护性。例如:
function createCounter() {
let count = 0;
return {
increment() {
count++;
console.log(`Count: ${count}`);
},
decrement() {
count--;
console.log(`Count: ${count}`);
}
};
}
const counter = createCounter();
counter.increment(); // "Count: 1"
counter.increment(); // "Count: 2"
counter.decrement(); // "Count: 1"
在上面的例子中,我们使用闭包创建了一个计数器对象 counter
,它包含了两个方法 increment
和 decrement
,分别实现了计数器加一和减一的功能。由于变量 count
只能在创建 counter
对象的闭包中被访问和修改,所以我们可以通过 counter
对象来间接地访问和修改变量 count
。这种方式提高了代码的安全性和可维护性。