一、JS面试题

Q1. JS的数据类型都有哪些?
A1: javascript有11种数据类型

  • 值类型(基本类型):字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol、BigInt。
  • 引用数据类型:对象(Object)、数组(Array)、函数(Function)、正则(RegExp)、日期(Date)。

    注:Symbol 是 ES6 引入了一种新的原始数据类型,表示独一无二的值。

       注:BigInt是一种新的数据类型,用于当整数值大于Number数据类型支持的范围时。这种数据类型允许我们安全地对大整数执行算术操作,表示高分辨率的时间戳,使用大整数ID等等,而不需要使用库(数字后面+n结尾)

Q2. 怎么判断JS的数据类型

A2:

方法一:变量的数据类型可以使用 typeof 操作符来查看,typeof返回的都是string类型的参数:
typeof "John"                // 返回 string
typeof 3.14                  // 返回 number
typeof false                 // 返回 boolean

typeof 42n                   // 返回 bigint

typeof Symbol()              // 返回 symbol
typeof Symbol('foo')         // 返回 symbol

typeof undefined             // 返回 undefined
typeof ttttttt               // 返回 undefined

typeof func                  // 返回 'function'
typeof class cs {}           // 返回 'function'
typeof String                // 返回 'function'
typeof RegExp                // 返回 'function'
typeof new Function()        // 返回 'function'

typeof {}                    // 返回 'object' 对象
typeof []                    // 返回 'object' 数组
typeof null                  // 返回 'object' null
typeof /d/                   // 返回 'object' 正则
typeof 的局限性,在于无法精确判断出 null、数组、对象、正则 的类型。所以如果要精准判断,还需要使用其他技术手段,或组合判断。如下,判断数组类型:
Object.prototype.toString.call([]) // '[object Array]'

[] instanceof Array // true

[].constructor === Array // true
方法二:instanceof(instanceof 是用来判断 A 是否为 B 的实例)
console.log({} instanceof Object);                // true
console.log([] instanceof Array);                 // true
console.log([] instanceof Object);                 // true
console.log(new Date() instanceof Date);          // true
console.log(function () { } instanceof Function); // true
console.log('123' instanceof String);             // false
由上述代码看出instanceof对于引用类型的类型检测支持很好,但是无法对基本类型数据进行类型检测。
方法三:Object.prototype.toString.call()
//1、基本数据类型
var num1 = 1;
var num2 = new Number(1);
console.log(Object.prototype.toString.call(num1) == '[Object Number]'); // true
console.log(Object.prototype.toString.call(num2) == '[Object Number]'); // true

//2、数组
console.log(Object.prototype.toString.call([]) == '[Object Array]') // true

//3、函数
console.log(Object.prototype.toString.call(function(){})=='[Object Function]')// true

//4、自定义对象
function P() { }
console.log(Object.prototype.toString.call(new P()) == '[Object Object]') // true


Object.prototype.toString.call()对于基本类型和引用类型都可以判断(除了自定义的类)

目前最推荐方法三 👆

Q3: 堆和栈存储机制有什么区别?

A3:程序运行的时候,需要内存空间存放数据。一般来说,系统会划分出两种不同的内存空间:一种叫做栈(stack),另一种叫做堆(heap)。

堆是无序的,栈是有序的(先进后出)。

堆存放对象类型数据,栈存放基本类型数据,栈存放对象数据的地址,存在栈中的数据大小与生存期必须是确定的。可以明确知道每个区块的大小,因此,栈stack的寻址速度要快于堆heap

栈是会自动释放的,堆需要手动释放或者垃圾回收机制回收。如obj = null

Q4:深拷贝与浅拷贝

A4:

  • 浅拷贝是指引用类型的数据,在拷贝的数据变化时,也会引起原本数据改变。多个栈中地址对应同一个引用数据(基本数据类型默认是深拷贝的)。
  • 深拷贝是指,拷贝的数据变化,不会引起原本数据的改变。多个栈中地址对应多个引用数据

浅拷贝方法一:

Object.assign()
var obj = {
    a: {
        b: 1
    },
    c: 3
};
var newObj = Object.assign({}, obj); // Object对象浅拷贝

newObj.a.b = 2; // 修改的newObj.a是引用类型,会影响原对象obj.a
console.log(obj.a.b); // 2

newObj.c = 4; // 修改的newObj.c是基本类型,不会影响原对象obj.c
console.log(obj.c); // 3
浅拷贝方法二(只使用与数组Array):
slice()、concat()、Array.from()
var arr = [1, 2, [3, 4]];
var newArr = arr.slice(); // Array数组浅拷贝
// var newArr = arr.concat(); // 同理
// var newArr = Array.from(arr); // 同理

newArr[2][1] = 5; // 修改的newArr[2]是引用类型,会影响原数组arr[2]
console.log(arr[2][1]); // 5

newArr[0]=6;  // 修改的newArr[0]是基本类型,不会影响原数组arr[0]
console.log(arr[0]); // 1
浅拷贝方法三(适用于Object对象和Array数组):
扩展运算符 ...

var obj = {
    a: {
        b: 1
    }
};
var newObj = { ...obj }; // Object对象浅拷贝
newObj.a.b = 2;
console.log(obj.a.b); // 2var arr = [1, 2, [3, 4]];
var newArr = [...arr]; // Array数组浅拷贝
newArr[2][1] = 5;
console.log(arr[2][1]); // 5


深拷贝方法一:

JSON.parse(JSON.stringify(…)) (简单但有缺陷)

  • 原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
  • 缺点:不能深拷贝含有undefined、function、symbol值的对象。
  • 优点:用法简单粗暴,已经满足90%的使用场景了。
var obj = {
    a: {
        b: 1
    },
    c: 3
};
var newObj = JSON.parse(JSON.stringify(obj));
newObj.a.b = 2;
console.log(obj.a.b); // 1
newObj.c = 4;
console.log(obj.c); // 3

/** -------------------------- **/

var arr = [1, 2, [3, 4]];
var newArr = JSON.parse(JSON.stringify(arr));
newArr[2][1] = 5;
console.log(arr[2][1]); // 4
newArr[0] = 6;
console.log(arr[0]); // 1

深拷贝方法二:

手写递归方法 (复杂但很完善)
// 定义检测数据类型的功能函数
function checkedType(target) {
    return Object.prototype.toString.call(target).slice(8, -1);
}

// 实现深度克隆---对象/数组
function deepCopy(target) {
    var result; // 初始化变量result 成为最终克隆的数据
    var targetType = checkedType(target); // 判断拷贝的数据类型
    if (targetType === 'Object') {
        result = {};
    } else if (targetType === 'Array') {
        result = [];
    } else {
        return target;
    }
    // 遍历目标数据
    for (let key in target) {
        // 获取遍历数据结构的每一项值。
        let value = target[key];
        // 判断目标结构里的每一值是否存在对象/数组
        if (checkedType(value) === 'Object' || checkedType(value) === 'Array') {
            // 继续递归遍历获取到value值
            result[key] = deepCopy(value);
        } else {
            // 获取到value值是基本的数据类型或者是函数。
            result[key] = value;
        }
    }
    return result;
}

var obj1 = {
    'name': 'zhangsan',
    'age': 18,
    'language': [1, [2, 3],
        [4, 5]
    ]
};
var obj2 = deepCopy(obj1);
obj2.name = "lisi";
obj2.language[1] = ["二", "三"];

console.log('obj1', obj1); // {name: "zhangsan", age: 18, language: [1, [2, 3], [4, 5]]}
console.log('obj2', obj2); // {name: "lisi", age: 18, language: [1, ["二", "三"], [4, 5]]}