阅读 4519

这16种原生函数和属性的区别,你真的知道吗? 精心收集,高级前端必备知识,快快打包带走

本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

前言

原生内置了很多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
复制代码

总结

  1. Object.keys:则返回的是所有可枚举属性键,也就是属性下的enumerable: true。但不包括Symbol值作为名称的属性键。

  2. Object.getOwnPropertyNames:返回的是对象所有自己的属性键 ,包括不可枚举属性但不包括Symbol值作为名称的属性键。

  3. Object.getOwnPropertySymbols: 方法返回一个给定对象自身的所有 Symbol 属性键的数组。

  4. Reflect.ownKeys: 返回一个由目标对象自身的属性键组成的数组。等同于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。

节点位置关系 Node.contains, Node.compareDocumentPosition

Node.compareDocumentPosition

比较当前节点与任意文档中的另一个节点的位置关系

语法 compareMask = node.compareDocumentPosition( otherNode )

返回值是一个具有以下值的位掩码:

常量名十进制值含义
DOCUMENT_POSITION_DISCONNECTED1不在同一文档中
DOCUMENT_POSITION_PRECEDING2otherNode在node之前
DOCUMENT_POSITION_FOLLOWING4otherNode在node之后
DOCUMENT_POSITION_CONTAINS8otherNode包含node
DOCUMENT_POSITION_CONTAINED_BY16otherNode被node包含
DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC32待定

在一些场景下,可能设置了不止一位比特值。比如 otherNode 在文档中是靠前的且包含了 Node, 那么DOCUMENT_POSITION_CONTAINS 和 DOCUMENT_POSITION_PRECEDING 位都会设置,所以结果会是 0x0A 即十进制下的 10。

看代码:
结果是: 20

  1. child 在 parent之后,赋值得4
  2. 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包含 |

总结

  1. compareDocumentPosition 返回的是数字,带组合意义的数据,不仅仅可以返回包含,还可以返回在之前之后等信息
  2. contains 返回的是布尔值,仅仅告诉你是否有包含关系

取文本 innerText, textContent

HTMLElement.innerText

解析过程:

  1. 对HTML标签进行解析;
  2. 对CSS样式进行带限制的解析和渲染;
  3. 将ASCII实体转换为对应的字符;
  4. 剔除格式信息(如\t、\r、\n等),将多个连续的空格合并为一个

Node.textContent

解析过程:

  1. 对HTML标签进行剔除
  2. 将ASCII实体转换为相应的字符。

需要注意的是:

  1. 对HTML标签是剔除不是解析,也不会出现CSS解析和渲染的处理,因此<br/>等元素是不生效的。
  2. 不会剔除格式信息和合并连续的空格,因此\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>
复制代码

看看结果:

image.png

总结

  1. innerText是会解析css的,<br/>有效,剔除格式信息(如\t、\r、\n等),将多个连续的空格合并为一个。
  2. textContent是剔除html标签,<br/>无效,\t、\r、\n和连续的空格将生效。

节点取值 value , nodeValue

Node.nodeValue

  • 对于text, comment, 和 CDATA 节点来说, nodeValue返回该节点的文本内容.
  • 对于 attribute 节点来说, 返回该属性的属性值.

对应着下面表格的 nodeType的值 text 3,4,8

常量nodeType 值描述
Node.ELEMENT_NODE1一个 元素 节点,例如

Node.TEXT_NODE3Element 或者 Attr 中实际的 文字
Node.CDATA_SECTION_NODE4一个 CDATASection,例如 <!CDATA[[ … ]]>。
Node.PROCESSING_INSTRUCTION_NODE7一个用于XML文档的 ProcessingInstruction (en-US) ,例如 声明。
Node.COMMENT_NODE8一个 Comment 节点。
Node.DOCUMENT_NODE9一个 Document 节点。
Node.DOCUMENT_TYPE_NODE10描述文档类型的 DocumentType 节点。例如 就是用于 HTML5 的。
Node.DOCUMENT_FRAGMENT_NODE11一个 DocumentFragment 节点

value

特定的一些HTMLElement元素,用value属性获取其值。常见的有value属性的元素如下:

<object classid="clsid:F08DF954-8592-11D1-B16A-00C0F0283628" id="Slider1" width="100" height="50">    
     <param name="BorderStyle" value="1" />
</object>
复制代码

总结

  1. nodeValue 是文本节点,属性节点,注释节点等类型的节点用来取值的方法
  2. vlaue是特定的元素节点用来取值的方法

节点复制 adoptNode, importNode, cloneNode

Document.adoptNode

将外部文档的一个节点拷贝一份,然后可以把这个拷贝的节点插入到当前文档中.

Document.importNode

从其他的document文档中获取一个节点。 该节点以及它的子树上的所有节点都会从原文档删除 , 并且它的ownerDocument 属性会变成当前的document文档。 之后你可以把这个节点插入到当前文档中。

Node.cloneNode

生成节点的一个副本。

实际上 Document 是继承自 Node, 也具备cloneNode方法。 image.png

这里提一个问题:
Document.adoptNodeDocument.importNode是操作外部文档的,那么操作所在的文档会有什么效果呢?

