这是我第 16 篇简书.
整体思路:
性能优化是什么?
从用户角度来看, 什么才是好的页面? 一个是页面加载的很快, 另一个是页面使用起来很流畅.
那么对应到前端开发的角度, 性能优化可以对应分为两个方向: 页面加载时间跟页面运行效率.
从浏览器打开到页面渲染完成, 花费了多少时间?
是的, 这个问题有点熟悉, 面试官比较常问的是从浏览器打开到页面渲染完成, 发生了什么事情. 这个问题网上很多回答, 我也不就重复的细说了. 主要的过程是:
浏览器解析 ->查询缓存 ->dns 查询 ->建立链接 ->服务器处理请求 ->服务器发送响应 ->客户端收到页面 ->解析 html->构建渲染树 ->开始显示内容 (白屏时间)-> 首屏内容加载完成 (首屏时间)-> 用户可交互 (DOMContentLoaded)-> 加载完成(load)
很显然, 如果我们要进行加载时间的优化, 我们需要从这里的每一个步骤都去思考, 去总结, 而避免东凑一点, 西凑一点.
具体细节:
一, web 性能优化
1, 减少 HTTP 请求(这个优化是最明显的)
(1)图片地图, 允许在一个图片上关联多个 URL, 目标 URL 取决于用户单击的图片上的位置.
(2)雪碧图(也叫 CSS 精灵, 是一种 CSS 图像合成技术, 一般用于图标, 将多张图片合并到一张图片, 配合 background-position 来使用)
(3)合并 JS 和 CSS 文件
(4)减少 http 请求头
(5)配置多个域名和 CDN 加速
把网站内容分散到多个, 处于不同地域位置的服务器上可以加快下载速度.
(6)使用缓存(HTTP 缓存, 浏览器缓存, 应用缓存)
恰当的缓存设置可以大大的减少 HTTP 请求, 例如, 很少变化的图片资源可以直接通过 HTTP Header 中的 Expires 设置一个很长的过期头 ; 变化不频繁而又可能会变的资源可以使用 Last-Modifed 来做请求验证. 尽可能的让资源能够在缓存中待得更久.
(7)优化 cookie
2, 避免坏请求
有时候页面中的 HTML 或 CSS 会向服务器请求一个不存在的资源, 比如图片或者 HTML 文件, 这会造成浏览器与服务器之间过多的往返请求.
3, 避免使用 document.write
在 JS 中, 可以使用 document.write. 在网页上显示内容或者调用外部资源, 而通过此方法, 浏览器采取一些多余的步骤(下载资源, 读取资源). 运行 JS 来了解需要做什么, 调用其他资源时, 需要重新在执行一次这个过程. 由于浏览器之前不知道要显示什么, 所以会降低页面加载的速度.
要知道, 任何能够被 document.write 调用的资源, 都可以通过 HTML 调用. 这样速度会更快
document.write('<scriptsrc="another.js"></script>');
改为
<scriptsrc="another.js"></script>
4, 尽量减少 dns 查询次数
当浏览器和服务器建立链接时, 它需要进行 dns 解析, 将域名解析为 ip 地址, 然而, 一旦客户端需要执行 dns lookup 时, 等待时间将会取决于域名服务器的有效响应速度.
虽然所有的 isp 的 dns 服务器都能缓存域名和 ip 地址映射表. 但如果缓存的 dns 记录过期了而需要更新, 则可能需要遍历多个 dns 节点, 有时候需要通过全球范围内来找到可信任的域名服务器, 一旦域名服务器工作繁忙, 请求解析时, 就需要排队则进一步延时等待时间.
所有减少 dns 查询次数很重要, 页面加载就尽量避免额外耗时, 为了减少 dns 查询次数, 最好的解决方法就是在页面中减少不同的域名请求的机会,
可通过 request checker 工具来检测页面中存在多少请求后, 进行优化.
5, 尽量减少重定向
有时候为了特定需求, 需要在网页中使用重定向. 重定向的意思是, 用户的原始请求 (如请求 A) 被重定向到其他的请求(如请求 B);
网页中使用重定向会造成网站性能和速度下降, 因为浏览器访问网址是一连串的过程, 如果访问到一半, 而跳转到新的地址, 就会重复发起一连串的过程, 这将浪费很多时间. 所有我们尽量避免重定向. Google 建议:
A, 不要链接到一个包含重定向的页面
B, 不要请求包含重定向的资源
6, 优化样式表和脚步顺序
Style 标签和样式表调用代码应该放置在 JS 代码的前面, 这样可以使页面的加载速度加快.
7, 避免 JS 阻塞渲染
浏览器在遇到一个引入外部 JS 文件的 < script > 标签时, 会停下所有工作下载并解析执行它. 在这个过程中, 页面渲染和用户交互完全被阻塞了. 这是页面加载就会停止.
8, 启用压缩 / Gzip
使用 gzip 对 HTML 和 CSS 文件进行压缩, 通常可以大约节省 50% 到 70%, 这样加载页面只需要更少的带宽和更少的时间.
使用 gzip 对 HTML 和 CSS 文件进行压缩, 通常可以大约节省 50% 到 70%, 这样加载页面只需要更少的带宽和更少的时间.
Google 建议删除干扰页面第一屏内容加载 的 JS, 第一屏指的是用户在屏幕中最初看到的页面, 无论桌面浏览器, 还是手机
二, HTML
1, 减少 DOM 元素数量
页面中存在大量 DOM 元素, 会导致 JS 遍历 DOM 的效率变慢. 尤其要尽量少用 iframe, 它是耗能最大的 dom 元素, 而且会阻塞 onload 事件(除了获取葵花码 扫码登录这些需要用到 iframe, 其他地方基本不使用了)
2, 使用 CSS+div 代替 table 布局, 去掉格式化控制标签如: strong,b,i 等, 使用 CSS 控制.
下面这个说给不怎么懂前端的技术总监听[滑稽]:
说前 5 点就行了
为什么不建议用 table 进行布局?
1)table 比其它 HTML 标记占更多的字节.(造成下载时间延迟, 占用服务器更多流量资源)
2)table 会阻挡浏览器渲染引擎的渲染顺序.(会延迟页面的生成速度, 让用户等待更久的时间)
3)table 里显示图片时需要你把单个, 有逻辑性的图片切成多个图.(增加设计的复杂度, 增加页面加载时间, 增加 http 会话数)
4)在某些浏览器中, table 里的文字的拷贝会出现问题.(会让用户不悦)
5)table 会影响其内部的某些布局属性的生效(比如 < td > 里的元素的 height:100%) (限制页面设计的自由性)
6)一旦学了 CSS 的知识, 你会发现使用 table 做页面布局会变得更麻烦.(先花时间学一些 CSS 知识, 会省去你以后大量的时间)
7)'table 对'对于页面布局来说, 从语义上看是不正确的.(它描述的是表现, 而不是内容)
8)table 代码可读性差.(不但无法利用 CSS, 而且会不知所云, 尤其在进行页面改版或内容抽取的时候)
9)table 一旦设计完成就变成死的, 很难通过 CSS 让它展现新的面貌
3, 减少不必要的嵌套, 尽量扁平化
因为当浏览器编译器遇到一个标签时就开始寻找它的结束标签, 直到它匹配上才能显示它的内容, 所以当嵌套很多标签时会影响页面加载速度(当然这个影响微乎其微).
三, CSS
是时候展现真正的技术了, 什么叫专业前端.
1. 内联首屏关键 CSS
性能优化中有一个重要的指标 -- 首次有效绘制 (First Meaningful Paint, 简称 FMP) 即指页面的首要内容出现在屏幕上的时间. 这一指标影响用户看到页面前所需等待的时间, FMP 能减少这一时间.
这点对于电商网站特别重要, 做后台管理的可以忽视.
将 CSS 直接内联到 HTML 文档中能使 CSS 更快速地下载. 而使用外部 CSS 文件时, 需要在 HTML 文档下载完成后才知道所要引用的 CSS 文件, 然后才下载它们. 所以说, 内联 CSS 能够使浏览器开始页面渲染的时间提前, 因为在 HTML 下载完成之后就能渲染了. 所以不能一味的没有想法的通过 link 标签引用外部 CSS 文件.
但是, 又不能内联所有的 CSS, 这种方式并不适用于内联较大的 CSS 文件. 因为初始拥塞窗口存在限制(TCP 相关概念, 通常是 14.6kB, 压缩后大小), 如果内联 CSS 后的文件超出了这一限制, 系统就需要在服务器和浏览器之间进行更多次的往返, 这样并不能提前页面渲染时间. 因此, 我们应当只将渲染首屏内容所需的关键 CSS 内联到 HTML 中.
如何确定首屏关键 CSS 呢? 显然, 我们不需要手动确定哪些内容是首屏关键 CSS. 推荐 GitHub 上一个开源项目 Critical CSS, 可以将属于首屏的关键样式提取出来.
不过内联 CSS 有一个缺点, 内联之后的 CSS 不会进行缓存, 每次都会重新下载. 所以上面的说到的初始拥塞窗口将内联后的文件大小控制在了 14.6kb 以内, 每次重新下载 14.6kb 可以接受.
2. 异步加载 CSS
CSS 会阻塞渲染, 在 CSS 文件请求, 下载, 解析完成之前, 浏览器将不会渲染任何已处理的内容. 有时, 这种阻塞是必须的, 因为我们需要在 CSS 加载后再开始渲染页面. 那么将首屏关键 CSS 内联后, 剩余的 CSS 内容的阻塞渲染就不是必需的了, 可以使用外部 CSS, 并且异步加载.
如何实现 CSS 的异步加载呢?
(前 3 点看看就好, 重点用第 4 点)
1)使用 JavaScript 动态创建样式表 link 元素, 并插入到 DOM 中.
- // 创建 link 标签
- const myCSS = document.createElement( "link" );
- myCSS.rel = "stylesheet";
- myCSS.href = "mystyles.css";
- // 插入到 header 的最后位置
- document.head.insertBefore( myCSS, document.head.childNodes[ document.head.childNodes.length - 1 ].nextSibling );
2)将 link 元素的 media 属性设置为用户浏览器不匹配的媒体类型(或媒体查询), 如 media="print", 甚至可以是完全不存在的类型 media="noexist". 对浏览器来说, 如果样式表不适用于当前媒体类型, 其优先级会被放低, 会在不阻塞页面渲染的情况下再进行下载.
<link rel="stylesheet" href="mystyles.css" media="noexist" onload="this.media='all'">
当然, 这么做只是为了实现 CSS 的异步加载, 别忘了在文件加载完成之后, 将 media 的值设为 screen 或 all, 从而让浏览器开始解析 CSS.
3)我们还可以通过 rel 属性将 link 元素标记为 alternate 可选样式表, 也能实现浏览器异步加载. 同样别忘了加载完成之后, 将 rel 改回去.
<link rel="alternate stylesheet" href="mystyles.css" onload="this.rel='stylesheet'">
4)然而, 上述的三种方法已过时. 现在, rel="preload" 这一 Web 标准指出了如何异步加载资源, 包括 CSS 类资源.
<link rel="preload" href="mystyles.css" as="style" onload="this.rel='stylesheet'">
注意, as 是必须的. 忽略 as 属性, 或者错误的 as 属性会使 preload 等同于 XHR 请求, 浏览器不知道加载的是什么内容, 因此此类资源加载优先级会非常低.
看起来, rel="preload" 的用法和上面两种没什么区别, 都是通过更改某些属性, 使得浏览器异步加载 CSS 文件但不解析, 直到加载完成并将修改还原, 然后开始解析.
但是它们之间其实有一个很重要的不同点, 那就是使用 preload, 比使用不匹配的 media 方法能够更早地开始加载 CSS.
3. 文件压缩(效果显著)
文件的大小会直接影响浏览器的加载速度, 这一点在网络较差时表现地尤为明显. 相信大家都早已习惯对 CSS 进行压缩, 现在的构建工具, 如 webpack,gulp/grunt,rollup 等也都支持 CSS 压缩功能. 压缩后的文件大小明显减小, 可以大大降低了浏览器的加载时间.
4. 去除无用 CSS
虽然文件压缩能够降低文件大小. 但 CSS 文件压缩通常只会去除无用的空格. 而去除无用 CSS 会进一步减小 CSS 文件的大小.(文件压缩是个前端都知道, 而去除无用 CSS 相信很多人没用到过, 还有前面异步加载的黑科技, 面试就是各种知识细节堆积, 加起来就能给人一种靠谱专业的冲击感)
一般情况下, 会存在这两种无用的 CSS 代码: 一种是不同元素或者其他情况下的重复代码, 一种是整个页面内没有生效的 CSS 代码. 对于前者, 在编写的代码时候, 我们应该尽可能地提取公共类, 减少重复. 对于后者, 在不同开发者进行代码维护的过程中, 总会产生不再使用的 CSS 的代码, 当然一个人编写时也有可能出现这一问题. 而这些无用的 CSS 代码不仅会增加浏览器的下载量, 还会增加浏览器的解析时间, 这对性能来说是很大的消耗. 所以我们需要找到并去除这些无用代码.
当然, 如果手动删除这些无用 CSS 是很低效的. 我们可以借助 Uncss 库来进行. Uncss 可以用来移除样式表中的无用 CSS, 并且支持多文件和 JavaScript 注入的 CSS.
5. 有选择地使用选择器
因为 CSS 选择器的匹配是从右向左进行的, 所以尽量保持以下原则:
保持简单, 不要使用嵌套过多过于复杂的选择器.
通配符和属性选择器效率最低, 需要匹配的元素最多, 尽量避免使用. (子元素选择器它不香吗)
不要使用类选择器和 ID 选择器修饰元素标签, 这样多此一举, 还会降低效率.
不要为了追求速度而放弃代码的可读性与可维护性, 没有绝对的优化.
一般开发团队都会定好 CSS 编写规范, 如果你的团队还没定, 那么你一定要普及大家用同一规范(为什么呢, 难道看别人代码时不难受吗?), 或者引用借鉴一些: 如 BEM,OOCSS,SUIT,SMACSS,ITCSS 等作为 CSS 编写规范. 使用统一的方法论能够帮助大家形成统一的风格, 减少命名冲突, 也能避免上述的问题.
6. 减少使用昂贵的属性
在浏览器绘制屏幕时, 所有需要浏览器进行操作或计算的属性相对而言都需要花费更大的代价. 当页面发生重绘时, 它们会降低浏览器的渲染性能. 所以在编写 CSS 时, 我们应该尽量减少使用昂贵属性, 如 box-shadow,border-radius,filter, 透明度,:nth-child 等.
当然, 并不是让大家不要使用这些属性(比如 border-radius 确实不得不用), 因为这些应该都是我们经常使用的属性. 只是当有两种方案可以选择的时候, 可以优先选择没有昂贵属性或昂贵属性更少的方案, 如果每次都做这样的决策, 网站的性能会在不知不觉中得到一定的提升.
7. 优化重排与重绘(划重点)
在网站的使用过程中, 某些操作会导致样式的改变, 这时浏览器需要检测这些改变并重新渲染, 其中有些操作所耗费的性能更多. 我们都知道, 当 FPS 为 60 时, 用户使用网站时才会感到流畅. 这也就是说, 我们需要在 16.67ms 内完成每次渲染相关的所有操作, 所以我们要尽量减少耗费更多的操作.
(1)减少重排
重排会导致浏览器重新计算整个文档, 重新构建渲染树, 这一过程会降低浏览器的渲染速度. 如下所示, 有很多操作会触发重排, 我们应该避免频繁触发这些操作:
改变 font-size 和 font-family
改变元素的内外边距
通过 JS 改变 CSS 类
通过 JS 获取 DOM 元素的位置相关属性(如 width/height/left 等)
CSS 伪类激活
滚动滚动条或者改变窗口大小
此外, 我们还可以通过 CSS Trigger 查询哪些属性会触发重排与重绘.
值得一提的是, 某些 CSS 属性具有更好的重排性能. 如使用 Flex 时, 比使用 inline-block 和 float 时重排更快, 所以在布局时可以优先考虑 Flex.
(2)避免不必要的重绘
当元素的外观 (如 color,background,visibility 等属性) 发生改变时, 会触发重绘. 在网站的使用过程中, 重绘是无法避免的. 不过, 浏览器对此做了优化, 它会将多次的重排, 重绘操作合并为一次执行. 不过我们仍需要避免不必要的重绘, 如页面滚动时触发的 hover 事件, 可以在滚动的时候禁用 hover 事件, 这样页面在滚动时会更加流畅.
此外, 我们编写的 CSS 中动画相关的代码越来越多, 我们已经习惯于使用动画来提升用户体验. 我们在编写动画时, 也应当参考上述内容, 减少重绘重排的触发. 除此之外我们还可以通过硬件加速, will-change 来提升动画性能, 感兴趣可百度.
最后需要注意的是, 用户的设备可能并没有想象中的那么好, 至少不会有我们的开发机器那么好. 我们可以借助 Chrome 的开发者工具进行 CPU 降速, 然后再进行相关的测试, 降速方法如下图所示. 如果需要在移动端访问的, 最好将速度限制更低, 因为移动端的性能往往更差.
先点击最右边的设置图标, 然后勾选画圆圈的方框, 最后将 CPU 调制到 4 倍降速, 移动端调成 8 倍降速
8. 不要使用 @import 引入 CSS
最后提一下, 不要使用 @import 引入 CSS, 相信大家也很少使用.
不建议使用 @import 主要有以下两点原因:
首先, 使用 @import 引入 CSS 会影响浏览器的并行下载. 使用 @import 引用的 CSS 文件只有在引用它的那个 CSS 文件被下载, 解析之后, 浏览器才会知道还有另外一个 CSS 需要下载, 这时才去下载, 然后下载后开始解析, 构建 render tree 等一系列操作. 这就导致浏览器无法并行下载所需的样式文件.
其次, 多个 @import 会导致下载顺序紊乱. 在 IE 中,@import 会引发资源文件的下载顺序被打乱, 即排列在 @import 后面的 JS 文件先于 @import 下载, 并且打乱甚至破坏 @import 自身的并行下载.
所以不要使用这一方法, 使用 link 标签就行了.
四, JS
(如果说 CSS 优化是锦上添花, 那么 CSS 是花, JS 优化才是锦)
1. 减少全局变量的查找.
因为全局变量在作用域链的最顶端, 频繁查找很耗性能. 所以在一个函数中尽量将全局对象存储为局部变量来查找, 因为访问局部变量的数要更快一些.
- function(){
- alert(Windows.location.href+Windows.location.host);
- }
修改为:
- funciton(){
- var location=Windows.location;
- alert(location.href+location.host);
- }
2. 慎用 with
with 会创建自已的作用域, 因此会增加其中执行代码的作用域的长度.(我好像从来没用过 with, 也没见别人代码里用过)
with(obj){ p = 1}; 代码块的行为实际上是修改了代码块中的 执行环境, 将 obj 放在了其作用域链的最前端, 在 with 代码块中访问非局部变量是都是先从 obj 上开始查找, 如果没有再依次按作用域链向上查找, 因此使用 with 相当于增加了作用域链长度. 而每次查找作用域链都是要消耗时间的, 过长的作用域链会导致查找性能下降.
因此, 除非你能肯定在 with 代码中只访问 obj 中的属性, 否则慎用 with, 可以使用局部变量缓存需要访问的属性.
- with(a,b,c,d){
- property1=1;
- property2=2;
- }
修改为:
- var obj=a.b.c.d;
- obj.property1=1;
- obj.property2=2;
3. 慎用 定时器
(1)如果针对的是不断运行的代码, 不应该使用 setTimeout, 而应该使用 setInterval. 因为 setTimeout 每一次都会初始化一个定时器. 而 setInterval 只会在开始的时候初始化一个定时器.
(2)不要给 setTimeout 或 setInterval 传递字符串参数.
4 避免使用 eval 和 Function
每次 eval 或 Function 构造函数作用于字符串表示的源代码时, 脚本引擎都需要将源代码转换成可执行代码. 这是很消耗资源的操作 -- 通常比简单的函数调用慢 100 倍以上.
eval 函数效率特别低, 由于事先无法知晓传给 eval 的字符串中的内容, eval 在其上下文中解释要处理的代码, 也就是说编译器无法优化上下文, 因此只能有浏览器在运行时解释代码. 这对性能影响很大.
Function 构造函数比 eval 略好, 因为使用此代码不会影响周围代码 ; 但其速度仍很慢.
此外, 使用 eval 和 Function 也不利于 JavaScript 压缩工具执行压缩.
5. 优化循环
循环是编程中最常见的结构, 优化循环是性能优化过程中很重要的一部分. 一个循环的基本优化步骤如下:
1)减值迭代 -- 大多数循环使用一个从 0 开始, 增加到某个特定值的迭代器. 在很多情况下, 从最大值开始, 在循环中不断减值的迭代器更加有效.
2)简化终止条件 -- 由于每次循环过程都会计算终止条件, 故必须保证它尽可能快, 即避免属性查找或其它操作. 最好是将循环控制量保存到局部变量中, 也就是说对数组或列表对象遍历的时候, 提前将 length 保存到局部变量中, 避免循环的每一步重复取值.
3)简化循环体 -- 循环体是执行最多的, 故要确保其被最大限度地优化. 确保没有某些可以被很容易移出循环的密集计算.
4)使用后测试循环 -- 在 JS 中, 我们常用的循环中:
for(in)的效率极差, 因为他需要查询散列键, 只要可以, 就应该尽量少用.
for 和 while 循环, while 优于 for, 可能 for 结构问题, 需要经常的跳转. do..while 更好.
最常用的 for 和 while 循环都是前测试循环, 而后测试循环如 do-while 循环可以避免最初终止条件的计算, 因些计算更快.
- for(var i = 0; i <values.length; i++) {
- process(values[i]);
- }
优化 1: 简化终止条件
- for(var i = 0, len = values.length; i < len; i++) {
- process(values[i]);
- }
优化 2: 使用后测试循环(注意: 使用后测试循环需要确保要处理的值至少有一个)
- var i values.length - 1;
- if(i> -1) {
- do {
- process(values[i]);
- }while(--i>= 0);
- }
6. 字符串拼接
在 JavaScript 中使用 "+" 号来拼接字符串效率是比较低的, 因为每次运行都会开辟新的内存并生成新的字符串变量, 然后将拼接结果赋值给新变量. 与之相比更为高效的做法是使用数组的 join 方法, 即将需要拼接的字符串放在数组中最后调用其 join 方法得到结果. 不过由于使用数组也有一定的开销, 因此当需要拼接的字符串较多的时候可以考虑用此方法.
1)如果需要连接多个字符串, 应该少使用 +=:
如
s+=a;s+=b;s+=c;
修改为:
s+=a+b+c;
2)而如果是收集字符串, 比如多次对同一个字符进行 += 操作的话, 最好使用一个缓存, 使用 JS 数组来收集, 最后 join 方法连接起来.
- var buf=[];
- for(var i=0;i<100;i++){
- buf.push(i.toString());
- }
- var all=buf.join("");
7. 少操作 DOM
DOM 操作应该是脚本中最耗性能的一类操作, 例如增加, 修改, 删除 DOM 元素或者对 DOM 集合进行操作.
(1)最小化现场更新
一旦你需要访问的 DOM 部分是已经显示的页面的一部分, 那么你就是在进行一个现场更新. 之所以叫现场更新, 是因为需要立即 (现场) 对页面对用户的显示进行更新, 每一个更改, 不管是插入单个字符还是移除整个片段, 都有一个性能惩罚, 因为浏览器需要重新计算无数尺寸以进行更新. 现场更新进行的越多, 代码完成执行所花的时间也越长.
(2)多使用 innerHTML
使用一次 innerHTML 复制代替构建 dom 元素.
有两种在页面上创建 DOM 节点的方法: 使用诸如 createElement()和 appendChild()之类的 DOM 方法, 以及使用 innerHTML. 对于小的 DOM 更改, 两者效率差不多, 但对于大的 DOM 更改, innerHTML 要比标准的 DOM 方法创建同样的 DOM 结构快得多.
当使用 innerHTML 设置为某个值时, 后台会创建一个 HTML 解释器, 然后使用内部的 DOM 调用来创建 DOM 结构, 而非基于 JS 的 DOM 调用. 而内部方法是编译好的而非解释执行, 故执行的更快.
- varfrag=document.createDocumentFragment();
- for(var i=0;i<1000;i++){
- varel=document.createElement('p');
- el.innerHTML=i;
- frag.appendChild(el);
- }
- document.body.appendChild(frag);
应该改为:
- var HTML=[];
- for(var i=0;i<1000;i++){
- HTML.push('<p>'+i+"</p>");
- }
- document.body.innerHTML=HTML.join("");
五, 用户体验角度
(1)预加载, 懒加载
(2)浏览器缓存, 应用缓存
(3)细节设计(全选 / 反全选 / 自动补全...... 所有你能想到的)
如果觉得干货很多, 请给一个赞.
来源: http://www.jianshu.com/p/afb1c3148440