JS权威指南读书笔记(四)

第十三章 Web浏览器中的JavaScript

在HTML里嵌入JavaScript

在HTML文档里嵌入客户端JavaScript代码有4种方法:

  1. 内联,放置在<script></script>标签对之间。
  2. 放置在由<script>标签的src属性指定的外部文件中。
  3. 放置在HTML事件处理程序中,该事件处理程序由onclick这样的HTML属性值指定。
  4. 放在一个URL里,这个URL使用特殊的"javascript:"协议。

现在大部分项目使用的是第二种方法,也就是通过src的方式引入脚本,使用该方法有以下优点:

  • 可以把大量的JS代码从HTML文件删除,有助于保持内容和行为的分离。
  • 如果多个Web页面共用相同的JS代码,用src属性就可以很方便的引入脚本。而且不需要重复写脚本。
  • 如果一个脚本由多个页面共享,那么只需要下载一次。其他的页面可以在浏览器缓存中读取。
  • 可以使用其他web服务器提供的脚本代码。很常用的做法就是引入第三方库的时候,一般使用百度或者谷歌提供的cdn地址。

URL中的JavaScript

javascript:URL 这个字符串是会被JS解释器运行的JS代码。它被当做单独的一行代码对待,这意味着语句之间必须用分号隔开,而//注释必须换成/* */.
javascript:URL能识别的“资源”是转换成字符串的执行代码的返回值。如果代码返回undefined,那么这个资源就是没有内容的。
javascript:URL可以用在可以使用常规URL的任意地方:比如<a>的href属性,<form>的action属性,甚至window.open()方法的参数。

如果要确保javascript:URL不会覆盖当前文档,可以用void操作符强制函数调用或给表达式赋予undefined值。比如:

<a href="javascript:void;"></a>

第14章 Window对象

浏览器定位和导航

Window对象的location属性引用的是Location对象,表示该窗口中当前显示的文档的URL,Document对象的location属性引用的也是Location对象,所以两者是恒等的(在浏览器内):

window.location === document.location
// 返回true

Document对象也有一个URL属性,是文档首次载入后保存该文档的URL的静态字符串。如果定位到文档中的片段标识符,Location对象会做相应的更新,而document.URL属性却不会改变。

以下是google首页https://www.google.com.hk/?hl=zh-CN&gws_rd=ssl的window.location的具体属性。

DOMStringList {length: 0}
assign:ƒ ()
hash:""
host:"www.google.com.hk"
hostname:"www.google.com.hk"
href:"https://www.google.com.hk/?hl=zh-CN&gws_rd=ssl"
origin:"https://www.google.com.hk"
pathname:"/"
port:""
protocol:"https:"
reload:ƒ reload()
replace:ƒ ()
search:"?hl=zh-CN&gws_rd=ssl"
toString:ƒ toString()
valueOf:ƒ valueOf()
Symbol(Symbol.toPrimitive):undefined

下面说几个比较重要的属性:
Location对象的href属性是一个字符串,后者包含URL的完整文本。Location对象的toString()方法返回href属性的值,因此在会隐式调用toString()的情况下,可以使用location代替location.href。所以以下代码是等价的:

window.location.href = 'https://www.google.com'
window.location = 'https://www.google.com'

search属性返回的是问号之后的URL(包括问号?),一般是用来查询的字符串,最常用的用法就是使用search在不同页面之间传递不敏感的信息,比如id之类的。标准的search参数是形如?key=value&key=value,在笔者开发的项目中,导出文件就是使用search参数通过get请求向后台传递参数,有一个问题就是在实际的生产环境(edas环境)中,传递中文参数会报错,所以前端还需要将value使用encodeURI()进行编码。

window.location.replace()在载入新文档之前会从浏览历史中把当前文档删除。如果检测到用户的浏览器不支持某些新特性,那么就可以使用该方法来载入polyfill版本。

if (!ifSupport) {
    window.location.replace('your page');
}

如果replace()的参数是一个相对URL,那么就会相对于当前页面所在的目录来解析。

