前言
原生内置了很多API, 作用类似,却也有差千差万别,了解其区别,掌握前端基础,是修炼上层,成为前端高级工程师的必备知识,让我们一起来分类归纳,一起成长吧。
上一篇前端基础好文: 那些你熟悉而又陌生的函数
属性获取 keys
, getOwnPropertyNames
, getOwnPropertySymbols
Object.keys
返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 。
Object.getOwnPropertyNames
返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。
Object.getOwnPropertySymbols
一个给定对象自身的所有 Symbol 属性的数组。
Reflect.ownKeys
返回一个由目标对象自身的属性键组成的数组。
等同于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
例子
const symbolSalary = Symbol.for("salary"); const symbolIsAnimal = Symbol.for("isAnimal"); const symbolSay = Symbol.for("say"); function Person(age, name){ this.age = age; this.name = name; this.walk = function () { console.log("person:walk"); } } // 原型方法 Person.prototype.say = function(words){ console.log("say:", words); } Person.prototype[symbolSay] = function(words){ console.log("symbolSay", words); } // 原型属性 Person.prototype[symbolIsAnimal] = true; Person.prototype.isAnimal = true; const person = new Person(100, "程序员"); person[symbolSalary] = 6000; person["sex"] = "男"; // sex 不可枚举 Object.defineProperty(person, "sex", { enumerable: false }); Object.defineProperty(person, symbolSalary, { enumerable: false, // 无效的设置 value: 999 }); const keys = Object.keys(person); const names = Object.getOwnPropertyNames(person); const symbols = Object.getOwnPropertySymbols(person); const ownKeys = Reflect.ownKeys(person); console.log("keys", keys); // [ 'age', 'name', 'walk' ] console.log("getOwnPropertyNames", names); // [ 'age', 'name', 'walk', 'sex' ] console.log("getOwnPropertySymbols", symbolSalary); // [ Symbol(salary) ] console.log("ownKeys", ownKeys); // [ 'age', 'name', 'walk', 'sex', Symbol(salary) ] console.log("--------") console.log(person.isAnimal); // true console.log(person[symbolIsAnimal]); // true console.log(person[symbolSalary]); // 999 person[symbolSay]("hello world"); // symbolSay hello world person.say("hello world"); // say: hello world person.walk(); // person:walk 复制代码
总结
- Object.keys:则返回的是所有可枚举属性键,也就是属性下的enumerable: true。但不包括Symbol值作为名称的属性键。
- Object.getOwnPropertyNames:返回的是对象所有自己的属性键 ,包括不可枚举属性但不包括Symbol值作为名称的属性键。
- Object.getOwnPropertySymbols: 方法返回一个给定对象自身的所有 Symbol 属性键的数组。
- Reflect.ownKeys: 返回一个由目标对象自身的属性键组成的数组。等同于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。
节点位置关系 Node.contains
, Node.compareDocumentPosition
Node.compareDocumentPosition
比较当前节点与任意文档中的另一个节点的位置关系
语法 compareMask = node.compareDocumentPosition( otherNode )
返回值是一个具有以下值的位掩码:
常量名 | 十进制值 | 含义 |
DOCUMENT_POSITION_DISCONNECTED | 1 | 不在同一文档中 |
DOCUMENT_POSITION_PRECEDING | 2 | otherNode在node之前 |
DOCUMENT_POSITION_FOLLOWING | 4 | otherNode在node之后 |
DOCUMENT_POSITION_CONTAINS | 8 | otherNode包含node |
DOCUMENT_POSITION_CONTAINED_BY | 16 | otherNode被node包含 |
DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | 32 | 待定 |
在一些场景下,可能设置了不止一位比特值。比如 otherNode 在文档中是靠前的且包含了 Node, 那么DOCUMENT_POSITION_CONTAINS 和 DOCUMENT_POSITION_PRECEDING 位都会设置,所以结果会是 0x0A 即十进制下的 10。
看代码:
结果是: 20
- child 在 parent之后,赋值得4
- child 被 parent包含,赋值的16
4 + 16 = 20
<div id="parent"> <div id="child"></div> </div> <script> const pEl = document.getElementById("parent"); const cEl = document.getElementById("child"); console.log(pEl.compareDocumentPosition(cEl)); // 20 </script> 复制代码
Node.contains
返回的是一个布尔值,来表示传入的节点是否为该节点的后代节点
基本等于compareDocumentPosition的 | DOCUMENT_POSITION_CONTAINED_BY | 16 |otherNode被node包含 |
总结
- compareDocumentPosition 返回的是数字,带组合意义的数据,不仅仅可以返回包含,还可以返回在之前之后等信息
- contains 返回的是布尔值,仅仅告诉你是否有包含关系
取文本 innerText
, textContent
HTMLElement.innerText
解析过程:
- 对HTML标签进行解析;
- 对CSS样式进行带限制的解析和渲染;
- 将ASCII实体转换为对应的字符;
- 剔除格式信息(如\t、\r、\n等),将多个连续的空格合并为一个
Node.textContent
解析过程:
- 对HTML标签进行剔除;
- 将ASCII实体转换为相应的字符。
需要注意的是:
- 对HTML标签是剔除不是解析,也不会出现CSS解析和渲染的处理,因此
<br/>
等元素是不生效的。 - 不会剔除格式信息和合并连续的空格,因此\t、\r、\n和连续的空格将生效
例子
<p id="source"> <style> #source { color: red; } </style> Take a look at<br>how this text<br>is interpreted below. <span style="display:none">HIDDEN TEXT</span> </p> <h3>Result of textContent:</h3> <textarea id="textContentOutput" rows="12" cols="50" readonly>...</textarea> <h3>Result of innerText:</h3> <textarea id="innerTextOutput" rows="12" cols="50" readonly>...</textarea> <script> const source = document.getElementById('source'); const textContentOutput = document.getElementById('textContentOutput'); const innerTextOutput = document.getElementById('innerTextOutput'); textContentOutput.innerHTML = source.textContent; innerTextOutput.innerHTML = source.innerText; </script> 复制代码
看看结果:
总结
- innerText是会解析css的,
<br/>
有效,剔除格式信息(如\t、\r、\n等),将多个连续的空格合并为一个。 - textContent是剔除html标签,
<br/>
无效,\t、\r、\n
和连续的空格将生效。
节点取值 value
, nodeValue
Node.nodeValue
- 对于
text
,comment
, 和CDATA
节点来说, nodeValue返回该节点的文本内容. - 对于 attribute 节点来说, 返回该属性的属性值.
对应着下面表格的 nodeType的值 text 3
,4
,8
常量 | nodeType 值 | 描述 |
Node.ELEMENT_NODE | 1 | 一个 元素 节点,例如 和 |
Node.TEXT_NODE | 3 | Element 或者 Attr 中实际的 文字 |
Node.CDATA_SECTION_NODE | 4 | 一个 CDATASection,例如 <!CDATA[[ … ]]>。 |
Node.PROCESSING_INSTRUCTION_NODE | 7 | 一个用于XML文档的 ProcessingInstruction (en-US) ,例如 声明。 |
Node.COMMENT_NODE | 8 | 一个 Comment 节点。 |
Node.DOCUMENT_NODE | 9 | 一个 Document 节点。 |
Node.DOCUMENT_TYPE_NODE | 10 | 描述文档类型的 DocumentType 节点。例如 就是用于 HTML5 的。 |
Node.DOCUMENT_FRAGMENT_NODE | 11 | 一个 DocumentFragment 节点 |
value
特定的一些HTMLElement元素,用value属性获取其值。常见的有value属性的元素如下:
- HTMLInputElement
<input value="1" />
- HTMLTextAreaElement
<textarea value= "你哈" />
- HTMLButtonElement
<button value= "提交" />
- HTMLDataElement
<data value="21053">圣女果</data>
- HTMLSelectElement
<select><option value ="volvo">Volvo</option>
- HTMLOptionElement
<select><option value ="volvo">Volvo</option>
- HTMLParamElement
<object classid="clsid:F08DF954-8592-11D1-B16A-00C0F0283628" id="Slider1" width="100" height="50"> <param name="BorderStyle" value="1" /> </object> 复制代码
- HTMLProgressElement
<progress value="22" max="100"></progress>
总结
- nodeValue 是文本节点,属性节点,注释节点等类型的节点用来取值的方法
- vlaue是特定的元素节点用来取值的方法
节点复制 adoptNode
, importNode
, cloneNode
Document.adoptNode
将外部文档的一个节点拷贝一份,然后可以把这个拷贝的节点插入到当前文档中.
Document.importNode
从其他的document文档中获取一个节点。 该节点以及它的子树上的所有节点都会从原文档删除 , 并且它的ownerDocument 属性会变成当前的document文档。 之后你可以把这个节点插入到当前文档中。
Node.cloneNode
生成节点的一个副本。
实际上 Document
是继承自 Node
, 也具备cloneNode
方法。
这里提一个问题:
Document.adoptNode
与Document.importNode
是操作外部文档的,那么操作所在的文档会有什么效果呢?
Node.cloneNode
有一个boolean
类型的可选参数deep
:
true
: 则该节点的所有后代节点也都会被克隆false
: 则只克隆该节点本身.
注意
- cloneNode
deep
参数在不同版本的浏览器实现中,默认值可能不一样, 所以强烈建议写上值。 - cloneNode 会克隆一个元素节点会拷贝它所有的属性以及属性值,当然也就包括了属性上绑定的事件(比如onclick="alert(1)"),但不会拷贝那些使用addEventListener()方法或者node.onclick = fn这种用JavaScript动态绑定的事件
总结
- adoptNode 从外部文档进行拷贝
- importNode 从外部文档进行拷贝,并从外部文档删除
- cloneNode 从本文档进行复制,有浅复制和深复制
父节点 childNodes
, children
Node.childNodes
节点的子节点集合,包括元素节点、文本节点还有属性节点
ParentNode.children
返回的只是节点的元素节点集合, 即 nodeType
为1的节点。
例子
来实际看一段代码:
<div id="root"> 1 <span>2</span> 3 <!-- <div></div> --> <!CDATA[[ 4 ]]> </div> <script> const rootEl = document.getElementById("root"); console.log(rootEl.children); console.log(rootEl.childNodes); </script> 复制代码
返回结果截图:
Node.parentNode
与Node.parentElement
也是同样的道理。
总结
- children只返回元素节点,也就是 nodeType为1的节点
- childNodes 返回所有类型的节点
添加节点 append
, appendChild
node.appendChild
将一个节点附加到指定父节点的子节点列表的末尾处
ParentNode.append
方法在 ParentNode的最后一个子节点之后插入一组 Node 对象或 DOMString 对象。 被插入的 DOMString 对象等价为 Text 节点.
例子
我们一次append三个节点,其中两个文本节点,一个div节点。
<div id="root"></div> <script> function createEl(type, innerHTML){ const el = document.createElement(type); el.innerHTML = innerHTML; return el; } const rootEl = document.getElementById("root"); rootEl.append("我们", createEl("div", "都是"), "好孩子"); </script> 复制代码
总结
- ParentNode.append()允许追加 DOMString 对象,而 Node.appendChild() 只接受 Node 对象。
- ParentNode.append() 没有返回值,而 Node.appendChild() 返回追加的 Node 对象。
- ParentNode.append() 可以追加多个节点和字符串,而 Node.appendChild() 只能追加一个节点。
简直说, append强大太多了。
文档可见状态 Document.hidden
, Document.visibilityState
document.hidden
返回布尔值,表示页面是(true)否(false)隐藏。
Document.visibilityState
返回document的可见性, 由此可以知道当前文档(即为页面)是在背后, 或是不可见的隐藏的标签页,或者(正在)预渲染.可用的值如下:
- 'visible' : 此时页面内容至少是部分可见. 即此页面在前景标签页中,并且窗口没有最小化.
- 'hidden' : 此时页面对用户不可见. 即文档处于背景标签页或者窗口处于最小化状态,或者操作系统正处于 '锁屏状态' .
- 'prerender' : 页面此时正在渲染中, 因此是不可见的 . 文档只能从此状态开始,永远不能从其他值变为此状态 .注意: 浏览器支持是可选的.
当此属性的值改变时, 会递交 visibilitychange
事件给Document.
例子
我们先输出当前状态,然后点击别的tab,等下再点击回来。
console.log( "visibilityState:", document.visibilityState, " hidden:", document.hidden, ); console.log(""); document.addEventListener("visibilitychange", function () { console.log( "visibilityState:", document.visibilityState, " hidden:", document.hidden ); }); 复制代码
我们日常可以用visibilitychange
来监听当前页面处于隐藏时,去清除定时器或页面中的动画, 停止音乐视频等的播放。
我还想到一个有意思的
- 广告倒计时
你离开后,不算倒计时,会不会被骂死
- 阅读某些协议
你离开后,停止倒计时
总结
- hidden 与 visibilityState 返回值不同,一个是布尔值,一个是字符串
- visibilityState 的状态多一种
prerender
, 其对应的hidden的值是true - visibilityState e有相关的事件
函数调用 call
, apply
, bind
Function.prototype.call
使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数
Function.prototype.apply
调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数
Function.prototype.bind
方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用
例子
function sum(...args) { const total = args.reduce((s, cur) => { return s + cur; }, 0); return (this.base || 0) + total; } const context = { base: 1000 }; const bindFun = sum.bind(context, 1, 2); const callResult = sum.call(context, 1, 2, 3, 4); const applyResult = sum.apply(context, [1, 2, 3, 4]); const bindResult = bindFun(3, 4); console.log("call:", callResult); // 1010 console.log("apply:", applyResult); // 1010 console.log("bind:", bindResult); // 1010 复制代码
总结
相同点,都能改变被调用函数的this指向。
- call: 第二个参数开始,可以接收任意个参数
- apply: 第二个参数,必须是数组或者类数组
- bind: 第二个参数开始,可以接收任意个参数 , 返回的是一个新的函数
注意点:
- bind调用多次,this指向第一次第一个参数
log 调用了两次bind, 第一次bind{ val: 1 }
, 第二次bind{ val: 2 }
, 输出的this是一次bind的上下文
function log() { console.log("this", this); } console.log(log.bind({ val: 1 }).bind({ val: 2 })()) // { val: 1 } 复制代码
再看一段类似的代码:
虽然this的指向不会再变改变,但是参数还是继续接受, arguments 长度为2, 第一次bind的1,第二次bind的 2 , 都照单全收。
function log() { console.log("this", this); // { val: 1 } console.log("arguments", arguments); // { '0': 1, '1': 2 } } console.log(log.bind({ val: 1 }, 1).bind({ val: 2 }, 2)()) // 1 复制代码
字符串截取 substr
, substring
String.prototype.substr
返回一个字符串中从指定位置开始到指定字符数的字符
语法: 第二参数,是需要截取的长度
str.substr(start[, length])
String.prototype.substring
返回一个字符串在开始索引到结束索引之间的一个子集, 或从开始索引直到字符串的末尾的一个子集。
语法: 第二参数,结束索引
str.substring(indexStart[, indexEnd])
例子
提示:
- 两个参数都没设置的时候,效果相同
- 第一个参数是大于等于0的整数,没设置第二参数的时候,效果相同
const str = "我们都是好孩子"; console.log(str.substr()) // 我们都是好孩子 console.log(str.substring()) // 我们都是好孩子 console.log(str.substr(1)) // 们都是好孩子 console.log(str.substring(1)) // 们都是好孩子 console.log(str.substr(-1)) // 子 console.log(str.substring(-1)) // 我们都是好孩子 console.log(str.substr(1, 2)) // 们都 console.log(str.substring(1, 2)) // 们 复制代码
总结
- substr 第二个参数是需要截取的长度
- substring 第二个参数是结束索引值的
- 没指定参数或者第一个参数是大于等于0的整数时,效果相同
- 第一个参数是负数或者第二个参数是负数,处理规则不通
遍历 for of
, for in
for in
获取enumerable:true
的属性键
for of
遍历属性值。不受到enumerable
限制。
例子
- 在数组原型上增加了方法
gogo
,for in
结果中出现了,而for of
结果冲未出现。 - 定义了 属性2不能被遍历,
for in
结果中未出现,而for of
结果中出现了。
// 原型上增加方法 Array.prototype.gogo = function(){ console.log("gogo"); } var a = [1,2,3]; // key值2不可以枚举 Object.defineProperty(a, 2, { enumerable: false }); Object.defineProperty(a, "2", { enumerable: false }); for(let p in a){ // 索引被遍历出来是字符串类型 console.log(p, typeof p); // 0 string; 1 string; gogo string } console.log("---") for(let v of a){ console.log(v); // 1 2 3 } 复制代码
总结
for in
- 获取enumerable:true的属性键。
- 可以遍历对象。
- 可以获取原型上的属性键。
- 数字属性键被遍历出来是字符串。 比如索引值
for of:
- 遍历属性值。不受到enumerable限制。
- 可遍历数组。 一般不可以遍历对象,如果实现了Symbol.iterator,可以遍历。 如Array,Map,Set,String,TypedArray,arguments 对象等等
- 不能获取原型上的值
当前时间 Date.now()
, Performance.now()
Date.now()
方法返回自 1970 年 1 月 1 日 00:00:00 (UTC) 到当前时间的毫秒数。
Performance.now
获取当前的时间戳的值(自创建上下文以来经过的时间),其值是一个精确到毫秒的 DOMHighResTimeStamp.
<!DOCTYPE html> <html lang="en"> <head> <script> console.log("p1", performance.now()) </script> </head> <body> <script> console.log("p2", performance.now()); setTimeout(() => { console.log("p3", performance.now()); }, 1000) </script> </body> </html> 复制代码
总结
Date.now()
的基准是1970 年 1 月 1 日 00:00:00 (UTC)
, 而Performance.now
是上下文创建。Date.now()
返回的是整数,Performance.now
返回的是double类型- 理论上
Performance.now
精度更高
域名信息 host
, hostname
location.host
其包含:主机名,如果 URL 的端口号是非空的,还会跟上一个 ':' ,最后是 URL 的端口号
location.hostname
返回域名
例子
https与http的默认端口号,是不会被 host包含的,看下面的代码
https://developer.mozilla.org:443
的host是 developer.mozilla.org
, 因为443是https的默认端口。
var anchor = document.createElement("a"); anchor.href = "https://developer.mozilla.org:443/en-US/Location.host"; console.log(anchor.host == "developer.mozilla.org:443") // false console.log(anchor.host == "developer.mozilla.org") // true console.log(anchor.hostname == "developer.mozilla.org:443"); // false console.log(anchor.hostname == "developer.mozilla.org"); // true anchor.href = "https://developer.mozilla.org:4097/en-US/Location.host"; console.log(anchor.host == "developer.mozilla.org:4097") // true console.log(anchor.hostname == "developer.mozilla.org") // true 复制代码
总结
- 默认端口下, host等于hostname
- host额外包含端口号
事件注册 on
, addEventListener
内联事件
注册事件。
EventTarget.addEventListener
方法将指定的监听器注册到 EventTarget 上,当该对象触发指定的事件时,指定的回调函数就会被执行。
例子
分别注册两次onclick和click事件,onclick只输出一次,click输出两次.
<button id="btn" >点我</button> <script> const btnEl = document.getElementById("btn"); btnEl.onclick = () => console.log("onclick", 1); btnEl.onclick = () => console.log("onclick", 1); btnEl.addEventListener("click", ()=> console.log("click", 1)); btnEl.addEventListener("click", ()=> console.log("click", 2)); </script> 复制代码
总结
- 内联事件是覆盖型,只能使用事件冒泡,
addEventListener
支持多个事件处理程序,并支持事件捕获。 - 内联事件特定情况下可以被Node.cloneNode复制,addEventListener的不行
更多细节参见 Node.cloneNode - addEventListener为DOM2级事件绑定,onclick为DOM0级事件绑定
按键时间 keypress
, keydown
keypress
当按下产生字符值的键时触发按键事件。产生字符值的键的示例有字母键、数字键和标点键。不产生字符值的键的例子是修改键,如 Alt、 Shift、 Ctrl 或 Meta。
不再推荐使用此功能。尽管一些浏览器可能仍然支持它,但它可能已经从相关的 web 标准中删除
keydown
与keypress事件不同,无论是否生成字符值,所有键都会触发 keydown 事件。
例子
输入123,keydown和keypress的值keyCode一样
总结
- 触发顺序keydown -> keypress
- keydown:当用户按下键盘上的任意键时触发;
- keypress:当用户按下键盘上的字符键时触发; 对中文输入法支持不好,无法响应中文输入
- keypress的keyCode与keydown不是很一致;
异步加载脚本 defer
,async
defer
异步加载,按照加载顺序执行脚本的
async
异步加载,乱序执行脚本。
这个一图胜千文
例子
四个script标签,两个async,两个defer。
代码内容如下:
- async1:
console.log("async1");
- async2:
console.log("async2");
- defer1:
console.log("defer1");
- defer2:
console.log("defer2");
<script src="./async1.js" async ></script> <div> sdfsdfsdfsdfsdfsdfd </div> <script src="./async2.js" async ></script> <script src="./defer1.js" defer ></script> <script src="./defer2.js" defer ></script> 复制代码
从上面可以看出,有时候 async2
会比async1
输出早,defer的输出也可能比async的输出早。 但是defer的输出一定 defer1
然后defer2
总结
- 都是异步加载,defer会按照加载顺序执行,async乱序执行