文档对象模型(DOM,Document Object Model)是一个应用编程接口(API),用于在HTML中使用扩展的HTML。DOM将整个页面抽象为一组分层节点。
DOM通过创建表示文档的树,让开发者可以随心所欲的控制网页的内容和结构。使用DOM API可以轻松地删除、添加、替换、修改节点。
对浏览器而言,DOM就是使用ECMAScript实现的,如今已经成为JavaScript语言的一大组成部分。
言而言之,DOM提供与网页内容交互的方法和接口。
IE3和Netscape Navigator3提供了浏览器对象模型(BOM)API,用于支持访问和操作浏览器的窗口。使用BOM,开发者可以操控浏览器显示页面之外的部分。
BOM的能力展示:
标签有8大属性,可以包含来自外部域的JavaScript文件,
的src属性可以是一个完整的URL,并且这个URL指向的资源可以跟包含它的HTML页面不在同一个域中,浏览器解析这个资源时,会想src属性指定的路径发送一个GET请求,以取得相应资源,这个初始的请求不受浏览器同源策略限制,但返回并被执行的JavaScript则受限制。当然,这个请求仍然受父页面HTTP/HTTPS协议的限制。
过去,所有的标签都放在head标签中,这就意味着所有JavaScript代码都要下载、解析和解释完成后,才能开始渲染页面(页面在浏览器解析到
标签的起始标签时开始渲染)。对于需要很多JavaScript的页面,会导致页面渲染的明显延迟,在此期间浏览器窗口完全空白。
为了解决这个问题,现代Web应用程序通常将所有JavaScript引用放在元素中的页面内容后面。
正常情况下,JavaScript的代码是书序执行的。
defer
脚本会延迟到整个页面都解析完毕后再运行,只适用于外部脚步。
async
脚本与defer
脚本类似,都是只适用于外部脚本,但async
脚本并不能保证按照它们出现的次序执行。
JavaScript可以通过向DOM中动态添加script元素同样可以加载指定的脚本,只要创建一个script元素并将其添加到DOM即可。
let script = document.createElement('script');
script.src = 'nezha.js';
document.head.appendChild(script);
默认情况下,以这种方式创建的标签都以异步方式加载,相当于加了
async
标签。
结语:
通过创建对象池管理一组可回收的对象,应用程序可以向这个对象池请求一个对象、设置其属性,使用他,然后
在操作完成后再把它交还给对象池,因为没有对象初始化,垃圾回收探测刽发现兑现更替,因此垃圾回收程序就不会频繁调用了。
let v1 = vectorPool.allocate();
let v2 = vectorPool.allocate();
let v3 = vectorPool.allocate();
v1.x = 10;
v1.y = 5;
v2.x = 11;
v2.y = 12;
addVector(v1,v2,v3);
vectorPool.free(v1);
vectorPool.free(v2);
vectorPool.free(v3);
由于数组是动态可变的,当创建一个大小为100的数组,使用时发现不够大,引擎会删除这个数组,然后创建一个新的,
垃圾回收程序会看到这个删除操作,然后很快的就来收一次垃圾,要避免这种动态分配操作。
JavaScript变量可以保存两种类型的值:原始值和引用值。
typeof
操作费可以确定值的原始类型,instanceof
操作符用于确保值得引用类型任何变量都存在于某个执行上下文中(也称为作用域)。这个上下文(作用域)决定了变量的生命周期,以及它们可以访问代码的哪些部分。
执行上下文可以总结如下:
JavaScript是使用垃圾回收的编程语言,开发者不需要操心内存分配和回收。
与Object的主要差异是,map实例会维护键值对的插入顺序,可以根据插入顺序执行迭代操作。
映射实例可以通过迭代器Iterator
,能以插入顺序生成[key,value]
形式的数组,可以通过entries()
方法,或者Symbol.iterator
属性,它引用entries()
取得这个迭代器。
const map = new Map([
["id","1"],
["name","哪吒"],
["age","18"]
]);
for(let x of map.entries()){
alert(x);
}
//[id,1]
//[name,哪吒]
//[age,18]
for(let x of map[Symbol.iterator]()){
alert(x);
}
//[id,1]
//[name,哪吒]
//[age,18]
map.foreach((val,key) => alert(`${key} -> ${val}`));
//id -> 1
//name -> 哪吒
//age -> 18
for(let key of map.keys()){
}
for(let v of map.values()){
}
给定固定大小内存的情况下,Map一般会比Object多存储50%的键值对。
插入Map一般会稍微快一点。
相差无几。
Map的删除性能完胜Object。
综上四点,选择Map显然是更好地选择。
Set会维护值插入时的顺序,因此支持按顺序迭代。
集合实例可以提供一个迭代器Iterator,能以插入顺序生成集合内容。可以通过values()方法及其别名方法keys(),或者Symbol.iterator属性,他引用values(),取得这个迭代器。
const s = new Set("哪吒","云韵","比比东");
alert(s.keys === s[Symbol.iterator]);//true
alert(s.values === s[Symbol.iterator]);//true
for(let value of s.values()){
}
for(let value of s[Symbol.iterator]){
}
因为values()是默认迭代器,所以可以直接对集合实例使用扩展操作,把集合转为数组:
const s = new Set("哪吒","云韵","比比东");
console.log([...s]);//["哪吒","云韵","比比东"]
生成器是ECMAScript6新增的一个极为灵活的结构,拥有在一个函数块内暂停和恢复代码执行的能力。这种新能力具有较深远的影响,比如,使用生成器可以自定义迭代器和实现协程。
生成器的形式是一个函数,函数名称前面加一个星号*,表示它是一个生成器。只要是可以定义函数的地方,就可以定义生成器。
调用生成器函数会产生一个生成器对象,生成器对象一开始处于暂停执行(suspended)状态。与迭代器相似,生成器对象也实现了Iterator接口,因此具有next()方法。调用这个方法会让生成器开始或恢复执行。
next()方法的返回值类似于迭代器,有一个done属性和一个value属性。函数体为空的生成器函数中间不会停留,调用一次next()就会让生成器到达done:true
状态。
value属性是生成器函数的返回值,默认值为undefined,可以通过生成器函数的返回值指定:
function * generatorFn(){
return '哪吒编程'
}
生成器函数只会在初次调用next()方法之后开始执行。
yield关键字可以让生成器停止和开始执行,也是生成器最有用的地方,生成器函数在遇到yield关键字之前会正常执行。遇到这个关键字之后,执行器会停止,函数作用域的状态会被保留。停止执行的生成器函数只能通过在生成器对象上调用next()方法来恢复执行。
因为生成器对象实现了Iterable接口,而且生成器函数和默认迭代器被调用之后都产生迭代器,所以生成器格外适合作为默认迭代器。
1、使用return()终止生成器
2、使用throw()
throw()方法会在暂停的时候提供一个错误注入到生成器对象中。如果错误未被处理,生成器就会关闭。不过如果生成器函数内部处理了这个错误,生成器就不会关闭,还可以恢复执行。
迭代是一种所有编程语言中都可以看到的模式。ECMASCript正式支持迭代模式并引入两个新的语言特性:迭代器和生成器。
迭代器是一个可以由任意对象实现的接口,支持连续获取对象产出的每一个值。任何实现Iterable
接口的对象都有一个Symbol.iterator
属性,这个属性引用默认迭代器。默认迭代器就像一个迭代器工厂,也就是一个函数,调用之后会产生一个实现Iterator
接口的对象。
迭代器必须通过连续调用next()
方法才能连续获取值,这个方法返回一个IteratorObject
。这个对象包含一个done
属性和一个value
属性。前者时刻一个布尔值,表示十分还有更多值可以访问;后者包含迭代器返回的当前值。这个接口可以通过手动反复调用next()
方法来消费,也可以通过原生消费者,比如for
循环来自动消费。
生成器是一种特殊的函数,调用之后会返回一个生成器对象。生成器对象实现了Iterable
接口,因此可用在任何消费可迭代对象的地方。生成器的独特之处在于支持yield
关键字,这个关键字能够暂停执行生成器函数。使用yield
关键字还可以通过next()
方法接收输入和产生输出。在加上星号之后,yield
关键字可以将跟在后面的可迭代对象序列化为一连串值。
ECMA-262把原型链定义为ECMAScript的主要继承方式。其基本思想就是通过原型继承多个引用类型的属性和方法。
每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型。如果原型是另一个类型的实例呢?那就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数,这样就在实例和原型之间构造了一条原型链。
为了解决原型链包含引用值导致的继承问题,引入了盗用构造函数的概念。基本思路很简单,在子类构造函数中调用父类构造函数,因为毕竟函数就是在特定上下文中执行代码的简单对象,所以可以使用apply()或call()方法,重新创建上下文执行构造函数。
相比于使用原型链,盗用构造函数的一个优点就是可以在子类构造函数中向父类构造函数传参。
组合继承综合了原型链和盗用构造函数,将两者的优点集中了起来,基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。
组合继承也保留了instanceof操作符和isPrototypeOf()方法识别合成对象的能力。
为什么80%的码农做不了架构师?>>>
作者简介:哪吒,CSDN2021博客之星亚军、新星计划导师✌、博客专家
哪吒多年工作总结:Java学习路线总结,搬砖工逆袭Java架构师
关注公众号【哪吒编程】,回复1024,获取Java学习路线思维导图、大厂面试真题、加入万粉计划交流群、一起学习进步
下一篇:Vue基础知识总结 1:Vue入门