执行window.location.reload()会刷新当前页面。

浏览器和屏幕信息

Navigator对象

Window对象的navigator属性引用的是包含浏览器厂商和版本信息的Navigator对象。下面是MacBook Air的主要window.navigator属性:

appCodeName:"Mozilla"
appName:"Netscape"
appVersion:"5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36"
cookieEnabled:true
language:"zh-CN"
languages:(4) ["zh-CN", "zh", "en", "fr"]
maxTouchPoints:0
onLine:true
platform:"MacIntel"
product:"Gecko"
productSub:"20030107"
usb:USB {onconnect: null, ondisconnect: null}
userAgent:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36"
vendor:"Google Inc."

appName:

Web浏览器的全称。在旧版本IE中,就是"Microsoft Internet Explorer"。为了兼容现存的浏览器嗅探代码,其他浏览器(包括新版的IE)通常也取名为"Netscape"。

appVersion:

此属性通常以数字开始,并跟着包含浏览器厂商和版本信息的详细字符串。前面的数字通常是4.0或者5.0,表示是第4代或第5代兼容的浏览器。但是该字符串没有标准的格式,无法来判断浏览器类型。通常使用的是userAgent属性。

userAgent:

浏览器在它的USER-AGENT HTTP头部发送的字符串。这个属性通常包含appVersion中的所有信息,并且常常可能包含其他细节,虽然没有标准格式,但是由于包含绝大部分信息,因此使用该属性判断浏览器类型。
下面是一个判断浏览器类型的通用方法:

function getExplore() {
    var sys = {},
        ua = navigator.userAgent.toLowerCase(),
        s;
    (s = ua.match(/rv:([\d.]+)\) like gecko/)) ? sys.ie = s[1]:
        (s = ua.match(/msie ([\d\.]+)/)) ? sys.ie = s[1] :
        (s = ua.match(/edge\/([\d\.]+)/)) ? sys.edge = s[1] :
        (s = ua.match(/firefox\/([\d\.]+)/)) ? sys.firefox = s[1] :
        (s = ua.match(/(?:opera|opr).([\d\.]+)/)) ? sys.opera = s[1] :
        (s = ua.match(/chrome\/([\d\.]+)/)) ? sys.chrome = s[1] :
        (s = ua.match(/version\/([\d\.]+).*safari/)) ? sys.safari = s[1] : 0;
    // 根据关系进行判断
    if (sys.ie) return ('IE: ' + sys.ie)
    if (sys.edge) return ('EDGE: ' + sys.edge)
    if (sys.firefox) return ('Firefox: ' + sys.firefox)
    if (sys.chrome) return ('Chrome: ' + sys.chrome)
    if (sys.opera) return ('Opera: ' + sys.opera)
    if (sys.safari) return ('Safari: ' + sys.safari)
    return 'Unknown'
}

onLine:
表示浏览器当前是否连接到网络。可以根据这个布尔值状态来进行一些操作。

Screen对象

Window对象的screen属性引用的是Screen对象。以下是chrome 62的window.screen属性的具体属性:

availHeight:797
availLeft:0
availTop:23
availWidth:1440
colorDepth:24
height:900
pixelDepth:24
width:1440

属性width和height指定的是以像素为单位的窗口大小。属性availWidth和availHeight指定的实际可用的显示大小。属性colorDepth指定的是显示的BPP(bits-per-pixel)值,典型的值由16、24和32。
可以用Screen对象来确定Web应用是否运行在一个小屏幕的设备上。

打开和关闭窗口

