「前端面试题」——JavaScript

138 阅读7分钟

let、var、const的区别?

var

  • 没有块级作用域的概念。有全局作用域、函数作用域的概念。
  • 不初始化值默认为undefined。
  • 存在变量提升。(将变量的声明部分提升到当前作用域的最顶端)。
  • 全局作用域的var声明的变量会挂载到window对象下。
  • 同一个作用域下可以重复声明。
console.log(a); //打印:undefind 
var a = 10; 
//等价于 
var a; 
console.log(a); 
a = 10;

let

  • 有块级作用域的概念。
  • 不存在变量提升。
  • 暂时性死区。(使用let/const)声明变量之前,该变量都是不可用的。
  • 同一作用域中不能重复声明。

const

与let 特性一致,仅有2个差别:

  • 必须立即初始化,不能留到以后赋值。
  • 常量的值不能改变。

JS的基本数据类型有哪些?基本数据类型和引用数据类型的区别?

基本数据类型

  • string
  • number
  • boolean
  • symbol
  • undefind
  • null
  • 存储于内存中的栈区,先进后出,后进先出。

区别

  1. 访问方式。
  • 按值访问
let str = 'hello'; 
let str2 = str; 
str = null; 
console.log(str, str2); // null 'hello'
  • 引用访问
const obj = {}; 
const obj2 = obj; 
obj.name = 'zhangsan'; 
console.log(obj, obj2); 
//{name: 'zhangsan'} {name: 'zhangsan'}
  1. 比较方式。
  • 原始值:比较的是值。
  • 引用值:比较的是地址。
  1. 动态属性。
  • 原始值:无法添加动态属性。
  • 引用至:可以添加动态属性。
  1. 变量赋值。
  • 原始值:赋的是值。
  • 引用值:赋的是地址。

是否了解JS中的包装类型?

就是当基本类型以对象的方式去使用时,js会转换成对应的包装类型,number、string、boolean都有对应的包装类型。

说一说JS中的原型与原型链的理解?

  • 每个对象都有一个__proto__属性,该属性指向自己的原型对象。
  • 每个构造函数都有一个prototype属性,该属性指向实例对象的原型对象。
  • 原型对象的constructor指向构造函数本身。
  • 每个对象都有自己的原型对象,而原型对象本身也有自己的原型对象,从而形成了一条原型对象链。

86d7136af730941754f5d1dd82deada3.jpg

谈谈你对JS执行上下文栈的理解?

执行上下文就是评估和执行JS代码的环境。分为全局上下文,函数上下文,Eval函数上下文。

谈谈你对作用域和作用域链的理解?

作用域

什么是作用域

作用域就是一个独立的地盘,让变量不会泄露出去,也就是说作用域最大的作用就是隔离变量,不同作用域下同名的变量不会有冲突。

作用域类型

  1. 全局作用域。
  • 最外层函数和最外层变量。
  • 所有未定义直接赋值的变量自动声明为拥有全局作用域。
  • 所有window属性拥有全局作用域。
  1. 函数作用域。
  2. 块级作用域。
  • 在函数内部会被创建。
  • 在一个代码块内部。被{}号包裹。

作用域链

在当前作用域中没有找到,就会一层一层往上寻找(是在创建时的作用域中取值,而不是调用时的作用域中取值),直到全局作用域还没找到,就宣布放弃。这种一层层的关系,就叫做作用域链。

JS中的垃圾回收机制

  1. 标记清除。
  • 垃圾回收器,在运行的时候会给所有在内存中的变量打上标记。
  • 然后去掉环境中的变量以及被环境引用的变量。
  • 被标记上的会被视为准备删除的变量;
  • 等垃圾回收机制开始执行的时候就会销毁这些变量。
  1. 引用计数。
  • 声明了一个变量,并把一个引用类型的值赋给这个变量,这个引用类型的引用数就为1;
  • 同一个值又赋给另外一个变量,那么这个引用类型的值的引用数又会+1;
  • 当包含这个引用类型的变量又被赋给了其他值,那么这个引用次数就会-1;
  • 当引用次数为0的时候,就说明没法访问这个变量了;
  • 垃圾回收机制下一次运行的时候,就会清除引用次数为0的变量。
  • 但是循环引用会造成内存泄漏

闭包

