你不知道的16个js原生函数和属性的区别

程序员成长指北

共 25912字,需浏览 52分钟

 ·

2021-08-10 06:23


点击上方 程序员成长指北,关注公众号

回复1,加入高级Node交流群

前言

原生内置了很多API, 作用类似,却也有差千差万别,了解其区别,掌握前端基础,是修炼上层,成为前端高级工程师的必备知识,让我们一起来分类归纳,一起成长吧。

上一篇前端基础好文:那些你熟悉而又陌生的函数[2]

属性获取 keysgetOwnPropertyNamesgetOwnPropertySymbols

Object.keys[3]

返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 。

Object.getOwnPropertyNames[4]

返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。

Object.getOwnPropertySymbols[5]

一个给定对象自身的所有 Symbol 属性的数组。

Reflect.ownKeys[6]

返回一个由目标对象自身的属性键组成的数组。
等同于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", {
   enumerablefalse
});

Object.defineProperty(person, symbolSalary, {
   enumerablefalse// 无效的设置 
   value999
});

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.containsNode.compareDocumentPosition

Node.compareDocumentPosition[7]

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

语法 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

  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[8]

返回的是一个布尔值,来表示传入的节点是否为该节点的后代节点

基本等于compareDocumentPosition的 | DOCUMENT_POSITION_CONTAINED_BY | 16 |otherNode被node包含 |

总结

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

取文本 innerTexttextContent

HTMLElement.innerText[9]

解析过程:

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

Node.textContent[10]

解析过程:

  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[11]

  • 对于textcomment, 和 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[12] <input value="1" />
  • HTMLTextAreaElement[13] <textarea value= "你哈" />
  • HTMLButtonElement[14] <button value= "提交" />
  • HTMLDataElement[15] <data value="21053">圣女果</data>
  • HTMLSelectElement[16] <select><option value ="volvo">Volvo</option>
  • HTMLOptionElement[17] <select><option value ="volvo">Volvo</option>
  • HTMLParamElement[18]
<object classid="clsid:F08DF954-8592-11D1-B16A-00C0F0283628" id="Slider1" width="100" height="50">    
     <param name="BorderStyle" value="1" />
</object>
复制代码
  • HTMLProgressElement[19] <progress value="22" max="100"></progress>

总结

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

节点复制 adoptNodeimportNodecloneNode

Document.adoptNode[20]

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

Document.importNode[21]

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

Node.cloneNode[22]

生成节点的一个副本。

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

这里提一个问题:
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[23]

节点的子节点集合,包括元素节点、文本节点还有属性节点

ParentNode.children[24]

返回的只是节点的元素节点集合, 即 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.parentNodeNode.parentElement也是同样的道理。

总结

  1. children只返回元素节点,也就是 nodeType为1的节点
  2. childNodes 返回所有类型的节点

添加节点 appendappendChild

node.appendChild[25]

将一个节点附加到指定父节点的子节点列表的末尾处

ParentNode.append[26]

方法在 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.hiddenDocument.visibilityState

document.hidden[27]

返回布尔值,表示页面是(true)否(false)隐藏。

Document.visibilityState[28]

返回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有相关的事件

函数调用 callapplybind

Function.prototype.call[29]

使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数

Function.prototype.apply[30]

调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数

Function.prototype.bind[31]

方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用

例子

function sum(...args{
    const total = args.reduce((s, cur) => {
        return s + cur;
    }, 0);

    return (this.base || 0) + total;
}

const context = {
    base1000
};

const bindFun = sum.bind(context, 12);

const callResult = sum.call(context, 1234);
const applyResult = sum.apply(context, [1234]);
const bindResult = bindFun(34);


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({ val1 }).bind({ val2 })())  // { 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({ val1 }, 1).bind({ val2 }, 2)())  // 1
复制代码

字符串截取 substrsubstring

String.prototype.substr[32]

返回一个字符串中从指定位置开始到指定字符数的字符

语法:第二参数,是需要截取的长度

str.substr(start[, length])

String.prototype.substring[33]

返回一个字符串在开始索引到结束索引之间的一个子集, 或从开始索引直到字符串的末尾的一个子集。

语法:第二参数,结束索引

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(12))  // 们都
console.log(str.substring(12))  // 们
复制代码

总结

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

具体参见 substr[34]和 substring[35]

遍历 for offor 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, {
    enumerablefalse
});
Object.defineProperty(a, "2", {
    enumerablefalse
});

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\(\)[36]

方法返回自 1970 年 1 月 1 日 00:00:00 (UTC) 到当前时间的毫秒数。

Performance.now[37]

获取当前的时间戳的值(自创建上下文以来经过的时间),其值是一个精确到毫秒的 DOMHighResTimeStamp[38].

<!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精度更高

域名信息 hosthostname

location.host[39]

其包含:主机名,如果 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额外包含端口号

事件注册 onaddEventListener

内联事件

注册事件。

EventTarget.addEventListener[40]

方法将指定的监听器注册到 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[41]

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

按键时间 keypresskeydown

keypress[42]

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

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

keydown[43]

与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属性[44]
keydown,keypress,keyup三者之间的区别[45]


关于本文

作者:云的世界

https://juejin.cn/post/6982742095375597575


如果觉得这篇文章还不错
点击下面卡片关注我
来个【分享、点赞、在看】三连支持一下吧

   “分享、点赞在看” 支持一波 

浏览 33
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报