window.open()用来打开一个新的浏览器窗口。该方法指定的URL到新的或已存在的窗口中,并返回代表那个窗口的Window对象。open()有4个可选的参数
第一个参数是要在新窗口显示的URL。如果省略这个参数,那么空页面的URL为about:blank
第二个参数是新打开窗口的名字。如果指定一个已经存在的窗口的名字,那么会直接使用(跳到)已存在的窗口。否则,会打开新的窗口,并且将指定的名字赋值给新窗口。如果省略此参数,那么新窗口的name为''。(chrome 62)查看新窗口的name可以通过window.name来获取到,该属性是可读可写的,脚本可以随意设置。
第三个参数是一个以逗号分隔的列表,包含大小和各种属性,用来表明新窗口如何打开。比如,要打开允许改变大小的浏览器窗口,并且包含状态栏、工具栏和地址栏,可以这样:

window.open("https://www.google.com.hk","someName","width=400,height=350,status=yes,resizable=yes");

然后就会打开一个指定宽高,但是没有状态栏、工具栏,可以缩放窗口。在chrome 62测试后,不论status值为true或false,新窗口都没有工具栏,不论resizable值为何,都可以改变窗口大小。
第三个参数是非标准的,HTML5规范也主张浏览器忽略。
第四个参数只有在第二个参数命名的是一个存在的窗口时才有用。该参数是一个布尔值,声明了由第一个参数指定的URL是替换掉当前URL(true),还是在新窗口新建一个URL(false),默认值为false。

第15章 脚本化文档

节点列表和HTML集合

getElementsByName()getElementsByTagName都返回NodeList对象,而类似document.imagesdocumemnt.forms的属性为HTMLCollection对象。
NodeList和HTMLCollection对象都是只读的类数组对象。具有leng属性,也具有索引(只读)。所以可以使用for循环进行迭代。
但是他们不是真正的数组,所以不能直接在两个集合上面调用Array的方法,可以通过以下方式来调用:

var tag = document.getElementsByTagName('*') 
var content = Array.prototype.map.call(tag,function(e) {
    return e.innerHTML;
});
//下面的方法是使用了ES6的数组方法from(),该方法将类数组转化为真正的数组。
let array = Array.from(tag);

NodeList和HTMLCollection对象不是历史文档状态的静态快照,而是实时的。如果在一个不存在div元素的文档中插入一个新的<div>元素,那么getElementsByTagName('div')length属性会由0变为1.

通过CSS选择器选取元素

与CSS3选择器的标准化一起的另一个称做“选择器API”的W3C标准定义了获取匹配一个给定选择器的元素的JS方法。该API的关键的Document方法querySelectorAll()。接受包含一个CSS选择器的字符串参数,返回一个表示文档中匹配选择器的所有元素的NodeList对象。

需要注意的是,querySelectorAll()返回的NodeList不是实时的。只包含调用时刻选择器所匹配的元素,不更新后续变化。如果没有匹配的元素,将返回一个空的NodeList对象([])。如果选择器字符串非法,将抛出异常。

类似的还有querySelector()方法。但是与querySelectorAll()不同的是,querySelector()只返回第一个匹配的元素,如果没有匹配的元素那么就返回null。

在CSS中,伪元素匹配文本节点的一部分而不是实际元素。如果和querySelector()querySelectorAll()一起使用它们是不匹配的。而且很多浏览器会拒绝返回":link"等伪类的匹配结果,因为会泄露用户的浏览历史记录。jQuery的$()querySelectorAll()就是等效的。

文档结构和遍历

Document对象、Element对象和Text对象都是Node对象。Node定义了以下重要的属性:

parentNode
该节点的父节点。如果一个节点没有父节点,那么将会返回null。

childNodes
只读的类数组对象,它是该节点的子节点的实时表示

firstChild、lastChild
该节点的子节点中的第一个和最后一个,如果没有则返回null。

nextSibling、previousSibling
该节点的兄弟节点的前一个和下一个。如果没有则返回null。

nodeType
该节点的类型。具体值见下表(参考于MDN)

