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
- 存储于内存中的栈区,先进后出,后进先出。
区别
- 访问方式。
- 按值访问
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'}
- 比较方式。
- 原始值:比较的是值。
- 引用值:比较的是地址。
- 动态属性。
- 原始值:无法添加动态属性。
- 引用至:可以添加动态属性。
- 变量赋值。
- 原始值:赋的是值。
- 引用值:赋的是地址。
是否了解JS中的包装类型?
就是当基本类型以对象的方式去使用时,js会转换成对应的包装类型,number、string、boolean都有对应的包装类型。
说一说JS中的原型与原型链的理解?
- 每个对象都有一个__proto__属性,该属性指向自己的原型对象。
- 每个构造函数都有一个prototype属性,该属性指向实例对象的原型对象。
- 原型对象的constructor指向构造函数本身。
- 每个对象都有自己的原型对象,而原型对象本身也有自己的原型对象,从而形成了一条原型对象链。
谈谈你对JS执行上下文栈的理解?
执行上下文就是评估和执行JS代码的环境。分为全局上下文,函数上下文,Eval函数上下文。
谈谈你对作用域和作用域链的理解?
作用域
什么是作用域
作用域就是一个独立的地盘,让变量不会泄露出去,也就是说作用域最大的作用就是隔离变量,不同作用域下同名的变量不会有冲突。
作用域类型
- 全局作用域。
- 最外层函数和最外层变量。
- 所有未定义直接赋值的变量自动声明为拥有全局作用域。
- 所有window属性拥有全局作用域。
- 函数作用域。
- 块级作用域。
- 在函数内部会被创建。
- 在一个代码块内部。被{}号包裹。
作用域链
在当前作用域中没有找到,就会一层一层往上寻找(是在创建时的作用域中取值,而不是调用时的作用域中取值),直到全局作用域还没找到,就宣布放弃。这种一层层的关系,就叫做作用域链。
JS中的垃圾回收机制
- 标记清除。
- 垃圾回收器,在运行的时候会给所有在内存中的变量打上标记。
- 然后去掉环境中的变量以及被环境引用的变量。
- 被标记上的会被视为准备删除的变量;
- 等垃圾回收机制开始执行的时候就会销毁这些变量。
- 引用计数。
- 声明了一个变量,并把一个引用类型的值赋给这个变量,这个引用类型的引用数就为1;
- 同一个值又赋给另外一个变量,那么这个引用类型的值的引用数又会+1;
- 当包含这个引用类型的变量又被赋给了其他值,那么这个引用次数就会-1;
- 当引用次数为0的时候,就说明没法访问这个变量了;
- 垃圾回收机制下一次运行的时候,就会清除引用次数为0的变量。
- 但是循环引用会造成内存泄漏
闭包
什么是闭包
- 闭包是一个封闭的空间,里面存储了其他地方会引用到的该作用域的值,在JS中通过作用域链来实现闭包。
- 只要在函数中使用了外部的数据,就创建了闭包。这种情况下所创建的闭包,我们在编码时是不需要去关心的,会自动销毁。
- 我们可以通过在函数内部返回一个函数,来手动创建闭包,从而让外部环境访问到函数内部的局部变量,让局部变量持续保存下来,不随着他的上下文环境一起销毁。
闭包可以解决什么问题?
解决全局污染的问题
怎么销毁闭包
如果是自动产生的闭包,我们无需操心闭包的销毁。如果是手动创建的闭包,可以把引用的变量设置为null,下次垃圾回收运行时,就会自动回收。
DOM事件的传播机制
事件冒泡
即事件开始由最具体的元素接收,然后逐级向上传播。
事件捕获
事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后收到事件。
标准DOM事件流
- 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>
深拷贝和浅拷贝的区别?如何实现
浅拷贝
只拷贝基本类型的数据,如果是引用类型,拷贝的是对象的引用地址,而不拷贝对象本身,新旧对象还是共享同一块内存。
浅拷贝实现
- 直接赋值
- Object.assign()
- ES6 扩展运算符
- 数组的slice、concat
深拷贝
在堆中重新分配内存,并且把源对象的所有属性都进行新建拷贝,新旧对象完全隔离,互不影响。
深拷贝方法
- JSON.parse(JSON.stringify(obj)),缺陷是方法会丢失。
- 递归
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会。