1.JS判断变量是不是数组,请列出多种方法
在 JavaScript 中,判断一个变量是否为数组有多种方法。以下是常用的几种方法:
- Array.isArray() 方法:
Array.isArray()
是 JavaScript 内置的方法,用于判断一个变量是否为数组。
const arr = [1, 2, 3]; console.log(Array.isArray(arr)); // 输出: true const notArr = 'Hello'; console.log(Array.isArray(notArr)); // 输出: false
- instanceof 运算符:使用
instanceof
运算符可以判断一个对象是否是某个构造函数的实例,从而判断它是否为数组。
const arr = [1, 2, 3]; console.log(arr instanceof Array); // 输出: true const notArr = 'Hello'; console.log(notArr instanceof Array); // 输出: false
- Array.prototype.isPrototypeOf() 方法:可以通过
Array.prototype.isPrototypeOf()
方法来检查原型链,判断一个对象是否为数组。
const arr = [1, 2, 3]; console.log(Array.prototype.isPrototypeOf(arr)); // 输出: true const notArr = 'Hello'; console.log(Array.prototype.isPrototypeOf(notArr)); // 输出: false
- Object.prototype.toString.call() 方法:通过
Object.prototype.toString.call()
方法,获取对象的内部属性[[Class]]
的值,再进行判断。
const arr = [1, 2, 3]; console.log(Object.prototype.toString.call(arr) === '[object Array]'); // 输出: true const notArr = 'Hello'; console.log(Object.prototype.toString.call(notArr) === '[object Array]'); // 输出: false
这些方法都可以用来判断一个变量是否为数组。在这些方法中,Array.isArray()
是最为常用的,因为它简单明了,而且在处理多个全局执行上下文或跨窗口的情况下表现更可靠。除此之外,根据具体的使用场景,也可以选择其他方法进行判断。
2.JS数组去重的方法,请列出多种方法
在 JavaScript 中,对数组进行去重的方法有多种。以下是一些常用的去重方法:
- 使用 Set:ES6 中的 Set 是一种数据结构,它可以帮助我们快速去除数组中的重复元素。
const arr = [1, 2, 2, 3, 3, 4, 5]; const uniqueArr = [...new Set(arr)]; console.log(uniqueArr); // 输出: [1, 2, 3, 4, 5]
- 使用 indexOf() 和 filter():通过遍历数组,使用 indexOf() 方法判断元素是否已经在结果数组中,然后使用 filter() 方法过滤重复元素。
const arr = [1, 2, 2, 3, 3, 4, 5]; const uniqueArr = arr.filter((item, index) => arr.indexOf(item) === index); console.log(uniqueArr); // 输出: [1, 2, 3, 4, 5]
- 使用 includes() 和 filter():与上面类似,使用 includes() 方法判断元素是否已经在结果数组中。
const arr = [1, 2, 2, 3, 3, 4, 5]; const uniqueArr = arr.filter((item, index) => arr.includes(item, index + 1) === false); console.log(uniqueArr); // 输出: [1, 2, 3, 4, 5]
- 使用 reduce():利用 reduce() 方法和一个空数组来去除重复元素。
const arr = [1, 2, 2, 3, 3, 4, 5]; const uniqueArr = arr.reduce((acc, cur) => acc.includes(cur) ? acc : [...acc, cur], []); console.log(uniqueArr); // 输出: [1, 2, 3, 4, 5]
- 使用 Map:通过 Map 数据结构来去重。
const arr = [1, 2, 2, 3, 3, 4, 5]; const uniqueArr = Array.from(new Map(arr.map(item => [item, item])).values()); console.log(uniqueArr); // 输出: [1, 2, 3, 4, 5]
- 使用 filter() 和 indexOf():类似于第二种方法,但使用 indexOf() 方法的返回值来判断元素是否是第一个出现的。
const arr = [1, 2, 2, 3, 3, 4, 5]; const uniqueArr = arr.filter((item, index) => arr.indexOf(item) === index); console.log(uniqueArr); // 输出: [1, 2, 3, 4, 5]
以上这些方法都可以用于数组去重,选择哪种方法取决于具体的需求和场景。通常来说,使用 Set 是最简洁和高效的方法,但如果需要兼容较旧的环境,也可以考虑其他方法。
3.JS找出多维数组最大值,请列出多种方法
在 JavaScript 中,找出多维数组中的最大值可以使用多种方法。以下是一些常用的方法:
- 使用 Math.max() 和 apply():将多维数组展开成一维数组,然后使用 Math.max() 和 apply() 来找出最大值。
const multiDimArray = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; const flattenedArray = multiDimArray.flat(); const maxVal = Math.max.apply(null, flattenedArray); console.log(maxVal); // 输出: 9
- 使用 reduce():使用 reduce() 方法遍历多维数组,找出最大值。
const multiDimArray = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; const maxVal = multiDimArray.reduce((max, arr) => Math.max(max, ...arr), -Infinity); console.log(maxVal); // 输出: 9
- 使用递归:编写一个递归函数来遍历多维数组,找出最大值。
function findMaxValue(arr) { let maxVal = -Infinity; for (let item of arr) { if (Array.isArray(item)) { maxVal = Math.max(maxVal, findMaxValue(item)); } else { maxVal = Math.max(maxVal, item); } } return maxVal; } const multiDimArray = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; const maxVal = findMaxValue(multiDimArray); console.log(maxVal); // 输出: 9
- 使用递归和扩展运算符:类似于第三种方法,但使用扩展运算符来展开数组。
function findMaxValue(arr) { let maxVal = -Infinity; for (let item of arr) { if (Array.isArray(item)) { maxVal = Math.max(maxVal, ...findMaxValue(item)); } else { maxVal = Math.max(maxVal, item); } } return [maxVal]; } const multiDimArray = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; const maxVal = findMaxValue(multiDimArray)[0]; console.log(maxVal); // 输出: 9
这些方法都可以用于找出多维数组中的最大值,选择哪种方法取决于个人偏好和代码的可读性。需要注意的是,当处理大型多维数组时,递归方法可能会导致栈溢出,因此最好使用迭代方法或展开数组来处理。
4.JS找出字符串出现最多次数的字符以及次数,请列出多种方法
在 JavaScript 中,找出字符串中出现最多次数的字符以及出现的次数可以使用多种方法。以下是一些常用的方法:
- 使用对象计数法:遍历字符串,使用对象来计数每个字符出现的次数,然后找到出现次数最多的字符。
function findMostFrequentChar(str) { const charCount = {}; for (let char of str) { charCount[char] = (charCount[char] || 0) + 1; } let maxChar = ''; let maxCount = 0; for (let char in charCount) { if (charCount[char] > maxCount) { maxChar = char; maxCount = charCount[char]; } } return { char: maxChar, count: maxCount }; } const inputString = 'abracadabra'; const result = findMostFrequentChar(inputString); console.log(result); // 输出: { char: 'a', count: 5 }
- 使用 Map 计数法:类似于对象计数法,但使用 Map 数据结构来计数字符出现的次数。
function findMostFrequentChar(str) { const charCount = new Map(); for (let char of str) { charCount.set(char, (charCount.get(char) || 0) + 1); } let maxChar = ''; let maxCount = 0; for (let [char, count] of charCount) { if (count > maxCount) { maxChar = char; maxCount = count; } } return { char: maxChar, count: maxCount }; } const inputString = 'abracadabra'; const result = findMostFrequentChar(inputString); console.log(result); // 输出: { char: 'a', count: 5 }
- 使用数组排序法:将字符串转换为数组,然后使用数组排序方法找到出现次数最多的字符。
function findMostFrequentChar(str) { const charArr = str.split('').sort(); let maxChar = charArr[0]; let maxCount = 1; let currentChar = charArr[0]; let currentCount = 1; for (let i = 1; i < charArr.length; i++) { if (charArr[i] === currentChar) { currentCount++; } else { currentChar = charArr[i]; currentCount = 1; } if (currentCount > maxCount) { maxChar = currentChar; maxCount = currentCount; } } return { char: maxChar, count: maxCount }; } const inputString = 'abracadabra'; const result = findMostFrequentChar(inputString); console.log(result); // 输出: { char: 'a', count: 5 }
这些方法都可以用于找出字符串中出现最多次数的字符以及出现的次数,具体选择哪种方法取决于个人喜好和代码的可读性。上述方法的时间复杂度较低,并且能够高效地处理较大的字符串。
5.JS给字符串新增方法实现功能,请列出多种方法
在 JavaScript 中,给字符串新增内容有多种方法。然而,需要注意的是,由于字符串是不可变的数据类型,这意味着一旦字符串被创建,就无法直接修改其内容。所以,我们实际上不能在原始字符串上直接新增内容,而是需要创建一个新的字符串。以下是一些常用的方法:
- 使用字符串拼接:通过字符串拼接运算符(
+
)或字符串模板(ES6中的模板字面量)来新增内容。
let str = 'Hello'; str = str + ' World'; // 使用 + 运算符 console.log(str); // 输出: 'Hello World' let name = 'John'; const greeting = `Welcome, ${name}!`; // 使用字符串模板 console.log(greeting); // 输出: 'Welcome, John!'
- 使用 concat() 方法:使用
concat()
方法将一个或多个字符串连接到原始字符串后面,并返回新的字符串。
let str = 'Hello'; str = str.concat(' World', '!'); console.log(str); // 输出: 'Hello World!'
- 使用字符串插入方法:如
slice()
、substr()
或substring()
可以用来在特定位置插入内容。
let str = 'Hello'; const inserted = str.slice(0, 2) + 'XX' + str.slice(2); console.log(inserted); // 输出: 'HeXXllo'
- 使用 Array.join() 方法:将字符串转换成数组,然后使用
Array.join()
方法在数组元素之间插入内容。
let str = 'Hello'; const arr = str.split(''); arr.splice(2, 0, 'XX'); str = arr.join(''); console.log(str); // 输出: 'HeXXllo'
需要注意的是,虽然我们不能直接在原始字符串上修改内容,但这些方法能够帮助我们创建一个新的字符串并实现新增内容的效果。在选择方法时,要根据具体的需求和场景,选择最合适的方式。
6.JS中new操作符具体做了什么
在 JavaScript 中,new
操作符用于创建一个对象实例,并调用构造函数来初始化该对象。当使用 new
操作符创建一个对象时,具体的步骤如下:
- 创建一个空对象:
new
操作符首先创建一个空的 JavaScript 对象。 - 将对象的原型指向构造函数的原型:新创建的空对象的原型会被设置为构造函数的
prototype
属性,这样新对象就能够访问构造函数原型上的方法和属性。 - 执行构造函数:构造函数会被调用,同时将新创建的对象绑定到
this
上。在构造函数内部,我们可以通过this
来引用新对象,并对其进行属性赋值和其他初始化操作。 - 返回对象实例:如果构造函数中没有显式返回其他对象,那么
new
操作符将返回新创建的对象实例。如果构造函数中有返回值,并且返回值是一个对象,那么new
操作符将返回该对象,而不是新创建的对象实例。
下面是一个简单的构造函数和使用 new
操作符创建对象的示例:
// 构造函数 function Person(name, age) { this.name = name; this.age = age; this.sayHello = function() { console.log(`Hello, my name is ${this.name}, and I am ${this.age} years old.`); }; } // 使用 new 操作符创建对象 const john = new Person('John', 30); john.sayHello(); // 输出: 'Hello, my name is John, and I am 30 years old.'
需要注意的是,new
操作符的使用应该谨慎,特别是在构造函数内部没有正确使用 this
或忘记使用 new
操作符时,可能会导致出错或不符合预期的结果。同时,new
操作符也会带来一些性能上的开销,因此在现代 JavaScript 中,更多地使用类(ES6 中的类)来创建对象,它提供了更清晰、易读和易维护的语法。
7.JS继承有哪些方式
在 JavaScript 中,实现继承的方式有多种。以下是常见的几种继承方式:
- 原型链继承:利用原型链实现继承。将父类的实例作为子类的原型,使子类能够访问父类的属性和方法。
function Parent() { this.name = 'Parent'; } Parent.prototype.sayHello = function() { console.log('Hello, I am ' + this.name); }; function Child() {} Child.prototype = new Parent(); const child = new Child(); child.sayHello(); // 输出: 'Hello, I am Parent'
原型链继承的缺点是,所有子类实例共享父类实例的属性,子类不能向父类传递参数。
- 构造函数继承:通过在子类构造函数中调用父类构造函数,实现继承。
function Parent(name) { this.name = name || 'Parent'; } function Child(name) { Parent.call(this, name); } const child = new Child('John'); console.log(child.name); // 输出: 'John'
构造函数继承的优点是,子类实例拥有独立的属性,不会共享父类实例的属性。但缺点是,子类无法访问父类原型上的方法。
- 组合继承:结合原型链继承和构造函数继承,使子类能够同时拥有父类的属性和方法。
function Parent(name) { this.name = name || 'Parent'; } Parent.prototype.sayHello = function() { console.log('Hello, I am ' + this.name); }; function Child(name) { Parent.call(this, name); } Child.prototype = new Parent(); const child = new Child('John'); child.sayHello(); // 输出: 'Hello, I am John'
组合继承的优点是,子类实例既拥有独立的属性,又能够访问父类原型上的方法。但缺点是,父类的构造函数会被调用两次,一次在创建子类实例时,一次在将父类实例作为子类原型时。
- ES6 中的类继承:ES6 引入了
class
关键字,使继承更加直观和易用。
class Parent { constructor(name) { this.name = name || 'Parent'; } sayHello() { console.log('Hello, I am ' + this.name); } } class Child extends Parent { constructor(name) { super(name); } } const child = new Child('John'); child.sayHello(); // 输出: 'Hello, I am John'
ES6 类继承更加简洁和易读,而且不会有原型链继承和构造函数继承的一些问题。
这些是 JavaScript 中常见的继承方式,每种方式都有其适用的场景和优缺点。在选择继承方式时,应该根据实际需求和代码结构来决定使用哪种方式。
8.JS深拷贝和浅拷贝的理解
深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是在 JavaScript 中用于复制对象的两种不同方式。
浅拷贝:
浅拷贝是指只复制对象的第一层属性,如果对象的属性是引用类型(如数组、对象等),则复制的是引用而不是真正的值。这意味着原对象和拷贝后的对象会共享同一个引用类型的属性,修改其中一个对象的属性会影响到另一个对象。在 JavaScript 中,可以使用 Object.assign()
方法或扩展运算符(...
)来进行浅拷贝。
// 使用 Object.assign() 进行浅拷贝 const obj1 = { name: 'John', age: 30 }; const shallowCopy = Object.assign({}, obj1); // 使用扩展运算符进行浅拷贝 const obj2 = { name: 'Jane', age: 25 }; const shallowCopy2 = { ...obj2 };
深拷贝:
深拷贝是指将对象及其所有嵌套的属性都复制到一个新的对象中,使得原对象和拷贝后的对象完全独立,互不影响。深拷贝会递归复制对象的所有属性,包括引用类型的属性。在 JavaScript 中,常用的深拷贝方法包括使用递归实现、JSON.parse()
和 JSON.stringify()
结合等方法。
// 使用递归实现深拷贝 function deepCopy(obj) { if (obj === null || typeof obj !== 'object') { return obj; } let copy; if (Array.isArray(obj)) { copy = []; obj.forEach((item, index) => { copy[index] = deepCopy(item); }); } else { copy = {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { copy[key] = deepCopy(obj[key]); } } } return copy; } const obj = { name: 'Alice', age: 28, hobbies: ['reading', 'coding'] }; const deepCopiedObj = deepCopy(obj);
深拷贝会创建一个完全独立的新对象,这意味着修改拷贝后的对象不会影响到原对象。
需要注意的是,深拷贝可能存在性能上的开销,特别是对于嵌套较深或包含大量数据的对象。因此,在选择拷贝方式时,需要根据实际情况来考虑使用浅拷贝还是深拷贝。
9.localStorage、sessionStorage、cookie的区别
localStorage
、sessionStorage
和 cookie
是在浏览器中用于存储数据的三种不同的机制。它们都允许将数据保存在客户端,以便在同一个域名下的页面间进行数据共享。然而,它们在存储方式、作用域和数据有效期等方面存在一些不同点。
- localStorage:
- 存储方式:
localStorage
使用键值对(key-value)的形式来存储数据。 - 作用域:
localStorage
存储的数据在同一个域名下的所有页面间是共享的,即使页面关闭后数据依然存在。 - 数据有效期:
localStorage
的数据没有过期时间,除非主动删除或清除浏览器缓存,否则数据会一直存在。 - 存储大小:通常情况下,每个域名的
localStorage
存储上限为 5MB。
- sessionStorage:
- 存储方式:
sessionStorage
也是使用键值对的形式来存储数据。 - 作用域:
sessionStorage
存储的数据在同一个浏览器窗口或标签页中是共享的,不同窗口或标签页之间的数据是隔离的。当页面被关闭后,sessionStorage
中的数据也会被清除。 - 数据有效期:
sessionStorage
中的数据在页面会话结束时自动删除,或者当页面被关闭后也会清除。 - 存储大小:通常情况下,每个域名的
sessionStorage
存储上限为 5MB。
- cookie:
- 存储方式:
cookie
也是使用键值对的形式来存储数据。 - 作用域:
cookie
存储的数据在同一个域名下的所有页面间是共享的,包括不同的窗口或标签页。 - 数据有效期:
cookie
可以设置一个过期时间,如果未设置过期时间,那么该cookie
在用户关闭浏览器后会被清除,否则会一直存在直到过期时间到达。 - 存储大小:通常情况下,每个域名的
cookie
存储上限为 4KB。
总结:
localStorage
和sessionStorage
都是 HTML5 新增的 Web Storage API,提供了更高效的本地存储方式,而cookie
是旧的存储机制,主要用于在客户端和服务器之间进行通信。localStorage
和sessionStorage
的作用域较为相似,但localStorage
的数据在页面关闭后依然存在,而sessionStorage
的数据会在页面关闭时清除。localStorage
和sessionStorage
存储数据的大小上限都比cookie
大得多。localStorage
和sessionStorage
的使用更加简单方便,不需要手动处理过期时间,而cookie
需要设置过期时间或在后端处理。
10.var、let、const区别
在 JavaScript 中,var
、let
和 const
是用于声明变量的三种关键字。它们在作用域、变量提升和可修改性等方面有一些不同点。
- 作用域:
var
:使用var
声明的变量存在函数作用域,即在函数内部声明的变量只在该函数内部有效。如果在函数外部使用var
声明的变量,它将成为全局变量。let
和const
:使用let
和const
声明的变量存在块级作用域,即在最近的一对花括号({}
)内声明的变量只在该块内部有效。
- 变量提升:
var
:使用var
声明的变量会发生变量提升,即在变量声明之前就可以访问变量,但值为undefined
。这是因为 JavaScript 在执行代码前会将var
声明的变量提升到函数或全局作用域的顶部。let
和const
:使用let
和const
声明的变量也会发生变量提升,但不同于var
,它们在变量声明之前不可访问,称为 “暂时性死区”。
console.log(a); // 输出: undefined var a = 10; console.log(b); // 报错: Uncaught ReferenceError: Cannot access 'b' before initialization let b = 20; console.log(c); // 报错: Uncaught ReferenceError: Cannot access 'c' before initialization const c = 30;
- 可修改性:
var
和let
:使用var
和let
声明的变量的值可以修改。const
:使用const
声明的变量是常量,它们的值不能被重新赋值,一旦赋值后就不能改变。但对于对象和数组来说,虽然不能对变量重新赋值,但仍然可以修改对象和数组的属性或元素。
var x = 10; x = 20; // 可以修改 let y = 30; y = 40; // 可以修改 const z = 50; z = 60; // 报错: Uncaught TypeError: Assignment to constant variable. const obj = { name: 'John' }; obj.age = 30; // 可以修改对象的属性 const arr = [1, 2, 3]; arr.push(4); // 可以修改数组的元素
总结:
var
存在函数作用域,变量提升,并且可以修改值。let
存在块级作用域,变量提升(但有暂时性死区),并且可以修改值。const
存在块级作用域,变量提升(有暂时性死区),且不能重新赋值,但对于对象和数组来说,可以修改其属性或元素。