常量 描述
Node.ELEMENT_NODE 1 一个元素节点,例如<p>和<div>
Node.TEXT_NODE 3 Element或者Attr中实际的文字
Node.PROCESSING_INSTRUCTION_NODE 7 一个用于XML文档的 ProcessingInstruction ,例如<?xml-stylesheet ... ?>声明。
Node.COMMENT_NODE 8 一个 Comment 节点。
Node.DOCUMENT_NODE 9 一个 Document 节点。
Node.DOCUMENT_TYPE_NODE 10 描述文档类型的 DocumentType 节点。例如 <!DOCTYPE html> 就是用于 HTML5 的。
Node.DOCUMENT_FRAGMENT_NODE 11 一个 DocumentFragment 节点

为什么值不是连续的,因为1-12中没有的值已经被废弃了,无需了解。

nodeValue
Text节点或Comment节点的文本内容。

nodeName
元素的标签名,以大写形式表示。

属性

表示HTML文档元素的HTMLElement对象定义了读/写属性,它们映射了元素的HTML属性。HTMLElement定义了通用的HTTP属性(比如id)的属性。特定的Element子类型为其元素定义了特定的属性。例如:

var image = document.getElementById("id");
var imgUrl = image.src;//获取图片的URL
var id = image.id;//获取节点的id属性

HTML属性名不区分大小写,但是JS属性名则大小写敏感。如果属性名包含不止一个单词,那么应该采用驼峰命名来书写js属性名。

有些HTML属性名在JS中是保留字。一般规则是为JS属性名加上前缀html。比如,HTML的for属性(<label>元素)在JS中变为htmlFor属性。HTML的属性class变为className

数据集属性

在HTML5文档中,任意以"data-"为前缀的小写的属性名字都是合法的。HTML5还在Element对象上定义了dataset属性。比如:

var node = document.getElementById('id');
node.dataset.x = 'x';
//node.dataset.x保存的就是data-x属性的值。

node.dataset.nameTest = 'test';
// node.dataset.nameTest驼峰式命名保存的是data-name-test属性的值。

dataset属性是实时、双向接口。

元素的内容

读取Element的innerHTML属性作为字符串标记返回那个元素的内容。通常设置innerHTML效率非常高,甚至在指定的值需要解析时效率也不错。但是,对innerHTML属性用"+="操作符重复追加一小段文本效率低下,因为既要序列化又要解析。

HTML5还标准化了outerHTML属性。outerHTML会返回包含被查询元素的开头和结尾标签所组成的字符串。当设置元素的outerHTML时,元素本身被新的内容替换。只有Element节点定义了outerHTML属性,Document节点没有。

创建、插入和删除节点

创建节点

使用Document对象的createElement()方法,给方法传递元素的标签名,对HTML来说名字不区分大小写。

Text节点用类似的方法创建:

var h = document.createElement("H1");
var t = document.createTextNode("Hello World");
h.appendChild(t);

上例就是使用createTextNode()方法在h1标签里创建了Text文本。

另一种创建新文档节点的方法是复制已存在的节点。每个节点有一个cloneNode()方法来返回该节点的副本。给方法传递参数false或者不传参数,那么该方法只进行浅复制(只复制当前节点);给方法传递参数true能够递归地复制所有的后代节点。举个例子:

//在谷歌的搜索结果页面进行浅复制某元素
document.getElementById('fbar').cloneNode();
//返回结果如下:
<div id="fbar" class="_Zvd" style="left:0;right:0"></div>
插入节点

一旦创建了一个新的节点,就可以使用Node的方法appendChild()insertBefore()将它插入到文档中。
appendChild()是在需要插入的Element节点上调用,该方法的参数是需要插入的元素。
insertBefore()接受两个参数。第一个参数就是待插入的节点,第二个参数是已存在的节点,第一参数将插入到第二参数的前面。该方法应该是在新节点的父节点上调用,第二参数必须是该父节点的子节点。如果传递null作为第二参数,那么行为就跟appendChild()类似。举个例子

// Create a new, plain <span> element
var sp1 = document.createElement("span");

// Get a reference to the element, before we want to insert the element
var sp2 = document.getElementById("childElement");
// Get a reference to the parent element
var parentDiv = sp2.parentNode;

// Insert the new element into the DOM before sp2
parentDiv.insertBefore(sp1, sp2);

