【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(课前准备)
【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(一、函数式编程范式)
【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(二、JavaScript 异步编程)
【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(三、手写Promise源码)
【Part1作业】https://gitee.com/zgp-qz/part01-task
【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(一、ECMAScript 新特性)
【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(二、TypeScript 语言)
【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(三、JavaScript 性能优化1)
【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(四、JavaScript 性能优化2)
【Part2作业】https://gitee.com/zgp-qz/part02-homework
随着软件不断发展,性能优化是个不可避免的话题,什么样的行为才能算得上是性能优化呢?本质上来说,任何一种提高运行效率,降低运行开销的行为,都可以看作是一种优化操作。这也就意味着在软件开发的过程中有很多值得优化的地方。
特别是在前端应用开发过程中,性能优化可以说是无处不在的。例如:请求资源用到的网络,数据的传输方式,开发过程中所使用到的框架等等,都可以进行优化。
这里要探讨的是 javascript 本身的优化。
具体来说就是:
从认知内存空间的使用,到垃圾回收的方式介绍,从而让我们可以编写出高效的 javascript 代码。
随着硬件技术的不断发展,同时,高级编程语言当中也都自带了 GC 机制,所以,这样的一些变化,就让我们在不需要特别注意内存空间使用的情况下也能够正常的完成相应的功能开发。
为什么在这里要重提内存管理呢?
JavaScript 中的内存管理( JS 中是如何进行内存管理的):
和其他语言是一样的,也是分三步来执行这样一个过程,但是,由于 ECMAScript 中并没有提供相应的操作 API,所以 JS 语言不能像 C 或者 C++ 那样由开发者主动的调用相应的 API 来完成这样的空间管理。
不过,即使如此,它也不能影响我们通过 JS 脚本来演示当前在内部一个空间的生命周期是怎样完成的。
首先先看一下在 JS 中,什么样的内容会被当做垃圾看待:
知道了什么是垃圾之后,JS引擎就会出来工作,把它们所占据的空间进行回收,这个过程就是所谓的 JavaScript 垃圾回收。
JavaScript 中的 可达对象 :
JavaScript 中的引用与可达
let obj = {
name: 'xm'} // xm 空间被 obj 引用,在全局的执行上下文下, 当前的 obj 可以从根上被找到的(可达的)
let ali = obj // xm 空间又多了一次引用,引用数值变化(后续引用计数算法会用到),
obj = null // obj 到 xm 空间的引用被断掉了,但是 xm 还是可达的,因为 ali 还在引用着
function objGroup(obj1, obj2) {
obj1.next = obj2
obj2.prev = obj1
return {
o1: obj1,
o2: obj2
}
}
let obj = objGroup({
name: 'obj1' }, {
name: 'obj2' })
console.log(obj);
编写代码的时候会存在着一些对象引用的关系,可以从根的下边来进行查找,按照这样一个链条终归能找到某样对象,
但是如果说去找到这样的对象的路径被破坏掉或者说被回收了,这个时候是没有办法再找到它,就会把它视作为垃圾
最后就可以让垃圾回收机制去把它回收掉。
GC(Garbage Collection):垃圾回收机制的简写。
GC工作的时候,可以帮助我们找到内存中的垃圾,并释放和回收空间,方便我们后续的代码继续去使用。
GC 算法是什么?
常见 GC 算法:
核心思想:设置引用数,判断当前引用数是否为 0
在内部去通过一个引用计数器
,来维护当前对象的引用数,从而判断该对象的引用数值是否为 0 来决定它是不是一个垃圾对象,当这个数值为 0 的时候 GC 就开始工作,将其所在的空间进行回收释放再使用。
也正式因为引用计数器
的存在,导致了引用计数算法在执行效率上与其他的 GC 算法有所差别。
引用计数器
改变的规则:引用关系改变时修改引用数字(当某一个对象它的引用关系发生改变的时候,引用计数器就会主动修改当前这个引用对象所对应的引用数值)
当引用数字为 0 的时候立即回收
const user1 = {
age: 111 }
const user2 = {
age: 222 }
const user3 = {
age: 333 }
const nameList = [user1.age, user2.age, user3.age]
// function fn(){
// num1 = 1
// num2 = 2
// }
function fn() {
/**
* 加上 const ,num1 和 num2 只能在 fn 内部起效果,当 fn 调用之后,
* 在外部全局的地方找不到 num1 和 num2 这时候 他们身上的引用计数就是 0 GC 就会立即工作,把它当作垃圾回收掉
*/
const num1 = 1
const num2 = 2
}
fn()
优点:
缺点:
function fn() {
const obj1 = {
}
const obj2 = {
}
/**
* 互相的指引关系,就算在外层找不到,里边还在引用着,引用计数算法无法回收
*/
obj1.name = obj2
obj2.name = obj1
return 'this is a test'
}
fn()
核心思想:分标记和清除二个阶段完成
第一个阶段:遍历所有的对象,然后所有的活动对象(可达对象),然后进行标记;
第二个阶段:遍历所有的对象,然后把那些身上没有标记的对象进行清除,同时注意的是在第二个阶段当中,它会把第一个阶段所设置的标记给抹掉,便于 GC 下一次还能够正常的工作。
注意一点,标记清除最后去清除的时候当前程序是停止工作的。
这样就能通过两次的遍历行为把当前的垃圾空间进行回收,最终交给一个所谓的空闲列表进行维护,后续可以使用。
通过 global 往下一直找,所有能找到的都打上标记,而 a1 b1 可能是某一个局部作用域内部成员,这个局部作用域执行完成之后就被回收了,所以从 global 链条下找不到 a1 和 b1 ,这时候 GC 机制就会认为它是垃圾对象,不给它做标记,最终在 GC 工作的时候,就会把 a1 和 b1 回收掉。
相对于引用计数来说,标记清除有一个最大的优点:它可以解决之前对象循环引用不能回收的问题(例如上面那张图,如果 a1 和 b1 互相引用,引用计数算法就不管用了)。
缺点:相对于之前的垃圾回收,会产生空间碎片化的问题,不能让空间得到最大化的使用。
首先,标记整理可以看做是标记清除的增强,因为他们在第一个阶段都是一样的,都是遍历所有的活动对象进行标记,只是在清除阶段不同,标记清除是直接将没有标记的垃圾对象做垃圾回收,但是标记整理会在清除之前,先去做一个整理的操作,移动对象的位置,让它们能够在地址上产生连续。
尽可能最大化的利用所释放出来的内存空间。
V8 是一款主流的 javascript 执行引擎
javascript 之所以能高效的运转,正是因为 V8 的存在
高效,是 V8 的一个最大卖点,速度之所以快,除了背后有一套优秀的管理机制之外,V8 还有一个特点:采用 即时编译
之前很多 javascript 引擎都需要将代码先转换成字节码,然后去执行,而 V8 就可以直接将源码翻译成可以执行的机器码,所以这时候速度是非常快的
还有一个特点:V8 内存设上限
64位 – 1.5G
32位 – 800M
为什么有这么一个操作?
第一:本身是为了浏览器去制造的,所以现有的内存大小对于网页应用来说已经足够使用
第二:V8 内部的垃圾回收机制也决定了它这样的一个设置是非常合理的
官方做过这样一个测试:
当垃圾内存达到 1.5G 的时候,采用增量标记算法进行垃圾回收,只需要消耗 50ms 而如果采用非增量标记去回收,需要 1S,从用户体验来说,1S已经算是很长的时间了,所以这里就以 1.5G 为界,对 V8 内部的内存进行一个上行的设置。
总结:
在程序的运行中,会用到很多的数据,这些数据又可以分为原始数据和对象类型的数据,对于这些基础的原始数据来说,都是由语言自身来控制的,所以这里所提到的回收主要还是指的是当前存活在我们堆区里的对象数据,因此,这个过程是离不开内存操作的。
而V8当中对内存是做了上限的,所以我们就要知道,在这样的一个情况下,它是怎样对垃圾进行回收的。
新生代对象回收实现:
回收细节说明:
老生代对象说明:
老生代对象回收实现:
细节对比:
标记增量如何优化垃圾回收?
明确一点:当垃圾回收进行工作的时候,它会阻塞我们 javascript 程序的执行。
所谓的标记增量,简单来说就是将整个垃圾回收操作分成多个小步,组合着去完成当前的整个回收,从而去替代之前一口气做完的垃圾回收操作。
好处就是让程序执行和垃圾回收去交替着完成,而不像以前那样程序运行的时候不做垃圾回收,做垃圾回收的时候不能运行程序。这样带来的时间消耗更加合理一些。
虽然这样看来,可能让人觉得当前程序停顿了很多次,但是要明白,整个 V8 最大的垃圾回收当它达到 1.5G 的时候采用非增量标记的形式时间也不超过 1秒钟,所以这个间断分割是合理的。而且这样一来呢,就最大限度的把以前的很长一段的停顿时间直接拆分成更小段,这样对于用户来说,就会显得更加得好一些。
为什么使用 Performance 工具?
通过使用 Performance 时刻监控内存
使用步骤:
开启录制
访问目标网址
进行用户操作
点击 stop
查看生成的分析数据
这里看一下当我们的应用程序在执行的过程中,如果内存出现了问题,那么它具体在界面上如何展示
这里就可以更好的配合 performance 工具进行一个问题的定位,这里就依据一些性能的模型给定的一些判定的标准。
内存问题的外在表现:
页面出现延迟加载或经常性暂停(网络环境除外)
一般就判定内存有问题,而且与当前的 GC 存在频繁的垃圾回收是相关的
页面持续性出现糟糕的性能(网络环境除外)
一般会认为存在内存膨胀,所谓的内存膨胀指的是当前的界面为了达到最佳的使用速度,它会申请一定的内存空间,但是这个内存空间的大小远超过当前设备本身所能提供的大小。
页面的性能随着时间延长越来越差
这个过程一般伴随着内存泄漏
因为在这种情况下刚开始是没有问题的,可能伴随着某些代码的出现,让我们的内存空间越来越少,这就是所谓的内存泄漏
当内存出现问题的时候,一般可以归纳为几种情况:
界定内存问题的标准:
监控内存的几种方式:
demo:
平台:浏览器
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>任务管理器监控内存变化title>
head>
<body>
<button id="btn">Addbutton>
<script>
btn.onclick = function() {
let arrList = new Array(1000000)
}
script>
body>
html>
使用浏览器打开:
shift + esc
打开浏览器的任务管理器
右键将 javascript 内存 列调出来
第一列内存指的是 DOM 内存,如果这个内存不断增大,说明页面内部不断的在创建内存
最后一列 javascript 内存,这里表示的是 js 的堆,这一列当中需要关注的是小括号里面的值,它表示的是界面当中,所有可达对象正在使用的内存大小。如果这个数值一直在增大,意味着界面中,要么在创建新对象,要么就是现有对象在不断的增长。
得出结论:如果说小括号里面的数值一直在增大,意味着当前的内存是有问题的,具体是什么问题,浏览器的任务管理器就看不出来了,只能看出来是有问题的,不能定位问题。
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>时间线记录内存变化title>
head>
<body>
<button id="btn">Addbutton>
<script>
const arr = []
function test() {
for(let i = 0; i<100000; i++){
document.body.appendChild(document.createElement('p'))
}
arr.push(new Array(1000000).join('x'))
}
document.getElementById('btn').addEventListener('click',test)
script>
body>
html>
打开录制,点击几次按钮,停止录制,生成下方图表
蓝色:JS堆,红色:文档,绿色:DOM节点,黄色:侦听器,紫色:GPU
为了便于观察,将除了JS堆之外的全都关掉,便于观察目前这个脚本的 JS 堆 内存的一个走势
这个图叫时序图,鼠标移动到调试工具最上边那一栏图表,以毫秒为单位记录的页面的变化
可以看到当前页面的一个状态,通过当前页面的状态可以定位到具体某一块代码。
这里只关注 JS 堆内存,可以看到,从刚开始的平稳状态,到内存突然间就上去了,是因为我们的点击按钮操作,而上去之后就又下来了,这是因为浏览器本身的垃圾回收机制,在脚本运行稳定之后 GC 就开始工作了,回收非活动对象,后边几次的上升就是我们的连续点击按钮操作,而中间穿插的下降就是浏览器自己的回收机制,有涨有降。
如果说这个内存走势图是一直往上走,而不往下降,那么这里就存在内存消耗的问题,更有可能是内存泄漏,这时候怎么去定位问题呢?
注意,这里是一个时序图,当我们发现某个节点有问题的时候,可以直接定位到某一个时间节点
可以直接查看每一个时间点的内存消耗,并可以看到当前时间点界面展示的一个状态,从而定位大致的一个存在问题的代码块。
工作原理:
找到当前的JS堆,然后进行照片留存,就可以看到里面的所有信息。就像是一个专门针对分离DOM的一种行为。
什么是分离DOM?
界面上看到的很多元素,其实都是DOM 节点,而这些节点本应该存活在DOM树上,不过对于DOM节点会有几种形态:垃圾对象、分离DOM。
如果这个节点从DOM树上脱离,而且在JS代码中也没有人引用着,它其实就成为了一个垃圾,而如果说当前的DOM节点只是从DOM树上脱离了,但是在 JS 代码中还有人在引用着,这种DOM就叫分离DOM。
这种分离DOM 在界面上是看不见的,但是在内存里却占据着空间,所以在这种情况下就是内存泄漏,因此可以通过堆快照的功能把它从这里都找出来,只要能找到,就可以回到代码里面针对它进行清除就行。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>堆快照监控内存</title>
</head>
<body>
<button id="btn">Add</button>
<script>
let tmpEle
function fn() {
let ul = document.createElement('ul')
for(let i = 0; i < 10; i++){
let li = document.createElement('li')
ul.appendChild(li) // 创建了但是不往页面上创建
}
tmpEle = ul
}
document.getElementById('btn').addEventListener('click',fn)
</script>
</body>
</html>
发现是没有东西的
点击 Add 之后再次获取堆快照,并且搜索 detached
你会发现快照2中有内容了,就是JS创建的DOM节点,并没有添加到界面中,但是已经存在了我们的内存当中了,这其实就是一种空间上的浪费,因此这里通过堆快照的功能,找到脚本里边所存在的问题。也就是存在所谓的分离DOM 。
要解决这种问题很简单,在代码中将元素置为 null 就行了,因为之后也不会对其有其他的操作了。
再次使用浏览器打开并重新获取堆快照查看:
我们会发现没有了
总结:
利用浏览器提供的一个 堆快照 的功能,把当前的堆进行拍找,拍完照之后查找里边是否存在着一些所谓的分离DOM(detached),因为分离DOM在界面中不体现,但是在内存里边确实存在,它是一种内存的浪费,我们要做的就是定位到代码里边分离DOM所在的位置,然后想办法把它清除掉,例子是直接置为 null。
为什么确定频繁垃圾回收?
两种方式:
谷歌浏览器所提供的一款性能工具
如何精准测试 javascript 性能
JSBench 一款JSPerf 的替代品,因为 jsperf 网站停止维护。
setup: 前置初始化的一些东西
setup html: 前置的一些HTML元素
setup js: 前置的一些统一的JS代码
teardown: 一些收尾的统一的操作
点击 run 之后,等待它运行一段时间
看这里就能看出来两段同样代码的效率了
建议:
使用性能测试的时候开一个标签页,其他的都关掉,因为浏览器是多线程的,随着打开的标签页越来越多,会互相之间抢占当前的资源。
再有就是测试的时候尽可能的停在当前的页面,因为这里可能会有一个挂起的操作。操作系统是可以同时做多件事情的,而测试过程中代码的执行是消耗时间的,因为它要大量的取样,如果这时候把界面最小化或者关掉,然后去写其他代码,在这个过程中它有可能会被挂起,再回来的时候这个时间不一定是准确的。
再有就是常规的需要注意的点,不能就执行一次得到的结论跟我们想的一样或者说不一样就认为它是合理的或者说它是最终的答案,我们应该让脚本多执行几次,然后取那个几率更高的一个结果,可能就是我们想要的测试行为。
再有就是在程序的执行中,不应该纠结于代码的执行时间,当前的工具主要的是跑一下代码的执行速度,但是对于我们的性能测试,并不是关注的只有时间,它只是众多性能指标中的一个。
一段代码执行速度快,并不意味着这段代码就很健壮,关注的点不一样,那么衡量的标准和结果就不同了,而对于代码中所涉及到的性能,其实就两个方面:要么拿空间换时间,要么节省更多的空间,换取时间。
从字面上看,就是说程序执行过程中,如果针对于某些数据需要进行存储,我们需要尽可能的把它放置在局部作用域当中变成一个局部变量,至于说为什么要这么做?我们且看分析:
为什么要慎用?
总归来说,我们在使用全局变量的时候,就需要考虑更多的事情,否则,就会给我们带来一些意想不到的情况。
这里只讨论执行效率。
// 全局变量
var i, str = ''
for(i = 0; i < 1000; i++){
str += i
}
// 局部变量
for(let i = 0; i < 1000; i++){
let str = ''
str += i
}
通过缓存全局变量的方式,让代码有更高的执行性能
将使用中,无法避免的 全局变量,缓存到局部
这里对比一下采用局部缓存和不采用局部缓存,性能差异到底有多大:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>缓存全局变量title>
head>
<body>
<input type="button" value="btn" id="btn1">
<input type="button" value="btn" id="btn2">
<input type="button" value="btn" id="btn3">
<input type="button" value="btn" id="btn4">
<p>111p>
<input type="button" value="btn" id="btn5">
<input type="button" value="btn" id="btn6">
<input type="button" value="btn" id="btn7">
<p>222p>
<input type="button" value="btn" id="btn8">
<input type="button" value="btn" id="btn9">
<p>333p>
<input type="button" value="btn" id="btn10">
<script>
// 没做缓存
function getBtn1(){
let oBtn1 = document.getElementById('btn1')
let oBtn3 = document.getElementById('btn3')
let oBtn5 = document.getElementById('btn5')
let oBtn7 = document.getElementById('btn7')
let oBtn9 = document.getElementById('btn9')
let oBtn10 = document.getElementById('btn10')
}
// 做缓存
function getBtn2(){
let doc = document
let oBtn1 = doc.getElementById('btn1')
let oBtn3 = doc.getElementById('btn3')
let oBtn5 = doc.getElementById('btn5')
let oBtn7 = doc.getElementById('btn7')
let oBtn9 = doc.getElementById('btn9')
let oBtn10 = doc.getElementById('btn10')
}
script>
body>
html>
js 中的三种概念:
构造函数、原型对象、实例对象,构造函数和实例对象都是可以指向原型对象。
如果某个构造函数的内部有一个成员方法,而后续的实例对象都需要频繁的去调用,这里就可以直接将它添加在原型对象上,而不需要把它放在构造函数内部。
这两种不同的实现方式,在性能上有所差异。
这里测试一下将代码放在构造函数内部和放在原型对象上的性能差异:
var fn1 = function() {
this.foo = function() {
console.log(11111)
}
}
let f1 = new fn1()
var fn2 = function() {
}
fn2.prototype.foo = function() {
console.log(11111)
}
let f2 = new fn2()
闭包的特点:
外部具有指向内部的引用,
在“外”部作用域访问“内”部作用域的数据
关于闭包:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>闭包陷阱title>
head>
<body>
<button id="btn">addbutton>
<script>
function foo() {
var el = document.getElementById('btn')
el.onclick = function() {
console.log(el.id)
}
}
foo()
script>
body>
html>
可以看到,onclick 函数引用到了foo作用域的变量,这里边的 el 是一直被引用着的,无法被回收,当这种代码越来越多的时候,对于内存是非常不友好的,也就是闭包所存在的陷阱,就是内存泄漏。
处理:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>闭包陷阱title>
head>
<body>
<button id="btn">addbutton>
<script>
function foo() {
var el = document.getElementById('btn')
el.onclick = function() {
console.log(el.id)
}
el = null // 清掉之后 GC 会自动清除这块内存空间
}
foo()
script>
body>
html>
关于属性访问方法,是和面向对象相关的,为了实现更好的封装性,所以更多的时候可能会将一些成员属性和方法给放在一个函数的内部,然后在外部去暴漏这样的一个方法对当前的属性进行增删改查的操作,但是在 js 的面向对象当中并不是特别的适用。
因为
function Person() {
this.name = 'icoder'
this.age = 18
this.getAge = function() {
return this.age
}
}
const p1 = new Person()
const a = p1.getAge()
function Person() {
this.name = 'icoder'
this.age = 18
}
const p2 = new Person()
const b = p2.age
for 循环是编码过程中经常使用到的语法结构,每当遇到数组或者说类数组结构都可以采用for循环的方式遍历。
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>For循环优化title>
head>
<body>
<p class="btn">addp>
<p class="btn">addp>
<p class="btn">addp>
<p class="btn">addp>
<p class="btn">addp>
<p class="btn">addp>
<p class="btn">addp>
<p class="btn">addp>
<p class="btn">addp>
<p class="btn">addp>
<script>
var aBtns = document.getElementsByClassName('btn')
for(var i = 0; i < aBtns.length; i ++){
console.log(i)
}
for(var i = 0, len = aBtns.length; i < len; i ++){
console.log(i)
}
script>
body>
html>
在实际开发应用中,往往会遇到大量数据的遍历结构,而拿到这样结构之后,往往又有多种选择进行遍历,例如:for ,forEach ,for … in 等等。
这里比对一下同样的数据,不同的遍历方法,哪个会相对快一些。
var arrList = new Array(1, 2, 3, 4, 5)
arrList.forEach(function(item) {
console.log(item)
})
for (var i = arrList.length; i; i--) {
console.log(arrList[i])
}
for (var i in arrList) {
console.log(arrList[i])
}
可以看到,forEach 是最快的,for … in 是最慢的
针对于外部应用开发来说,DOM 操作是非常频繁的,而针对于 DOM 的交互操作,又是非常消耗性能的,特别是创建新的节点,将节点添加至界面中时,这个过程一般都会伴随着回流和重绘,这两个操作对于性能的消耗时比较大的,这里看一下针对于节点的添加有怎样的优化操作:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>优化节点添加title>
head>
<body>
<script>
// 直接插入
for(var i = 0; i < 10; i++){
var op = document.createElement('p')
op.innerHTML = i
document.body.appendChild(op)
}
// 先创建文档碎片,再插入
const fragEl = document.createDocumentFragment()
for(var i = 0; i < 10; i++){
var op = document.createElement('p')
op.innerHTML = i
fragEl.appendChild(op)
}
document.body.appendChild(fragEl)
script>
body>
html>
通过数字可以看出来通过文档碎片的方式统一添加要比直接 create append 的操作是要快的。
所谓的克隆指的是:当我们要去新增节点的时候,可以找到一个与他类似的一个节点,把它克隆一下,然后再把克隆好的这样的节点直接添加到我们界面当中。
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>克隆优化节点操作title>
head>
<body>
<p id="box">oldp>
<script>
// 新建
for(var i = 0; i < 3; i++){
var op = document.createElement('p')
op.innerHTML = i
document.body.appendChild(op)
}
// 克隆
var oldP = document.getElementById('box')
for(var i = 0; i < 3; i++){
var op = oldP.cloneNode(false)
op.innerHTML = i
document.body.appendChild(op)
}
script>
body>
html>
所谓的直接量替换 new Object 就是当我们要创建数组的时候,我们可以直接用 new 的方式创建,也可以直接采用它的字面量。
// 字面量
var a = [1, 2, 3]
// new Object
var a1 = new Array(3)
a1[0] = 1
a1[1] = 2
a1[2] = 3
【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(课前准备)
【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(一、函数式编程范式)
【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(二、JavaScript 异步编程)
【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(三、手写Promise源码)
【Part1作业】https://gitee.com/zgp-qz/part01-task
【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(一、ECMAScript 新特性)
【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(二、TypeScript 语言)
【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(三、JavaScript 性能优化1)
【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(四、JavaScript 性能优化2)
【Part2作业】https://gitee.com/zgp-qz/part02-homework