Node.cloneNode 有一个boolean类型的可选参数deep

  • true: 则该节点的所有后代节点也都会被克隆
  • false: 则只克隆该节点本身.

注意

  1. cloneNode deep参数在不同版本的浏览器实现中,默认值可能不一样, 所以强烈建议写上值。
  2. cloneNode 会克隆一个元素节点会拷贝它所有的属性以及属性值,当然也就包括了属性上绑定的事件(比如onclick="alert(1)"),但不会拷贝那些使用addEventListener()方法或者node.onclick = fn这种用JavaScript动态绑定的事件

总结

  1. adoptNode 从外部文档进行拷贝
  2. importNode 从外部文档进行拷贝,并从外部文档删除
  3. 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>
复制代码

返回结果截图: image.png

Node.parentNodeNode.parentElement也是同样的道理。

总结

  1. children只返回元素节点,也就是 nodeType为1的节点
  2. 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>
复制代码

image.png

总结

  • 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
        );
      });
复制代码

image.png

我们日常可以用visibilitychange来监听当前页面处于隐藏时,去清除定时器或页面中的动画, 停止音乐视频等的播放。

我还想到一个有意思的

  1. 广告倒计时

你离开后,不算倒计时,会不会被骂死

  1. 阅读某些协议

你离开后,停止倒计时

总结

  1. hidden 与 visibilityState 返回值不同,一个是布尔值,一个是字符串
  2. visibilityState 的状态多一种 prerender, 其对应的hidden的值是true
  3. 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指向。

  1. call: 第二个参数开始,可以接收任意个参数
  2. apply: 第二个参数,必须是数组或者类数组
  3. bind: 第二个参数开始,可以接收任意个参数 , 返回的是一个新的函数

注意点:

  1. 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])

例子

提示:

  1. 两个参数都没设置的时候,效果相同
  2. 第一个参数是大于等于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))  // 们
复制代码

总结

  1. substr 第二个参数是需要截取的长度
  2. substring 第二个参数是结束索引值的
  3. 没指定参数或者第一个参数是大于等于0的整数时,效果相同
  4. 第一个参数是负数或者第二个参数是负数,处理规则不通

具体参见 substrsubstring

遍历 for of, for in

for in

获取enumerable:true的属性键

for of

遍历属性值。不受到enumerable限制。

例子

  1. 在数组原型上增加了方法gogo, for in结果中出现了,而 for of结果冲未出现。
  2. 定义了 属性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

  1. 获取enumerable:true的属性键。
  2. 可以遍历对象。
  3. 可以获取原型上的属性键。
  4. 数字属性键被遍历出来是字符串。 比如索引值

for of:

  1. 遍历属性值。不受到enumerable限制。
  2. 可遍历数组。 一般不可以遍历对象,如果实现了Symbol.iterator,可以遍历。 如Array,Map,Set,String,TypedArray,arguments 对象等等
  3. 不能获取原型上的值

当前时间 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>
复制代码

image.png

总结

  1. Date.now()的基准是 1970 年 1 月 1 日 00:00:00 (UTC), 而Performance.now是上下文创建。
  2. Date.now()返回的是整数,Performance.now返回的是double类型
  3. 理论上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
复制代码

总结

  1. 默认端口下, host等于hostname
  2. 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>
复制代码

image.png

总结

  1. 内联事件是覆盖型,只能使用事件冒泡,addEventListener支持多个事件处理程序,并支持事件捕获。

  2. 内联事件特定情况下可以被Node.cloneNode复制,addEventListener的不行
    更多细节参见 Node.cloneNode

  3. addEventListener为DOM2级事件绑定,onclick为DOM0级事件绑定

按键时间 keypress, keydown

keypress

当按下产生字符值的键时触发按键事件。产生字符值的键的示例有字母键、数字键和标点键。不产生字符值的键的例子是修改键,如 Alt、 Shift、 Ctrl 或 Meta。

不再推荐使用此功能。尽管一些浏览器可能仍然支持它,但它可能已经从相关的 web 标准中删除

keydown

与keypress事件不同,无论是否生成字符值,所有键都会触发 keydown 事件。

例子

输入123,keydown和keypress的值keyCode一样

image.png

总结

  1. 触发顺序keydown -> keypress
  2. keydown:当用户按下键盘上的任意键时触发;
  3. keypress:当用户按下键盘上的字符键时触发; 对中文输入法支持不好,无法响应中文输入
  4. keypress的keyCode与keydown不是很一致;

异步加载脚本 defer,async

defer

异步加载,按照加载顺序执行脚本的

async

异步加载,乱序执行脚本。

这个一图胜千文

image.png

例子

四个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>
复制代码

image.png

image.png

从上面可以看出,有时候 async2会比async1输出早,defer的输出也可能比async的输出早。 但是defer的输出一定 defer1然后defer2

总结

  1. 都是异步加载,defer会按照加载顺序执行,async乱序执行

JS魔法堂:被玩坏的innerHTML、innerText、textContent和value属性
keydown,keypress,keyup三者之间的区别

文章分类
前端
文章标签