需要注意的是,如果调用上述两个方法将已存在文档中的一个节点再次插入,那么该节点将自动从当前的位置删除并在新的位置重新插入。也就是说,没有必要显示的进行删除节点。

删除和替换节点

removeChild()方法从文档树中删除一个节点。同样的,该方法是在参数的父节点上调用。参数就是需要删除的子节点。所以可以这么删除一个节点:

var n = document.getElementById('childrenId');
n.parentNode.removeChild(n);

replaceChild()方法删除一个节点并用一个新的节点取代。同样的,该方法需要在参数的父节点上调用。该方法具有两个参数,第一个参数是新节点,第二个参数是需要代替的节点。比如:

// 用一个文本字符串来替换节点n
n.parentNode.replaceChild(document.createTextNode("Hello World"),n);
使用DocumentFragment

使用DocumentFragment是一种特殊的Node,作为其他节点的一个临时的容器。

var frag = document.createDocumentFragment();

DocumentFragment是独立的,而不是任何其他文档的一部分。它的parentNode总是null。但是可以有任意多的子节点。
DocumentFragment 是DOM节点。它们不是主DOM树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到DOM树。在DOM树中,文档片段被其所有的子元素所代替。

因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(reflow)(对元素位置和几何上的计算)。因此,使用文档片段document fragments 通常会起到优化性能的作用。举个例子:

// assuming it exists (ul element)
let ul = document.getElementsByTagName("ul")[0],
    docfrag = document.createDocumentFragment();

const browserList = [
    "Internet Explorer", 
    "Mozilla Firefox", 
    "Safari", 
    "Chrome", 
    "Opera"
];

browserList.forEach((e) => {
    let li = document.createElement("li");
    // 插入纯文本
    li.textContent = e;
    // 将遍历的li插入文档片段中
    docfrag.appendChild(li);
});
//遍历完后,将文档片段插入到ul中,文档片段被子元素代替
ul.appendChild(docfrag);
可编辑的内容

有两种方法来启用编辑功能。其一,设置任何标签的HTML contenteditable属性;其二,设置对应元素的JavaScript contenteditable属性。浏览器可能为表单字段和contenteditable元素支持自动拼写检查。在支持该功能的浏览器中,检查可能默认开启或者关闭。为元素添加spellcheck属性显示开启拼写检查,spellcheck=false显示关闭。

var node = document.getElementById("editor");
node.contentEditable = "true";//通过JS开启编辑

将Document对象的designMode属性设置为字符串"on"使得整个文档可编辑。designMode属性没有对应的HTML属性。举个例子:

document.designMode = "on";//对整个文档开启编辑

所有现代浏览器都支持contenteditabledesignMode属性。但是它们是不太兼容的。所有浏览器都允许插入与删除文本并用鼠标和键盘移动光标。
浏览器定义了很多文本编辑命令,大部分没有快捷键。可以使用Document对象的execCommand()方法。需要注意的是,这是Document的方法,而不是设置了contenteditable属性的元素的方法。如果文档中有多个可编辑的元素,命令将自动应用到选区或插入光标所在的那个元素上。具体的参数可参考这里

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • 个人博客:https://yeaseonzhang.github.io 花了半个多月的时间,终于又把“JS红宝书”...
    Yeaseon阅读 11,375评论 9 52
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 一、JS前言 (1)认识JS 也许你已经了解HTML标记(也称为结构),知道了CSS样式(也称为表示),会使用HT...
    凛0_0阅读 2,721评论 0 8
  • 我是个侦探小说迷,最喜欢的作者是阿加莎,偶像是福尔摩斯。 没事就喜欢研究各种奇奇怪怪的谋杀事件,觉得以自己的聪明才...
    酸菜面阅读 7,310评论 3 4
  • 小瓜今天回家乡。 往年她都是年三十才回家,今年提前了两天。 她想回去帮他老妈整理屋子,不知道她老妈会不会有机会给她...
    果123阅读 138评论 0 0