JavaScript 由 Brendan Eich 于 1995 年开发,是最流行的 Web 开发语言之一。
它最初是为了开发动态网页而创建的。每个 JS 程序都称为脚本,它可以附加到任何网页的 HTML 中。这些脚本在页面加载时自动运行。
一种最初用于创建动态网页的语言,现在可以在服务器上执行,实际上可以在任何由 JavaScript 引擎组成的设备上执行。
基本的Javascript面试题和答案合集:
1. javascript 中存在哪些不同的数据类型?
要知道 JavaScript 变量的类型,我们可以使用typeof运算符。
原始类型
- 字符串 - 它代表一系列字符并用引号写入。字符串可以用单引号或双引号表示。
例子 :
var str = "Vivek Singh Bisht" ; //使用双引号
var str2 = 'John Doe' ; //使用单引号
- Number - 它代表一个数字,可以写有或没有小数。
例子 :
var x = 3; //without decimal
var y = 3.6; //with decimal
- BigInt - 此数据类型用于存储超出 Number 数据类型限制的数字。它可以存储大整数,并通过在整数文字上添加“n”来表示。
例子 :
var bigInteger = 234567890123456789012345678901234567890;
- Boolean - 它代表一个逻辑实体,只能有两个值:true 或 false。布尔值通常用于条件测试。
例子 :
var a = 2;
var b = 3;
var c = 2;
(a == b) // returns false
(a == c) //returns true
- Undefined - 当一个变量被声明但没有赋值时,它的值为 undefined 并且它的类型也是 undefined。
例子 :
var x; // value of x is undefined
var y = undefined; // we can also set the value of a variable as undefined
- Null - 它代表不存在或无效的值。
例子 :
var z = null;
- Symbol - 它是 JavaScript 的 ES6 版本中引入的一种新数据类型。它用于存储匿名且唯一的值。
例子 :
var symbol1 = Symbol ( 'symbol' );
- typeof 原始类型:
typeof "John Doe" // Returns "string"
typeof 3.14 // Returns "number"
typeof true // Returns "boolean"
typeof 234567890123456789012345678901234567890n // Returns bigint
typeof undefined // Returns "undefined"
typeof null // Returns "object" (kind of a bug in JavaScript)
typeof Symbol('symbol') // Returns Symbol
非原始类型
原始数据类型只能存储单个值。为了存储多个复杂的值,使用了非原始数据类型。
对象 - 用于存储数据集合。
例子:
// Collection of data in key-value pairs
var obj1 = {
x: 43,
y: "Hello world!",
z: function(){
return this.x;
}
}
// Collection of data as an ordered list
var array1 = [5, "Hello", true, 4.1];
*注意- 重要的是要记住,任何不是原始数据类型的数据类型,在 javascript 中都是对象类型。
2. Javascript常见面试题有哪些:解释javascript的提升。
提升是 javascript 的默认行为,其中所有变量和函数声明都移到顶部。
这意味着无论变量和函数在哪里声明,它们都被移动到作用域的顶部。作用域可以是本地的,也可以是全局的。
Javascript面试题解析示例 1:
hoistedVariable = 3;
console.log(hoistedVariable); // outputs 3 even when the variable is declared after it is initialized
var hoistedVariable;
示例 2:
hoistedFunction(); // Outputs " Hello world! " even when the function is declared after calling
function hoistedFunction(){
console.log(" Hello world! ");
}
示例 3:
// Hoisting takes place in the local scope as well
function doSomething(){
x = 33;
console.log(x);
var x;
}
doSomething(); // 输出 33,因为局部变量“x”在局部作用域内被提升
**注意 - 不提升变量初始化,仅提升变量声明:
var x;
console.log(x); // Outputs "undefined" since the initialization of "x" is not hoisted
x = 23;
**注意 - 为避免提升,你可以通过在代码顶部使用“use strict”以严格模式运行 javascript:
"use strict";
x = 23; // Gives an error since 'x' is not declared
var x;
3.“==”和“===”运算符的区别。
两者都是比较运算符。两个运算符的区别在于,“==”用于比较值,而“===”用于比较值和类型。
例子:
var x = 2;
var y = "2";
(x == y) // Returns true since the value of both x and y is the same
(x === y) // Returns false since the typeof x is "number" and typeof y is "string"
4.解释javascript中的隐式类型强制。
javascript 中的隐式类型强制是将值从一种数据类型自动转换为另一种数据类型。当表达式的操作数具有不同的数据类型时,就会发生这种情况。
字符串强制
使用“+”运算符时会发生字符串强制转换。将数字添加到字符串时,数字类型始终转换为字符串类型。
示例 1:
var x = 3;
var y = "3";
x + y // Returns "33"
示例 2:
var x = 24;
var y = "Hello";
x + y // Returns "24Hello";
**注意 - ' + ' 运算符用于将两个数字相加时,输出一个数字。相同的 ' + ' 运算符用于将两个字符串相加时,输出连接的字符串:
var name = "Vivek";
var surname = " Bisht";
name + surname // Returns "Vivek Bisht"
让我们来理解我们将数字添加到字符串的两个示例,
当 JavaScript 看到表达式 x + y 的操作数是不同类型(一个是数字类型,另一个是字符串类型)时,它会转换数字type 到字符串类型,然后执行操作。由于转换后两个变量都是字符串类型,'+'运算符在第一个示例中输出连接的字符串“33”,在第二个示例中输出“24Hello”。
**注意 - 使用“-”运算符时也会发生类型强制,但使用“-”运算符时的区别在于,将字符串转换为数字然后进行减法。
var x = 3;
Var y = "3";
x - y //Returns 0 since the variable y (string type) is converted to a number type
布尔强制
使用逻辑运算符、三元运算符、if 语句和循环检查时会发生布尔强制。要理解 if 语句和运算符中的布尔强制转换,我们需要理解真值和假值。
真值是那些将被转换(强制)为true 的值。假值是那些将被转换为false 的值。
除了0、0n 、-0、“”、null、undefined 和 NaN之外的所有值都是真值。
if语句:
例子:
var x = 0;
var y = 23;
if(x) { console.log(x) } // The code inside this block will not run since the value of x is 0(Falsy)
if(y) { console.log(y) } // The code inside this block will run since the value of y is 23 (Truthy)
逻辑运算符:
与其他编程语言中的运算符不同,javascript 中的逻辑运算符不返回真或假。他们总是返回操作数之一。
OR ( | | ) 运算符- 如果第一个值为真,则返回第一个值。否则,总是返回第二个值。
AND ( && ) 运算符- 如果两个值都为真,则始终返回第二个值。如果第一个值为假,则返回第一个值,如果第二个值为假,则返回第二个值。
例子:
var x = 220;
var y = "Hello";
var z = undefined;
x | | y // Returns 220 since the first value is truthy
x | | z // Returns 220 since the first value is truthy
x && y // Returns "Hello" since both the values are truthy
y && z // Returns undefined since the second value is falsy
if( x && y ){
console.log("Code runs" ); // This block runs because x && y returns "Hello" (Truthy)
}
if( x || z ){
console.log("Code runs"); // This block runs because x || y returns 220(Truthy)
}
相等强制
使用“==”运算符时会发生相等强制。正如我们之前所说的,' == ' 运算符比较的是值而不是类型。
虽然上述语句是解释 == 运算符的简单方法,但并不完全正确。现实情况是,在使用“==”运算符时,会发生强制转换。
'==' 运算符将两个操作数转换为相同的类型,然后比较它们。
例子:
var a = 12;
var b = "12";
a == b // Returns true because both 'a' and 'b' are converted to the same type and then compared. Hence the operands are equal.
使用“===”运算符时不会发生强制转换。在 '===' 运算符的情况下,两个操作数不会转换为相同的类型。
例子:
var a = 226;
var b = "226";
a === b // Returns false because coercion does not take place and the operands are of different types. Hence they are not equal.
5. Javascript面试题和答案合集:javascript 是静态类型语言还是动态类型语言?
JavaScript 是一种动态类型语言。在动态类型语言中,在运行时检查变量的类型, 而静态类型语言则在编译时检查变量的类型。
由于 javascript 是一种松散(动态)类型的语言,因此 JS 中的变量不与任何类型相关联。变量可以保存任何数据类型的值。
例如,分配了数字类型的变量可以转换为字符串类型:
var a = 23;
var a = "Hello World!";
6. JavaScript 中的 NaN 属性是什么?
NaN 属性表示“非数字”值。它表示一个不是合法数字的值。 NaN 的typeof将返回一个Number。
要检查值是否为 NaN,我们使用isNaN()函数,
**注意- isNaN() 函数将给定值转换为数字类型,然后等于 NaN。
isNaN("Hello") // Returns true
isNaN(345) // Returns false
isNaN('1') // Returns false, since '1' is converted to Number type which results in 0 ( a number)
isNaN(true) // Returns false, since true converted to Number type results in 1 ( a number)
isNaN(false) // Returns false
isNaN(undefined) // Returns true
7.解释值传递和引用传递。
Javascript面试题解析:在 JavaScript 中,原始数据类型通过值传递,非原始数据类型通过引用传递。
为了理解按值传递和按引用传递,我们需要了解当我们创建一个变量并为其赋值时会发生什么,
var x = 2;
在上面的示例中,我们创建了一个变量 x 并为其分配了一个值“2”。在后台,“=”(赋值运算符)在内存中分配一些空间,存储值“2”并返回分配的内存空间的位置。因此,上面代码中的变量x指向的是内存空间的位置,而不是直接指向值2。
处理原始数据类型和非原始数据类型时,赋值运算符的行为不同,
赋值运算符处理原始类型:
var y = 234;
var z = y;
在上面的例子中,赋值运算符知道分配给 y 的值是一个原始类型(在这种情况下是数字类型),所以当第二行代码执行时,将 y 的值赋值给 z,赋值运算符取值y (234) 并在内存中分配一个新空间并返回地址。因此,变量 z 不是指向变量 y 的位置,而是指向内存中的新位置。
var y = #8454; // y pointing to address of the value 234
var z = y;
var z = #5411; // z pointing to a completely new address of the value 234
// Changing the value of y
y = 23;
console.log(z); // Returns 234, since z points to a new address in the memory so changes in y will not effect z
从上面的例子中,我们可以看到原始数据类型在传递给另一个变量时,是按值传递的。不是将相同的地址分配给另一个变量,而是传递值并创建新的内存空间。
处理非原始类型的赋值运算符:
var obj = { name: "Vivek", surname: "Bisht" };
var obj2 = obj;
在上面的例子中,赋值运算符,直接将变量obj的位置传递给变量obj2。换句话说,将变量 obj 的引用传递给变量 obj2。
var obj = #8711; // obj pointing to address of { name: "Vivek", surname: "Bisht" }
var obj2 = obj;
var obj2 = #8711; // obj2 pointing to the same address
// changing the value of obj1
obj1.name = "Akki";
console.log(obj2);
// Returns {name:"Akki", surname:"Bisht"} since both the variables are pointing to the same address.
从上面的例子我们可以看出,在传递非原始数据类型的同时,赋值运算符直接传递了地址(引用)。
因此,非原始数据类型总是通过引用传递。
8. JavaScript 中的立即调用函数是什么?
立即调用函数(称为 IIFE,发音为 IIFY)是一个定义后立即运行的函数。
IIFE 的语法:
(function(){
// Do something;
})();
要理解 IIFE,我们需要了解在创建 IIFE 时添加的两组括号:
第一组括号:
(function (){
//Do something;
})
在执行 javascript 代码时,每当编译器看到“函数”这个词时,它就假定我们在代码中声明了一个函数。因此,如果我们不使用第一组括号,编译器会抛出错误,因为它认为我们在声明一个函数,而按照声明函数的语法,函数应该始终有一个名称。
function() {
//Do something;
}
// Compiler gives an error since the syntax of declaring a function is wrong in the code above
为了消除这个错误,我们添加了第一组括号,告诉编译器该函数不是函数声明,而是函数表达式。
第二组括号:
(function (){
//Do something;
})();
从 IIFE 的定义中,我们知道我们的代码应该在定义后立即运行。函数仅在被调用时运行。如果我们不调用该函数,则返回函数声明:
(function (){
// Do something;
})
// Returns the function declaration
因此要调用该函数,我们使用第二组括号。.
9. 用javascript解释高阶函数。
通过将其他函数作为参数或通过返回它们来对其他函数进行操作的函数称为高阶函数。
高阶函数是JavaScript中函数是一等公民的结果。
高阶函数的例子:
function higherOrder(fn) {
fn();
}
higherOrder(function() { console.log("Hello world") });
function higherOrder2() {
return function() {
return "Do something";
}
}
var x = higherOrder2();
x() // Returns "Do something"
10.解释“this”关键字。
“this”关键字指的是函数是其属性的对象。
“this”关键字的值将始终取决于调用该函数的对象。
使困惑?我们通过例子来理解上面的语句:
function doSomething() {
console.log(this);
}
doSomething();
你认为上面代码的输出会是什么?
**注意 - 观察我们调用函数的行。
再次检查定义:
“this”关键字指的是函数是其属性的对象。
在上面的代码中,函数是哪个对象的属性?
由于函数是在全局上下文中调用的,所以函数是全局对象的一个属性。
因此,上述代码的输出将是全局对象。由于我们在浏览器中运行了上面的代码,所以全局对象就是window 对象。
示例 2:
var obj = {
name: "vivek",
getName: function(){
console.log(this.name);
}
}
obj.getName();
在上面的代码中,在调用的时候,getName函数是对象obj的一个属性,因此this关键字将引用对象obj,因此输出为“vivek”。
示例 3:
var obj = {
name: "vivek",
getName: function(){
console.log(this.name);
}
}
var getName = obj.getName;
var obj2 = {name:"akshay", getName };
obj2.getName();
你能猜出这里的输出吗?
输出将是“akshay”。
虽然 getName 函数是在对象obj内部声明的,但在调用时, getName() 是obj2的属性,因此“this”关键字将引用obj2。
理解this关键字的愚蠢方法是,无论何时调用函数,都检查点之前的对象。值这个。关键字将始终是点之前的对象。
如果像 example1 那样点之前没有对象,则 this 关键字的值将是全局对象。
示例 4:
var obj1 = {
address : "Mumbai,India",
getAddress: function(){
console.log(this.address);
}
}
var getAddress = obj1.getAddress;
var obj2 = {name:"akshay"};
obj2.getAddress();
你能猜出输出吗?
输出将是一个错误。
虽然在上面的代码中, this 关键字指的是对象obj2, obj2 没有属性“address”',因此 getAddress 函数会抛出错误。
11. 解释 call()、apply() 和 bind() 方法。
call()
它是 javascript 中的一个预定义方法。
此方法通过指定所有者对象来调用方法(函数)。
示例 1:
function sayHello(){
return "Hello " + this.name;
}
var obj = {name: "Sandy"};
sayHello.call(obj);
// Returns "Hello Sandy"
call() 方法允许一个对象使用另一个对象的方法(函数)。
示例 2:
var person = {
age: 23,
getAge: function(){
return this.age;
}
}
var person2 = {age: 54};
person.getAge.call(person2);
// Returns 54
call() 接受参数:
function saySomething(message){
return this.name + " is " + message;
}
var person4 = {name: "John"};
saySomething.call(person4, "awesome");
// Returns "John is awesome"
apply()
apply 方法类似于 call() 方法。唯一的区别是,
call() 方法单独接受参数,而 apply() 方法将参数作为数组。
function saySomething(message){
return this.name + " is " + message;
}
var person4 = {name: "John"};
saySomething.apply(person4, ["awesome"]);
bind()
此方法返回一个新函数,其中“this”关键字的值将绑定到作为参数提供的所有者对象。
带参数的示例:
var bikeDetails = {
displayDetails: function(registrationNumber,brandName){
return this.name+ " , "+ "bike details: "+ registrationNumber + " , " + brandName;
}
}
var person1 = {name: "Vivek"};
var detailsOfPerson1 = bikeDetails.displayDetails.bind(person1, "TS0122", "Bullet");
// Binds the displayDetails function to the person1 object
detailsOfPerson1();
// Returns Vivek, bike details: TS0452, Thunderbird
12. Javascript常见面试题有哪些:JavaScript 中的柯里化是什么?
柯里化是一种将参数为 n 的函数转换为一个或更少参数的函数的高级技术。
柯里化函数示例:
function add (a) {
return function(b){
return a + b;
}
}
add(3)(4)
例如,如果我们有一个函数f(a,b),那么柯里化之后的函数将被转换为f(a)(b)。
通过使用柯里化技术,我们不会改变函数的功能,我们只是改变它的调用方式。
让我们看看柯里化的作用:
function multiply(a,b){
return a*b;
}
function currying(fn){
return function(a){
return function(b){
return fn(a,b);
}
}
}
var curriedMultiply = currying(multiply);
multiply(4, 3); // Returns 12
curriedMultiply(4)(3); // Also returns 12
正如你在上面的代码中看到的,我们已经将函数multiply(a,b) 转换为一个函数 curriedMultiply,它一次接受一个参数。
13. 用javascript解释作用域和作用域链。
JS 中的作用域,决定了代码中各个部分的变量和函数的可访问性。
一般而言,作用域会让我们知道在代码的给定部分,我们可以或不能访问哪些变量和函数。
JS 中的作用域分为三种:
- 全局范围
- 局部或函数作用域
- 块作用域
全局作用域
在全局命名空间中声明的变量或函数具有全局作用域,这意味着所有具有全局作用域的变量和函数都可以从代码内部的任何位置访问
var globalVariable = "Hello world";
function sendMessage(){
return globalVariable; // can access globalVariable since it's written in global space
}
function sendMessage2(){
return sendMessage(); // Can access sendMessage function since it's written in global space
}
sendMessage2(); // Returns “Hello world”
函数作用域
在函数内部声明的任何变量或函数都具有局部/函数作用域,这意味着在函数内部声明的所有变量和函数都可以从函数内部访问,而不能从函数外部访问。
function awesomeFunction(){
var a = 2;
var multiplyBy2 = function(){
console.log(a*2); // Can access variable "a" since a and multiplyBy2 both are written inside the same function
}
}
console.log(a); // Throws reference error since a is written in local scope and cannot be accessed outside
multiplyBy2(); // Throws reference error since multiplyBy2 is written in local scope
块作用域
块作用域与使用 let 和 const 声明的变量有关。用 var 声明的变量没有块作用域。
块作用域告诉我们,在块 { } 内声明的任何变量只能在该块内访问,不能在该块外访问。
{
let x = 45;
}
console.log(x); // Gives reference error since x cannot be accessed outside of the block
for(let i=0; i<2; i++){
// do something
}
console.log(i); // Gives reference error since i cannot be accessed outside of the for loop block
Scope Chain
JavaScript 引擎也使用 Scope 来查找变量。
让我们通过一个例子来理解:
var y = 24;
function favFunction(){
var x = 667;
var anotherFavFunction = function(){
console.log(x); // Does not find x inside anotherFavFunction, so looks for variable inside favFunction, outputs 667
}
var yetAnotherFavFunction = function(){
console.log(y); // Does not find y inside yetAnotherFavFunction, so looks for variable inside favFunction and does not find it, so looks for variable in global scope, finds it and outputs 24
}
anotherFavFunction();
yetAnotherFavFunction();
}
favFunction();
正如你在上面的代码中看到的,如果 javascript 引擎在本地范围内没有找到变量,它会尝试在外部范围内检查变量。如果外部作用域中不存在该变量,它会尝试在全局作用域中查找该变量。
如果在全局空间中也找不到该变量,则会引发引用错误。
14. 解释 JavaScript 中的闭包。
Javascript面试题解析:闭包是函数记住在其外部作用域中声明的变量和函数的能力。
var Person = function(pName){
var name = pName;
this.getName = function(){
return name;
}
}
var person = new Person("Neelesh");
console.log(person.getName());
让我们通过例子来理解闭包:
function randomFunc(){
var obj1 = {name:"Vivian", age:45};
return function(){
console.log(obj1.name + " is "+ "awesome"); // Has access to obj1 even when the randomFunc function is executed
}
}
var initialiseClosure = randomFunc(); // Returns a function
initialiseClosure();
让我们理解上面的代码,
当我们将函数分配给变量时, 函数 randomFunc() 被执行并返回一个函数:
var initialiseClosure = randomFunc();
然后当我们调用 initialiseClosure 时执行返回的函数:
initialiseClosure();
上面的代码行输出“Vivian is awesome”,这可能是因为闭包。
当函数 randomFunc() 运行时,它看到返回函数正在使用其中的变量 obj1:
console.log(obj1.name + " is "+ "awesome");
因此randomFunc(),不是在执行后销毁obj1的值,而是将值保存在内存中以供进一步参考。 这就是为什么返回函数即使在函数已经执行之后也能够使用在外部作用域中声明的变量的原因。
函数即使在执行后也能存储变量以供进一步引用的能力,称为闭包。
15. 什么是对象原型?
所有 javascript 对象都从原型继承属性。
例如,
Date 对象从 Date 原型
继承属性 Math 对象从 Math 原型
继承属性 Array 对象从 Array 原型继承属性。
链的顶部是Object.prototype。每个原型都从 Object.prototype 继承属性和方法。
原型是对象的蓝图。Prototype 允许我们在对象上使用属性和方法,即使当前对象上不存在这些属性和方法。
让我们看看原型帮助我们使用方法和属性:
var arr = [];
arr.push(2);
console.log(arr); // Outputs [2]
在上面的代码中,我们可以看到,我们没有在数组“arr”上定义任何称为 push 的属性或方法,但 javascript 引擎没有抛出错误。
原因是使用原型。正如我们之前讨论的,Array 对象从 Array 原型继承属性。
javascript 引擎发现当前数组对象上不存在 push 方法,因此在 Array 原型中查找方法 push 并找到该方法。
每当在当前对象上找不到属性或方法时,javascript 引擎将始终尝试在其原型中查找,如果它仍然不存在,则在原型的原型中查找等等。
16. 什么是回调?
回调是将在另一个函数执行后执行的函数。
在javascript中,函数被视为一等公民,它们可以用作另一个函数的参数,可以由另一个函数返回,也可以用作对象的属性。
用作另一个函数的参数的函数称为回调函数。
例子:
function divideByHalf(sum){
console.log(Math.floor(sum / 2));
}
function multiplyBy2(sum){
console.log(sum * 2);
}
function operationOnSum(num1,num2,operation){
var sum = num1 + num2;
operation(sum);
}
operationOnSum(3, 3, divideByHalf); // Outputs 3
operationOnSum(5, 5, multiplyBy2); // Outputs 20
在上面的代码中,我们对两个数字的和执行数学运算。
operationOnSum 函数接受 3 个参数,第一个数字、第二个数字以及要对其总和执行的操作(回调)。
在上面的代码中,divideByHalf 和 multiplyBy2 函数都用作回调函数。
这些回调函数只有在函数 operationOnSum 执行后才会执行。
因此,回调函数是在另一个函数执行后才会执行的函数。
17.Javascript面试题和答案合集:什么是Memoization?
Memoization 是一种缓存形式,其中函数的返回值基于其参数进行缓存。如果该函数的参数未更改,则返回该函数的缓存版本。
让我们通过将简单函数转换为记忆函数来理解记忆化:
**注意- Memoization 用于昂贵的函数调用,但在以下示例中,我们正在考虑使用一个简单的函数来更好地理解 memoization 的概念。
考虑以下函数:
function addTo256(num){
return num + 256;
}
addTo256(20); // Returns 276
addTo256(40); // Returns 296
addTo256(20); // Returns 276
在上面的代码中,我们编写了一个函数,将参数添加到 256 并返回它。
当我们使用相同的参数(在上述情况下为“20”)再次调用函数 addTo256 时,我们将再次计算相同参数的结果。
在上面的例子中,用相同的参数一次又一次地计算结果并不是什么大问题,但是想象一下,如果函数做了一些繁重的工作,那么,用相同的参数一次又一次地计算结果会导致浪费时间。
这就是记忆化的用武之地,通过使用记忆化,我们可以根据参数存储(缓存)计算结果。如果在调用函数时再次使用相同的参数,我们不计算结果,而是直接返回存储(缓存)的值。
让我们将上述函数 addTo256 转换为记忆函数:
function memoizedAddTo256(){
var cache = {};
return function(num){
if(num in cache){
console.log("cached value");
return cache[num]
}
else{
cache[num] = num + 256;
return cache[num];
}
}
}
var memoizedFunc = memoizedAddTo256();
memoizedFunc(20); // Normal return
memoizedFunc(20); // Cached return
在上面的代码中,如果我们使用相同的参数运行 memoizedFunc 函数,它不会再次计算结果,而是返回缓存的结果。
**注意- 尽管使用记忆化可以节省时间,但由于我们存储了所有计算结果,因此会导致更大的内存消耗。
18. 什么是编程语言中的递归?
递归是一种迭代操作的技术,它通过重复调用函数本身直到它到达结果。
function add(number) {
if (number <= 0) {
return 0;
} else {
return number + add(number - 1);
}
}
add(3) => 3 + add(2)
3 + 2 + add(1)
3 + 2 + 1 + add(0)
3 + 2 + 1 + 0 = 6
递归函数示例:
以下函数使用递归计算数组中所有元素的总和:
function computeSum(arr){
if(arr.length === 1){
return arr[0];
}
else{
return arr.pop() + computeSum(arr);
}
}
computeSum([7, 8, 9, 99]); // Returns 123
19. javascript中构造函数有什么用?
构造函数用于在 javascript 中创建对象。
我们什么时候使用构造函数?
如果我们想创建多个具有相似属性和方法的对象,则使用构造函数。
**注意- 构造函数的名称应始终以 Pascal Notation 书写:每个单词都应以大写字母开头。
例子:
function Person(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
}
var person1 = new Person("Vivek", 76, "male");
console.log(person1);
var person2 = new Person("Courtney", 34, "female");
console.log(person2);
在上面的代码中,我们创建了一个名为 Person 的构造函数。
每当我们想要创建一个 Person 类型的新对象时,
我们需要使用 new 关键字来创建它:
var person3 = new Person("Lilly", 17, "female");
上面的代码行将创建一个 Person 类型的新对象。
构造函数允许我们对相似的对象进行分组。
20.Javascript常见面试题有哪些:什么是DOM?
DOM 代表文档对象模型。
DOM 是 HTML 和 XML 文档的编程接口。
当浏览器尝试呈现 HTML 文档时,它会根据 HTML 文档创建一个名为 DOM 的对象。使用这个 DOM,我们可以操作或更改 HTML 文档中的各种元素。
HTML 代码如何转换为 DOM 的示例:
高级 Javascript 面试问题:
21. 什么是箭头函数?
箭头函数是在 JavaScript 的 ES6 版本中引入的。
它们为我们提供了一种新的、更短的语法来声明函数。
箭头函数只能用作函数表达式。
下面我们来详细对比一下普通函数声明和箭头函数声明:
// 传统函数表达式
var add = function (a,b){
return a + b;
}
// 箭头函数表达式
var arrowAdd = (a,b) => a + b;
箭头函数是在没有 function 关键字的情况下声明的。如果只有一个返回表达式,那么我们不需要在箭头函数中也使用 return 关键字,如上例所示。此外,对于只有一行代码的函数,可以省略大括号 { }。
// 传统函数表达式
var multiplyBy2 = function (num){
return num * 2 ;
}
// 箭头函数表达式
var arrowMultiplyBy2 = num => num * 2 ;
如果函数只接受一个参数,则参数周围的括号 () 可以省略,如上面的代码所示。
var obj1 = {
valueOfThis: function(){
return this;
}
}
var obj2 = {
valueOfThis: ()=>{
return this;
}
}
obj1.valueOfThis(); // Will return the object obj1
obj2.valueOfThis(); // Will return window/global object
传统函数表达式和箭头函数最大的不同,就是对this关键字的处理。
根据一般定义,this关键字总是指调用函数的对象。
正如你在上面的代码中看到的,obj1.valueOfThis()返回 obj1,因为this关键字指的是调用该函数的对象。
在箭头函数中,没有绑定this关键字。
在这箭头函数内的关键字,不引用调用它的对象。它而是从父作用域继承它的值,在这种情况下它是窗口对象。
因此,在上面的代码中,obj2.valueOfThis()返回窗口对象。
22. Javascript常见面试题有哪些:使用 var、let 和 const 声明变量的区别。
在 JavaScript 的 ES6 版本之前,仅使用关键字 var 来声明变量。
在 ES6 版本中,引入了关键字 let 和 const 来声明变量。
关键词 | const | let | var |
全局范围 | 不 | 不 | 是的 |
函数范围 | 是的 | 是的 | 是的 |
块作用域 | 是的 | 是的 | 不 |
可以重新分配 | 不 | 是的 | 是的 |
让我们通过示例了解差异:让我们通过示例
了解差异:
var variable1 = 23;
let variable2 = 89;
function catchValues(){
console.log(variable1);
console.log(variable2);
// Both the variables can be accessed anywhere since they are declared in the global scope
}
window.variable1; // Returns the value 23
window.variable2; // Returns undefined
在全局作用域中使用 let 关键字声明的变量的行为就像在全局作用域中使用 var 关键字声明的变量一样。
在全局范围内使用 var 和 let 关键字声明的变量可以从代码中的任何位置访问。
但是,有一个区别!
在全局范围内使用 var 关键字声明的变量被添加到 window/global 对象中。因此,可以使用 window.variableName 访问它们。
而使用 let 关键字声明的变量不会添加到全局对象中,因此,尝试使用 window.variableName 访问此类变量会导致错误。
var 与 let 在函数范围内
function varVsLetFunction(){
let awesomeCar1 = "Audi";
var awesomeCar2 = "Mercedes";
}
console.log(awesomeCar1); // Throws an error
console.log(awesomeCar2); // Throws an error
使用var 和let关键字在函数/局部作用域中声明的变量的行为完全相同,这意味着它们不能从作用域外部访问。
{
var variable3 = [1, 2, 3, 4];
}
console.log(variable3); // Outputs [1,2,3,4]
{
let variable4 = [6, 55, -1, 2];
}
console.log(variable4); // Throws error
for(let i = 0; i < 2; i++){
//Do something
}
console.log(i); // Throws error
for(var j = 0; j < 2; i++){
// Do something
}
console.log(j) // Outputs 2
在 javascript 中,块表示写在花括号{}内的代码。
使用var关键字声明的变量没有块作用域。这意味着在块作用域{} 中 使用var关键字声明的变量与在全局作用域中声明变量相同。 在块范围内
用let关键字声明的变量不能从块外部访问。
const 关键字
带有const关键字的变量的行为与用 let 关键字声明的变量完全一样,只有一个区别,任何用 const 关键字声明的变量都不能重新赋值。
例子:
const x = {name:"Vivek"};
x = {address: "India"}; // Throws an error
x.name = "Nikhil"; // No error is thrown
const y = 23;
y = 44; // Throws an error
在上面的代码中,虽然我们可以改变用const关键字声明的变量内部的属性值,但我们不能完全重新分配变量本身。
23.什么是rest参数和spread运算符?
在 JavaScript 的 ES6 版本中引入了 rest 参数和扩展运算符。
rest参数 ( … )
它提供了一种改进的方法来处理函数的参数。
使用 rest 参数语法,我们可以创建可以接受可变数量参数的函数。
使用 rest 参数将任意数量的参数转换为数组。
它还有助于提取全部或部分参数。
可以通过在参数前应用三个点 (...) 来使用 Rest 参数。
function extractingArgs(...args){
return args[1];
}
// extractingArgs(8,9,1); // Returns 9
function addAllArgs(...args){
let sumOfArgs = 0;
let i = 0;
while(i < args.length){
sumOfArgs += args[i];
i++;
}
return sumOfArgs;
}
addAllArgs(6, 5, 7, 99); // Returns 117
addAllArgs(1, 3, 4); // Returns 8
**注意- Rest 参数应始终用于函数的最后一个参数:
// Incorrect way to use rest parameter
function randomFunc(a,...args,c){
//Do something
}
// Correct way to use rest parameter
function randomFunc2(a,b,...args){
//Do something
}
展开运算符 (...)
虽然展开运算符的语法与 rest 参数完全相同,但展开运算符用于展开数组和对象字面量。我们还在函数调用中需要一个或多个参数的地方使用扩展运算符。
function addFourNumbers(num1,num2,num3,num4){
return num1 + num2 + num3 + num4;
}
let fourNumbers = [5, 6, 7, 8];
addFourNumbers(...fourNumbers);
// Spreads [5,6,7,8] as 5,6,7,8
let array1 = [3, 4, 5, 6];
let clonedArray1 = [...array1];
// Spreads the array into 3,4,5,6
console.log(clonedArray1); // Outputs [3,4,5,6]
let obj1 = {x:'Hello', y:'Bye'};
let clonedObj1 = {...obj1}; // Spreads and clones obj1
console.log(obj1);
let obj2 = {z:'Yes', a:'No'};
let mergedObj = {...obj1, ...obj2}; // Spreads both the objects and merges it
console.log(mergedObj);
// Outputs {x:'Hello', y:'Bye',z:'Yes',a:'No'};
***注意- 其余参数和扩展运算符之间的主要区别:
- Rest参数用于接受可变数量的参数并转换为数组,而扩展运算符则采用数组或对象并将其扩展
- Rest 参数用于函数声明,而扩展运算符用于函数调用。
24. javascript中promise有什么用?
Javascript面试题解析:Promise 用于处理 JavaScript 中的异步操作。
在承诺之前,回调用于处理异步操作。但是由于回调的功能有限,使用多个回调来处理异步代码会导致代码难以管理。
Promise 对象有四种状态 -
- Pending - 承诺的初始状态。这个状态代表promise既没有被履行也没有被拒绝,处于pending状态。
- Fulfilled - 此状态表示承诺已完成,意味着异步操作已完成。
- Rejected - 此状态表示由于某种原因承诺已被拒绝,这意味着异步操作已失败。
- Settled - 此状态表示承诺已被拒绝或已完成。
Promise 是使用Promise构造函数创建的,该构造函数接受一个带有两个参数的回调函数,分别是resolve和reject。
resolve是一个将在异步操作成功完成后调用的函数。 当异步操作失败或发生某些错误时,
reject 是一个将被调用的函数。 Promise示例:
Promise 用于处理服务器请求等异步操作,为了便于理解,我们使用一个操作来计算三个元素的总和。
在下面的函数中,我们在函数内返回一个 promise:
function sumOfThreeElements(...elements){
return new Promise((resolve,reject)=>{
if(elements.length > 3 ){
reject("Only three elements or less are allowed");
}
else{
let sum = 0;
let i = 0;
while(i < elements.length){
sum += elements[i];
i++;
}
resolve("Sum has been calculated: "+sum);
}
})
}
在上面的代码中,我们正在计算三个元素的总和,如果元素数组的长度大于3,则拒绝promise,否则解决promise并返回总和。
我们可以通过将 then() 和 catch() 方法附加到消费者来消费任何承诺。
then()方法用于在承诺完成时访问结果。 当承诺被拒绝时,
catch()方法用于访问结果/错误。
在下面的代码中,我们正在消费承诺:
sumOfThreeElements(4, 5, 6)
.then(result=> console.log(result))
.catch(error=> console.log(error));
// In the code above, the promise is fulfilled so the then() method gets executed
sumOfThreeElements(7, 0, 33, 41)
.then(result => console.log(result))
.catch(error=> console.log(error));
// In the code above, the promise is rejected hence the catch() method gets executed
25.javascript中的类是什么?
在 ES6 版本中引入,类只不过是构造函数的语法糖。
它们提供了一种在 javascript 中声明构造函数的新方法。
以下是如何声明和使用类的示例:
// Before ES6 version, using constructor functions
function Student(name,rollNumber,grade,section){
this.name = name;
this.rollNumber = rollNumber;
this.grade = grade;
this.section = section;
}
// Way to add methods to a constructor function
Student.prototype.getDetails = function(){
return 'Name: ${this.name}, Roll no: ${this.rollNumber}, Grade: ${this.grade}, Section:${this.section}';
}
let student1 = new Student("Vivek", 354, "6th", "A");
student1.getDetails();
// Returns Name: Vivek, Roll no:354, Grade: 6th, Section:A
// ES6 version classes
class Student{
constructor(name,rollNumber,grade,section){
this.name = name;
this.rollNumber = rollNumber;
this.grade = grade;
this.section = section;
}
// Methods can be directly added inside the class
getDetails(){
return 'Name: ${this.name}, Roll no: ${this.rollNumber}, Grade:${this.grade}, Section:${this.section}';
}
}
let student2 = new Student("Garry", 673, "7th", "C");
student2.getDetails();
// Returns Name: Garry, Roll no:673, Grade: 7th, Section:C
关于类要记住的要点:
- 与函数不同,类不会被提升。类在声明之前不能使用。
- 一个类可以通过使用 extend 关键字从其他类继承属性和方法。
- 类中的所有语法都必须遵循 javascript 的严格模式('use strict')。如果不遵循严格模式规则,则会抛出错误。
26. 什么是生成器函数?
在 ES6 版本中引入,生成器函数是一类特殊的函数。
它们可以在中途停止,然后从停止的地方继续。
生成器函数使用function*关键字而不是普通的function关键字声明:
function* genFunc(){
// Perform operation
}
在普通函数中,我们使用return关键字返回一个值,一旦 return 语句被执行,函数执行就会停止:
function normalFunc(){
return 22;
console.log(2); // This line of code does not get executed
}
在生成器函数的情况下,当被调用时,它们不执行代码,而是返回一个生成器对象。这个生成器对象处理执行。
function* genFunc(){
yield 3;
yield 4;
}
genFunc(); // Returns Object [Generator] {}
生成器对象包含一个名为next() 的方法,调用此方法时,会执行代码直到最近的yield语句,并返回 yield 值。
例如,如果我们在上面的代码上运行 next() 方法:
genFunc().next(); // Returns {value: 3, done:false}
如你所见,next 方法返回一个由value和done属性组成的对象。
Value 属性表示产生的值。
Done 属性告诉我们函数代码是否完成。(如果完成则返回 true)
生成器函数用于返回迭代器。让我们看一个返回迭代器的例子:
function* iteratorFunc() {
let count = 0;
for (let i = 0; i < 2; i++) {
count++;
yield i;
}
return count;
}
let iterator = iteratorFunc();
console.log(iterator.next()); // {value:0,done:false}
console.log(iterator.next()); // {value:1,done:false}
console.log(iterator.next()); // {value:2,done:true}
正如你在上面的代码中看到的,最后一行返回done:true,因为代码到达了 return 语句。
27. 用javascript解释WeakSet。
在 JavaScript 中,Set 是唯一且有序的元素的集合。
就像 Set 一样,WeakSet 也是唯一和有序元素的集合,但有一些关键区别:
- Weakset 只包含对象,不包含其他类型。
- 弱集内的对象被弱引用。这意味着,如果弱集内的对象没有引用,它将被垃圾收集。
- 与 Set 不同,WeakSet 只有三个方法,add()、delete()和has()。
const newSet = new Set([4, 5, 6, 7]);
console.log(newSet);// Outputs Set {4,5,6,7}
const newSet2 = new WeakSet([3, 4, 5]); //Throws an error
let obj1 = {message:"Hello world"};
const newSet3 = new WeakSet([obj1]);
console.log(newSet3.has(obj1)); // true
28. 用javascript解释WeakMap。
Javascript面试题解析:在 JavaScript 中,Map 用于存储键值对。键值对可以是原始类型和非原始类型。
WeakMap 与 Map 类似,但主要区别在于:
- weakmap 中的键和值应该始终是一个对象。
- 如果没有对该对象的引用,则该对象将被垃圾回收。
const map1 = new Map();
map1.set('Value', 1);
const map2 = new WeakMap();
map2.set('Value', 2.3); // Throws an error
let obj = {name:"Vivek"};
const map3 = new WeakMap();
map3.set(obj, {age:23});
29. 什么是对象解构?
对象解构是一种从对象或数组中提取元素的新方法。
对象解构:
ES6 版本之前:
const classDetails = {
strength: 78,
benches: 39,
blackBoard:1
}
const classStrength = classDetails.strength;
const classBenches = classDetails.benches;
const classBlackBoard = classDetails.blackBoard;
使用对象解构的相同示例:
const classDetails = {
strength: 78,
benches: 39,
blackBoard:1
}
const {strength:classStrength, benches:classBenches,blackBoard:classBlackBoard} = classDetails;
console.log(classStrength); // Outputs 78
console.log(classBenches); // Outputs 39
console.log(classBlackBoard); // Outputs 1
可以看出,使用对象解构,我们在一行代码中提取了对象内的所有元素。
如果我们希望我们的新变量与对象的属性具有相同的名称,我们可以删除冒号:
const {strength:strength} = classDetails;
// The above line of code can be written as:
const {strength} = classDetails;
数组解构:
ES6版本之前:
const arr = [1, 2, 3, 4];
const first = arr[0];
const second = arr[1];
const third = arr[2];
const fourth = arr[3];
使用对象解构的相同示例:
const arr = [1, 2, 3, 4];
const [first,second,third,fourth] = arr;
console.log(first); // Outputs 1
console.log(second); // Outputs 2
console.log(third); // Outputs 3
console.log(fourth); // Outputs 4
30. 什么是时间死区?
Javascript面试题和答案合集:时间死区是使用let和const关键字声明的变量时发生的行为。
这是一种在变量初始化之前尝试访问变量的行为。
时间死区的例子:
x = 23; // Gives reference error
let x;
function anotherRandomFunc(){
message = "Hello"; // Throws a reference error
let message;
}
anotherRandomFunc();
在上面的代码中,无论是在全局作用域还是在函数作用域,我们都试图访问尚未声明的变量。这称为时间死区。
编码问题:
31. 猜以下代码的输出:
// Code 1:
function func1(){
setTimeout(()=>{
console.log(x);
console.log(y);
},3000);
var x = 2;
let y = 12;
}
func1();
// Code 2:
function func2(){
for(var i = 0; i < 3; i++){
setTimeout(()=> console.log(i),2000);
}
}
func2();
// Code 3:
(function(){
setTimeout(()=> console.log(1),2000);
console.log(2);
setTimeout(()=> console.log(3),0);
console.log(4);
})();
答案:
代码 1 - 输出2和12。因为,即使let变量没有被提升,由于 javascript 的异步特性,完整的函数代码在 setTimeout 函数之前运行。因此,它可以访问 x 和 y。
代码 2 - 输出3次,因为用var关键字声明的变量没有块作用域。此外,在 for 循环中,变量 i 首先递增,然后检查。
代码 3 - 按以下顺序输出:
2
4
3
1 // After two seconds
即使第二个超时函数的等待时间为零秒,javascript 引擎始终使用 Web API 评估 setTimeout 函数,因此,完整的函数在 setTimeout 函数可以执行之前执行。
32.猜以下代码的输出:
// Code 1:
let x= {}, y = {name:"Ronny"},z = {name:"John"};
x[y] = {name:"Vivek"};
x[z] = {name:"Akki"};
console.log(x[y]);
// Code 2:
function runFunc(){
console.log("1" + 1);
console.log("A" - 1);
console.log(2 + "-2" + "2");
console.log("Hello" - "World" + 78);
console.log("Hello"+ "78");
}
runFunc();
// Code 3:
let a = 0;
let b = false;
console.log((a == b));
console.log((a === b));
答案:
代码 1 - 输出将是{name: “Akki”}。
添加对象作为另一个对象的属性应该小心。
写x[y] = {name:”Vivek”}和写x['object Object'] = {name:”Vivek”} 一样,
在设置对象的属性时,javascript 将参数强制转换为字符串。
因此,由于y是一个对象,它将被转换为'object Object'。
x[y] 和 x[z] 都引用相同的属性。
代码 2 - 按以下顺序输出:
11
Nan
2-22
NaN
Hello78
代码 3 - 由于相等强制按以下顺序输出:
true
false
33.猜猜下面代码的输出:
var x = 23;
(function(){
var x = 43;
(function random(){
x++;
console.log(x);
var x = 21;
})();
})();
答案:
输出是NaN。
random() 函数具有函数作用域,因为 x 在函数作用域中被声明和提升。
重写随机函数可以更好地了解输出:
function random(){
var x; // x is hoisted
x++; // x is not a number since it is not initialized yet
console.log(x); // Outputs NaN
x = 21; // Initialization of x
}
34. 猜猜以下代码的输出:
// Code 1
let hero = {
powerLevel: 99,
getPower(){
return this.powerLevel;
}
}
let getPower = hero.getPower;
let hero2 = {powerLevel:42};
console.log(getPower());
console.log(getPower.apply(hero2));
// Code 2
const a = function(){
console.log(this);
const b = {
func1: function(){
console.log(this);
}
}
const c = {
func2: ()=>{
console.log(this);
}
}
b.func1();
c.func2();
}
a();
// Code 3
const b = {
name:"Vivek",
f: function(){
var self = this;
console.log(this.name);
(function(){
console.log(this.name);
console.log(self.name);
})();
}
}
b.f();
答案:
代码 1 - 按以下顺序输出:
undefined
42
原因 - 第一个输出未定义,因为在调用函数时,它是引用全局对象来调用的:
window.getPower() = getPower();
代码 2 - 按以下顺序输出:
global/window object
object "b"
global/window object
由于我们在func2中使用箭头函数,因此 this关键字指的是全局对象。
代码 3 - 按以下顺序输出:
"Vivek"
undefined
"Vivek"
仅在函数f内部的 IIFE 中,this关键字指的是 global/window 对象。
35. 猜猜以下代码的输出:
**注意 - 代码 2 和代码 3 要求你修改代码,而不是猜测输出。
// Code 1
(function(a){
return (function(){
console.log(a);
a = 23;
})()
})(45);
// Code 2
// Each time bigFunc is called, an array of size 700 is being created,
// Modify the code so that we don't create the same array again and again
function bigFunc(element){
let newArray = new Array(700).fill('♥');
return newArray[element];
}
console.log(bigFunc(599)); // Array is created
console.log(bigFunc(670)); // Array is created again
// Code 3
// The following code outputs 2 and 2 after waiting for one second
// Modify the code to output 0 and 1 after one second.
function randomFunc(){
for(var i = 0; i < 2; i++){
setTimeout(()=> console.log(i),1000);
}
}
randomFunc();
答案 -
代码 1 - 输出 45。
即使 a 在外部函数中定义,由于闭包,内部函数可以访问它。
代码 2 - 可以使用闭包修改此代码,
function bigFunc(){
let newArray = new Array(700).fill('♥');
return (element) => newArray[element];
}
let getElement = bigFunc(); // Array is created only once
getElement(599);
getElement(670);
代码 3 - 可以通过两种方式修改:
使用let关键字:
function randomFunc(){
for(let i = 0; i < 2; i++){
setTimeout(()=> console.log(i),1000);
}
}
randomFunc();
使用闭包:
function randomFunc(){
for(var i = 0; i < 2; i++){
(function(i){
setTimeout(()=>console.log(i),1000);
})(i);
}
}
randomFunc();
36. 编写一个对排序数组执行二分查找的函数。
回答:
function binarySearch(arr,value,startPos,endPos){
if(startPos > endPos) return -1;
let middleIndex = Math.floor(startPos+endPos)/2;
if(arr[middleIndex] === value) return middleIndex;
elsif(arr[middleIndex > value]){
return binarySearch(arr,value,startPos,middleIndex-1);
}
else{
return binarySearch(arr,value,middleIndex+1,endPos);
}
}
37.实现返回使用更新的数组的函数[R整数数组上向右旋转一个。
示例:
给定以下数组:
[2,3,4,5,7]
执行3 次右旋转:
第一次旋转:[7,2,3,4,5],第二次旋转:[5,7,2,3, 4] and, 第三轮:[4,5,7,2,3]
返回[4,5,7,2,3]
答案:
function rotateRight(arr,rotations){
if(rotations == 0) return arr;
for(let i = 0; i < rotations;i++){
let element = arr.pop();
arr.unshift(element);
}
return arr;
}
rotateRight([2, 3, 4, 5, 7], 3); // Return [4,5,7,2,3]
rotateRight([44, 1, 22, 111], 5); // Returns [111,44,1,22]