vue中引用js-凯发k8官方网
点击上方“蓝字”关注本公众号
写在前面
在所有的编程语言中,我们声明一个变量时,需要系统为我们分配一块内存。当我们不再需要这个变量时,需要将内存进行回收(这个过程称之为垃圾回收)。在c语言中,有malloc和free来协助我们进行内存管理。在js中,开发者不需要手动进行内存管理,js引擎会为我们自动做这些事情。但是,这并不意味着我们在使用js进行编码时,不需要关心内存问题。
js中的内存分配与变量
内存声明周期如下:
分配你所需要的内存
使用分配到的内存(读、写)
不需要时将其释放
在js中,这三步都是对开发者无感的,不需要我们过多的关心。
我们需要注意的是,当我们声明一个变量、得到一块内存时,需要正确区分一个变量到底是一个基本类型还是引用类型。
基本类型:string,number,boolean,null,undefined,symbol
引用类型:object,array,function
对于基本类型变量来说,系统会为其分配一块内存,这块内存中保存的,就是变量的内容。
对于引用类型变量来说,其存储的只是一个地址而已,这个地址指向的内存块才是是变量的真正内容。引用变量的赋值,也只是把地址进行传递(复制)。举个例子:
// a 和 b 指向同一块内存var a = [1,2,3];var b = a;a.push(4);console.log(b); // [1,2,3,4]还有一点需要注意,js中的函数传参,其实是按值传递(按引用传递)。举个例子:
// 函数f的入参,其实是把 a 的值复制了一份。注意 a 是一个引用类型变量,其保存的是一个指向内存块的一个地址。function f(obj) { obj.b = 1;}var a = { a : 1};f(a);console.log(a); // { a: 1, b: 1}在平时的开发中,完全理解js中变量的存储方式是十分重要的。对于我自己来说,尽量避免把引用类型变量到处传递,可能一不小心在某个地方修改了变量,另一个地方逻辑没有判断好,很容易出bug,特别是在项目复杂度较高,且多人开发时。这也是我比较喜欢使用纯函数的原因。
另外,根据我之前的面试经验,有不少的小伙伴认为下面的代码会报错,这也是对js中变量存储方式掌握不熟导致的。
// const 声明一个不可改变的变量。// a 存储的只是数组的内存地址而已,a.push 并不会改变 a 的值。const a = [];a.push('1'); console.log(a); // ['1']js中的垃圾回收
垃圾回收算法主要依赖于引用的概念。在内存管理的环境中,一个对象如果有访问另一个对象的权限(隐式或者显式),叫做一个对象引用另一个对象。例如,一个javascript对象具有对它原型的引用(隐式引用)和对它属性的引用(显式引用)。
在这里,“对象”的概念不仅特指 javascript 对象,还包括函数作用域(或者全局词法作用域)。当变量不再需要时,js引擎会把变量占用的内存进行回收。但是怎么界定【变量不再需要】呢?主要有两种方法。
引用计数法
把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。mdn上的例子:
var o = { a: { b:2 }}; // 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量o// 很显然,没有一个可以被垃圾收集var o2 = o; // o2变量是第二个对“这个对象”的引用o = 1; // 现在,“这个对象”只有一个o2变量的引用了,“这个对象”的原始引用o已经没有var oa = o2.a; // 引用“这个对象”的a属性 // 现在,“这个对象”有两个引用了,一个是o2,一个是oao2 = "yo"; // 虽然最初的对象现在已经是零引用了,可以被垃圾回收了 // 但是它的属性a的对象还在被oa引用,所以还不能回收oa = null; // a属性的那个对象现在也是零引用了 // 它可以被垃圾回收了这种方法有一个局限性,那就是无法处理循环引用。在下面的例子中,两个对象被创建,并互相引用,形成了一个循环。它们被调用之后会离开函数作用域,所以它们已经没有用了,可以被回收了。然而,引用计数算法考虑到它们互相都有至少一次引用,所以它们不会被回收。
// 这种情况下,o和o2都无法被回收function f(){ var o = {}; var o2 = {}; o.a = o2; // o 引用 o2 o2.a = o; // o2 引用 o return "azerty";}f();标记-清除 算法
这个算法假定设置一个叫做根(root)的对象(在javascript里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。
关于js中的垃圾回收算法,网上已经有很多的文章讲解,这里不再进行赘述。
js中的内存泄露
尽管js为我们自动处理内存的分配、回收问题,但是在某些特定的场景下,js的垃圾回收算法并不能帮我们去除已经不再使用的内存。这种【由于疏忽或错误造成程序未能释放已经不再使用的内存】的现象,被称作内存泄露。
内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。
可能产生内存泄露的场景有不少,包括全局变量,dom事件,定时器等等。
下面是一段存在内存泄露的示例代码:
class page1 extends react.component { events= [] componentdidmount() { window.addeventlistener('scroll', this.handlescroll.bind(this)); } render() { returnpage1p> .... div> } handlescroll(e) { this.events.push(e); }}
当我们点击按钮跳转到page2后,在page2不停进行滚动操作,我们会发现内存占用不断的上涨:
产生这个内存泄露的原因是:我们在page1被unmount的时候,尽管page1被销毁了,但是page1的滚动回调函数通过eventlistener依然可“触达”,所以不会被垃圾回收。进入page2后,滚动事件的逻辑依然生效,内部的变量无法被gc。如果用户在page2进行长时间滑动等操作,页面会逐渐变得卡顿。
上述的例子,在我们开发的过程中,并不少见。不仅仅是事件绑定,也有可能是定时上报逻辑等等。如何解决呢?记得在unmount的时候,进行相应的取消操作即可。
在平时的项目开发中,内存泄露还有很多其他的场景。浏览器页面还好,毕竟一直开着某个页面的用户不算太多,而且刷新就好。而node.js发生内存泄露的后果就比较严重了,可能服务就直接崩溃了。掌握js的变量存储方式、内存管理机制,养成良好的编码习惯,可以帮助我们减少内存泄露的发生。
js中的弱引用
前面我们讲到了js的垃圾回收机制,如果我们持有对一个对象的引用,那么这个对象就不会被垃圾回收。这里的引用,指的是强引用。
在计算机程序设计中,还有一个弱引用的概念:一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收。
weakmap、weakset
要说weakmap,先来说一说map。map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值) 都可以作为一个键或一个值。
map对对象是强引用:
const m = new map();let obj = { a: 1 };m.set(obj, 'a');obj = null; // 将obj置为null并不会使 { a: 1 } 被垃圾回收,因为还有map引用了 { a: 1 }weakmap是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。weakmap是对对象的弱引用:
const wm = new weakmap();let obj = { b: 2 };wm.set(obj, '2');obj = null; // 将obj置为 null 后,尽管 wm 依然引用了{ b: 2 },但是由于是弱引用,{ b: 2 } 会在某一时刻被gc正由于这样的弱引用,weakmap 的 key 是不可枚举的 (没有方法能给出所有的 key)。如果key 是可枚举的话,其列表将会受垃圾回收机制的影响,从而得到不确定的结果。
weakset可以视为 weakmap 中所有值都是布尔值的一个特例,这里就不再赘述了。
javascript 的 weakmap 并不是真正意义上的弱引用:实际上,只要键仍然存活,它就强引用其内容。weakmap 仅在键被垃圾回收之后,才弱引用它的内容。这种关系更准确地称为 ephemeron 。
weakref
weakref是一个更高级的api,它提供了真正的弱引用。我们直接借助上文的内存泄露的例子来看一看weakref的效果:
import react from 'react';import { link } from 'react-router-dom';// 使用weakref将回调函数“包裹”起来,形成对回调函数的弱引用。function addweaklistener(listener) { const weakref = new weakref(listener); const wrapper = e => { if (weakref.deref()) { return weakref.deref()(e); } } window.addeventlistener('scroll', wrapper);}class page1 extends react.component { events= [] componentdidmount() { addweaklistener(this.handlescroll.bind(this)); } componentwillunmount() { console.log(this.events); } render() { returnpage1p> .... div> } handlescroll(e) { this.events.push(e); }}export default page1;
我们再来看看点击按钮跳转到page2后的内存表现:
可以很直观的看到,在跳转到page2后,持续滚动一段时间后,内存平稳。这是因为随着page1被unmount,真正的滚动回调函数( page1的 handlescroll 函数)被gc掉了。其内部的变量也最终被gc。
但其实,这里还有一个问题,虽然我们通过weakref.deref() 拿不到 handlescroll 滚动回调函数了(已被gc),但是我们的包裹函数 wrapper 依然会执行。因为我们没有执行removeeventlistener。理想情况是:我们希望滚动监听函数也被取消掉。
可以借助finalizationregistry来实现这个功能。看下面的示例代码:
// finalizationregistry构造函数接受一个回调函数作为参数,返回一个示例。我们把实例注册到某个对象上,当该对象被gc时,回调函数会触发。const glistenersregistry = new finalizationregistry(({ window, wrapper }) => { console.log('gc happen!!'); window.removeeventlistener('scroll', wrapper);});function addweaklistener(listener) { const weakref = new weakref(listener); const wrapper = e => { console.log('scroll'); if (weakref.deref()) { return weakref.deref()(e); } } // 新增这行代码,当listener被gc时,会触发回调函数。回调函数传参由我们自己控制。 glistenersregistry.register(listener, { window, wrapper }); window.addeventlistener('scroll', wrapper);}weakref 和 finalizationregistry 属于高级api,在chrome v84 和 node.js 13.0.0 后开始支持。一般情况下不建议使用。因为容易用错,导致更多的问题。
写在后面
本文从js中的内存管理讲起,说到了js中的弱引用。虽然js引擎帮我们处理了内存管理问题,但是我们在业务开发中并不能完全忽视内存问题,特别是在node.js的开发中。
关于js内存管理的更多细节,可以移步我之前翻译的一篇文章:
v8引擎的内存管理
参考资料:
1、https://www.youtube.com/watch?v=tpm-uhwkiq8
2、https://www.infoq.cn/article/lksmb2tlgh1ehg0*bbyg
3、https://developer.mozilla.org/zh-cn/docs/web/javascript/memory_management
总结
以上是凯发k8官方网为你收集整理的vue中引用js_从js中的内存管理说起 —— js中的弱引用的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: python教材目录_python实用教
- 下一篇: 经济学中的定量分析python_(转)p