JS垃圾回收机制
内存溢出:程序运行出现的错误,就像水杯,满了之后再加水就溢出了。同理,内存溢出就是程序运行所需的内存大于可用内存,就出现内存溢出错误。
例子:写一个千万级别的循环,然后用浏览器打开,浏览器就会非常卡,甚至直接报错内存不足,崩溃了。不同浏览器有不同的表现。
产生原因:内存溢出一般是内存泄漏造成的,占用的内存不需要用到了,但是没有及时释放。内存泄漏积累的多了轻则会系统性能,重则直接引起内存溢出系统崩溃。
哪些场景会引发内存泄漏?
全局变量引起的内存泄漏:
根据 JS 的垃圾回收机制,全局变量不会被回收,所以一些意外的、不需要的全局变量多了,没有释放,就造成了内存泄漏。
闭包:
内部的变量因为被闭包引用得不到释放,会造成内存泄漏。因此我们在开发过程中,尽量不要使用闭包。
计时器、回调、监听等事件没有移除:
这些事件没有移除是一直存在的,一直存在没有被释放就会造成内存泄漏。
给 DOM 添加属性或方法:
给 DOM 添加属性或方法等,也会造成变量引用得不到释放,造成内存泄漏。
最核心的:由于垃圾回收机制,全局变量或者是被全局变量引用,垃圾回收机制就无法回收。如果一些用完一次 ...
Object.defineProperty和Proxy区别
代理和反射
vue2 中的 Object.defineProperty()和 vue3 中的 Proxy()本质上的作用都是代理。
那么什么是代理和反射呢?
反射和代理就是一种拦截并向基本操作嵌入额外行为的能力。本质上属于数据劫持。
反射Reflect 是一个内建对象**,**可简化 Proxy 的创建。
Reflect 对象使调用一些内部方法([[Get]]、[[Set]]等)成为可能,它的方法是内部方法的最小包装。
Reflect 允许我们将操作符(new,delete 等)作为函数(Reflect.construct,Reflect.deleteProperty等)执行调用。
对于每个可被Proxy捕获的内部方法,在Reflect中都有一个对应的方法,其名称和参数与 Proxy 捕捉器相同。所以,我们可以使用 Reflect 来将操作转发给原始对象。
Reflect 调用的命名与捕捉器的命名完全相同,并且接收相同的参数,因此,return Reflect … 提供了一个安全的方式,可以轻松地转发操作,并确保我们不会忘记与此相关的任何内容。
代理是目标对象抽象,也就是说,它 ...
Vue 响应式原理和双向绑定原理区分
数据响应式原理
通过数据劫持结合发布-订阅者模式的方式来实现的。
Vue 内部通过 Object.defineProperty() 监听对象属性的改变,它有对应的两个描述属性 get 和 set,当数据发生改变后,通过此方法对两个属性进行重写操作,从而通过发布-订阅者模式通知界面发生改变。
Vue2 是借助 Object.defineProperty() 实现的,而 Vue3 是借助 Proxy 实现的,通过 Proxy 对象创建一个对象的代理,并且 Proxy 的监听是深层次的,监听整个对象,而不是某个属性。
发布-订阅者模式:
new Vue() 首先执行初始化,对 data 执行响应化处理,此过程发生在 Observer 中
compiler 对模板执行编译,找到其中动态绑定的数据,从 data 中获取并初始化视图
由于 data 的某个 key 在一个视图中可能出现多次,所以每个 key 都需要一个管家 Dep 来管理多个 Watcher
同时定义一个更新函数 update()和 Watcher,将来对应数据变化时 Watcher 会调用更新函数。
一旦 data 中数据发 ...
shadow dom
问题引入:
input 为什么能输入内容?
思路:
以 Chrome 为例,F12 打开 Chrome 浏览器控制台,点击设置,开启 Element 下的 Show user agent shadow DOM 选项,可以看见一些隐藏的结构:
可以看到 input 标签下有一个 shadow-dom,点开 shadow-dom 可以看到里面的内容。这其中的内容就是具体的实现。
Shadow DOM
什么是 Shadow DOM?
Shadow DOM 是”DOM 中的 DOM“,是独立的 DOM,具有自己的元素和样式,与原始 DOM 完全隔离,是我们无法控制操作的 DOM。
相当于一个作用域的概念,使其不被外部所影响。可以看做是一颗单独的 DOM 树,这样就不会有 css 的命名冲突或样式的意外泄漏的情况。
为什么需要 Shadow DOM?
shadow dom 是游离于 DOM 树之外的节点树,但其创建是基于普通的 DOM 元素(非 document),并且创建的节点可以直接从界面上直观的看到
shadow dom 有良好的密封性(浏览器提供的一种“封装”功能,提供了一种 ...
keep-alive实现原理
该组件内没有常规的<template></template>等标签,因为它不是一个常规的模板组件,取而代之的是,它内部多了一个 render 函数,它是一个函数式组件。执行<keep-alive>组件渲染时,就会执行这个render函数。
keep-alive 缓存机制是根据 LRU 策略来设置缓存组件新鲜度,将很久未访问的组件从缓存中删除。
组件实现原理
// 源码位置:src/core/components/keep-alive.jsexport default { name: "keep-alive", abstract: true, props: { include: patternTypes, exclude: patternTypes, max: [String, Number], }, created() { this.cache = Object.create(null); this.keys = []; }, des ...
JS为什么要进行变量提升?
1.变量提升如何体现?
变量提升通常发生在 var 声明的变量里,使用 var 声明一个变量时,该变量会被提升到作用域的顶端,但是赋值的部分并不会被提升。
原理:
JS 引擎工作方式:先解析代码,获取所有被声明的变量
然后再运行。
2.为什么要进行变量提升?
首先,我们知道 JS 拿到一个变量时会进行解析和执行。
在解析阶段,JS 会检查语法,并对函数进行预编译。解析时会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为 undefined,函数先声明好可使用。在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局上下文环境类似,不过函数上下文会多出 this、arguments 和函数的参数。
全局上下文:变量定义、函数声明
函数执行上下文:变量定义、函数声明、this、arguments
在执行阶段,按照代码的顺序依次执行
为什么会进行变量提升?
提高性能
容错性更好
(1)提高性能
JS 代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。
这样做是为了提高性能,如果没有这一步,那么每次执行代码前都必须重新编 ...
JS高频面试题
apply、call、bind 的区别
三者都可以改变函数的 this 对象指向
三者第一个参数都是 this 要指向的对象,如果如果没有这个参数或参数为 undefined 或 null,则默认指向全局 window
三者都可以传参,但是 apply 是数组,而 call 是参数列表,且 apply 和 call 是一次性传入参数,而 bind 可以分为多次传入
bind 是返回绑定 this 之后的函数,需要手动执行函数,apply、call 则是立即执行
new 操作符的实现原理
new 操作符的执⾏过程:
(1)⾸先创建了⼀个新的空对象
(2)设置原型,将对象的原型设置为函数的 prototype 对象。
(3)让函数的 this 指向这个对象,执⾏构造函数的代码(为这个新对象添加属性)
(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引⽤类型,就返回这个引⽤类
型的对象。
function objectFactory() { let newObject = null; let constructor = Array.prototype.shi ...
JS编译原理
JS 编译原理
var name = "rose";
上面这行代码在 JS 中会这样呈现:
var name; // 编译阶段处理name = "rose"; // 执行阶段处理
JS 编译主要分为两个阶段:编译阶段和执行阶段。
编译阶段
此阶段主角为编译器。
JS 找遍作用域,看是否存在 name 的变量
如果已经存在,则什么都不做,直接忽略var name这个声明,继续编译下去;
如果没有,则在当前作用域中新增一个name变量
编译器会为引擎生成运行时所需的代码,程序就进入了执行阶段。
执行阶段
此阶段主角为JS 引擎。
JS 引擎在运行时,会先找遍当前作用域,看是否有一个叫name的变量。
如果有,直接赋值
如果没有,则为当前作用域没有。则去父级作用域看是否有,如果无,则去上一级作用域中查找。
如果最终没有找到,则抛异常。
作用域套作用域,即作用域链。
作用域
变量最基本的能力就是能够存储变量中的值,并且允许我们对此变量进行访问和修改,而对于变量存储,访问的规则是 作用域。
全局作用域
在任何函数外或代码块外的顶层 ...