什么是闭包

  • 闭包是一个封闭的空间,里面存储了其他地方会引用到的该作用域的值,在JS中通过作用域链来实现闭包。
  • 只要在函数中使用了外部的数据,就创建了闭包。这种情况下所创建的闭包,我们在编码时是不需要去关心的,会自动销毁。
  • 我们可以通过在函数内部返回一个函数,来手动创建闭包,从而让外部环境访问到函数内部的局部变量,让局部变量持续保存下来,不随着他的上下文环境一起销毁。

闭包可以解决什么问题?

解决全局污染的问题

怎么销毁闭包

如果是自动产生的闭包,我们无需操心闭包的销毁。如果是手动创建的闭包,可以把引用的变量设置为null,下次垃圾回收运行时,就会自动回收。

DOM事件的传播机制

事件冒泡

即事件开始由最具体的元素接收,然后逐级向上传播。

事件捕获

事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后收到事件。

标准DOM事件流

maopao.jpg

  • DOM标准规定事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。
  • 事件捕获阶段:实际目标DIV在捕获阶段不会触发事件,捕获是从window开始,到html,到body 意味着捕获阶段结束。
  • 处于目标阶段:事件发生在DIV上并处理,但是本次事件处理会被看成是冒泡阶段的一部分。
  • 冒泡阶段:事件又传播回文档。

JS中对象的属性描述符有哪些?分别有什么作用?

  • value:设置属性值,默认值为undefined;
  • writable:属性值是否可写,默认值为true;
  • enumerable:设置属性是否可枚举,即是否允许使用 for/in 或 Object.keys()函数遍历访问。默认为true。
  • configurable:是否可设置属性特征,默认为true,如果设置为false,不能修改属性值,不能修改属性描述符。
  • get:取值函数。
  • set:存值函数。
  • 通过Object.getOwnPropertyDescriptor(obj, 'age'); 获取对象属性的描述符。

防抖

在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。


<input type="text" id="text" />     
<script>         
const inputDom = document.getElementById('text'); 

inputDom.onkeyup = function (event) {             
    handleDebounce(event);         
}
   
const handleDebounce = debounce(function (event) {             
    console.log(event.target.value);         
}, 500);

function debounce(func, wait) {             
    let timer = null;             
    return function (...args) { 
        timer && clearTimeout(timer);                 
        timer = setTimeout(function () {                     
        func(...args);                 
        }, wait);             
    }         
}     
</script>

节流

规定在n秒内,只能触发一次函数。如果n秒内触发多次函数,只有一次生效。

<body>
    <input type="text" id="text" />
    <script>
        const inputDom = document.getElementById('text');
        inputDom.onkeyup = function (event) {
            handleThrottle(event);
        }
        
        const handleThrottle = throttle(function (event) {
            console.log(event.target.value);
        }, 2000);
        
        function throttle(func, wait) {
            let prev = 0;
            return function (...args) {
                const now = Date.now();
                if (now - prev > wait) {
                    func(...args);
                    prev = now;
                }
            }
        }
    </script>
</body>

深拷贝和浅拷贝的区别?如何实现

浅拷贝

只拷贝基本类型的数据,如果是引用类型,拷贝的是对象的引用地址,而不拷贝对象本身,新旧对象还是共享同一块内存。

浅拷贝实现

  1. 直接赋值
  2. Object.assign()
  3. ES6 扩展运算符
  4. 数组的slice、concat

深拷贝

在堆中重新分配内存,并且把源对象的所有属性都进行新建拷贝,新旧对象完全隔离,互不影响。

深拷贝方法

  1. JSON.parse(JSON.stringify(obj)),缺陷是方法会丢失。
  2. 递归
function deepClone(target) {
    let result;
    if (typeof target === 'object') {
        if (Array.isArray(target)) {
            result = [];
            target.forEach(item => {
                result.push(deepClone(item));
            });
        } else if (target === null) {
            result = null;
        } else if (target.constructor === RegExp || target.constructor === Date) {
            result = target;
        } else {
            result = {};
            Object.keys(target).forEach(item => {
                result[item] = deepClone(target[item]);
            });
        }
    } else {
        result = target;
    }
    return result;
}

typeof 和instanceof 的区别

  • typeof 能识别boolean、string、number、bigint、undefined、symbol、function、object。
  • instanceof 用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。

Map和WeakMap有什么区别

  • Map的键可以是任意类型,而WeakMap的键只能是对象或函数。
  • Map是可迭代的,而WeakMap不可迭代。
  • WeakMap不会阻止他的键被垃圾回收,而Map会。