整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

第42节 Element元素节点-Javascrip

第42节 Element元素节点-Javascript-王唯

内容是《Web前端开发之Javascript视频》的课件,请配合大师哥《Javascript》视频课程学习。

Element类型用于表示XML或HTML元素,提供了对元素标签名、子节点及特性的访问;

Element类继承自Node接口;它继承了Node接口中的所有属性和方法,比如parentNode、childNode等;同时,它在Node接口的基础上扩展了自己的属性和方法;

Element类型的特征:

  • nodeType值为1;
  • nodeName的值为元素的标签名;
  • nodeValue的值为null;
  • parentNode可能是Document或Element;
  • 其子节点可能是Element、Text,Comment、ProcessingInstruction、CATASection或EntityReference;
var mydiv=document.getElementById("mydiv");
console.log(mydiv.nodeType);  // 1
console.log(mydiv.nodeName);  // DIV
console.log(mydiv.nodeValue);  // null
console.log(mydiv.parentNode);  // <body>
console.log(mydiv.childNodes);  // NodeList

Element的属性:

id属性:表示元素的标识符,与全局属性id对应;

var mydiv=document.getElementById("mydiv");
console.log(mydiv.id);
mydiv.id="yourdiv";
console.log(mydiv.id);
var yourdiv=document.getElementById("yourdiv");
console.log(yourdiv.id);

tagName属性:与nodeName属性一样,可以返回元素的标签名,在HTML文档中返回大写,在XML中返回原生,因此在使用tagName时,最后使用toLowerCase()转换;

var mydiv=document.getElementById("mydiv");
console.log(mydiv.tagName);  // DIV
if(mydiv.tagName.toLowerCase()=="div"){
    //...
}

一般使用tagName,因为从字义上理解更加清晰;

Element的子类型HTMLElement:

HTMLElement类型直接继承自Element,因此,HTML元素即属于HTMLElement类也属于Element类;

所有HTML元素都由HTMLElement类型或其子类型表示,比如:HTMLDIVElement,就是具体的div元素的类型;

HTMLElement类型在Element类的基础上,并添加了一些属性,这些属性分别对应于每个HTML元素中都存在的标准特性:id、title、lang、dir、className;

这些属性都是可读可写的,并且也是动态的;

<div id="mydiv" name="mydiv" title="DIV" lang="en" dir="ltr" class="divclass">
// …
<script>
var mydiv=document.getElementById("mydiv");
console.log(mydiv.id);
console.log(mydiv.title);
console.log(mydiv.lang);
console.log(mydiv.dir);
console.log(mydiv.className);
mydiv.id="yourdiv";
mydiv.title="你的DIV";
mydiv.lang="fr";
mydiv.dir="rtl";
mydiv.className="reddiv";
</script>

Element特性(属性):

每个元素都有若干个特性,这些特性的用途是给出相应元素或其内容的附加信息;DOM为Element对象定义了一些API来获取或设置这些XML或HTML属性(特性);

操作特性主要有三个方法:

getAttribute()、setAttribute()、removeAttribute();这些方法可以针对任何特性使用,包括那些以HTMLElement类型属性的形式定义的特性;

getAttribute(attributeName)方法:

返回元素上一个指定的特性值,如果指定的特性不存在,则返回null或 "";特性的名称不区分大小写;

如果取得class,需要传入class,而不是className;

var mydiv=document.getElementById("mydiv");
console.log(mydiv.getAttribute("id"));  // mydiv
console.log(mydiv.getAttribute("class"));  // divclass
console.log(mydiv.getAttribute("title"));  // null

也可以取得自定义特性(即不是HTML元素的标准特性),但要注意,根据HTML5的规范,自定义特性应该加上data-前缀以便验证;

console.log(mydiv.getAttribute("custom"));  // customvalue
console.log(mydiv.getAttribute("data-name"));  // wangwei

任何元素的所有特性,都可以通过DOM元素本身的属性来访问,不过,只有公认的(非自定义)特性才会以属性的形式添加到DOM对象中;但IE可以为自定义特性创建属性;

console.log(mydiv.id);
console.log(mydiv.className);
console.log(mydiv.myname);  // undefined
console.log(mydiv.align);  // left,认为align是公认的
var img=document.getElementById("myimg");
var imgurl=img.src;
console.log(img.id==="myimg");
var f=document.forms[0];
f.action="https://www.zeronetwork.cn/do.php";
f.method="POST";

HTML属性名不区分大小写,但Javascript属性名则大小写敏感;从HTML属性名转换到Javascript属性名应该采用小写,但是如果属性名包含不止一个单词,则采用小驼峰式,如:defaultCheded和tabIndex;

有些HTML属性名在Javascript中是保留字,对于这些属性,一般的规则是为该属性名加前缀”html”,如,HTML的for属性在Javascript中变为htmlFor属性,class属性比较特殊,它在Javascript中变成className属性;

表示HTML属性的值通常是字符串,但当属性为布尔值或数值时,Javascript中对应的属性也是布尔值或数值,而不是字符串;

<label id="lbInput" for="txtInput">文本框:</label>
<input id="txtInput" tabindex="2" type="text" readonly />
<script>
var txtInput=document.getElementById("txtInput");
console.log(txtInput.tabIndex);  // 1
console.log(txtInput.readOnly);  // true
var lbInput=document.getElementById("lbInput");
console.log(lbInput.htmlFor);  // txtInput
</script>

style和事件处理程序特性:

style:在通过getAttribute()访问时,返回的style特性值中包含的是CSS文本;而通过属性访问它会返回一个CSSStyleDeclaration对象(由于style属性是用于以编程方式访问元素样式的对象,因此并没有直接映射到style特性,有关CSS编程,后面我们会讲到);

事件处理程序特性,类似于onclick等这样的事件处理程序,当在特性中使用时,onclick中包含的就是JS代码,使用getAttribute()会返回相应的代码的字符串,但在访问onclick属性时,会返回一个Javascript函数;

var mydiv=document.getElementById("mydiv");
console.log(mydiv.getAttribute("style")); // font-size: 14px;color:red;
console.log(mydiv.style); // CSSStyleDeclaration or CSS2Properties
console.log(mydiv.getAttribute("onclick"));  // alert('zeronetwork');
console.log(mydiv.onclick); // function onclick(event)

setAttribute(name, value)方法:

设置指定元素上的某个特性值,如果特性已经存在,则更新该值,否则,使用指定的名称和值添加一个新的特性;

该接受两个参数:要设置的特性名和值;

此方法可以操作HTML特性也可以操作自定义特性;

var mydiv=document.getElementById("mydiv");
mydiv.setAttribute("id","outerdiv");
mydiv.setAttribute("class","outerdivclass");
mydiv.setAttribute("title","mydiv title");
mydiv.setAttribute("style","border-bottom:1px solid;color:purple;");
mydiv.setAttribute("custom","custom value");
console.log(mydiv.title);  // mydiv title
console.log(mydiv.custom);  // undefined

通过该方法设置的特性名会被统一转换成小写形式,即“ID”最终会变成“id”;

mydiv.setAttribute("ID","myID");  // id
mydiv.setAttribute("CID","customID");  // cid

布尔特性只要出现在元素上就会被认为是 true,无论它的值是什么;一般来说,应该将 value 设置为空字符串,也有人使用这个属性的名称作为值,虽然不会出现什么问题,但不规范的;

var txtInput=document.getElementById("txtInput");
txtInput.setAttribute("readonly",true);// 会渲染成readonly="true"
txtInput.setAttribute("readonly",""); // 渲染成readonly
console.log(txtInput.readOnly);  // true

因为所有特性都是属性,所以直接给属性赋值可以设置特性的值,但如果添加的是一个自定义的属性,该属性不会自动成为元素的特性;

mydiv.title="mydiv title";
mydiv.style="border-bottom:1px solid;color:purple;";
mydiv.custom="custom value";  // html中并没有渲染custom
console.log(mydiv.title);  // mydiv title
console.log(mydiv.custom);  // custom value
console.log(mydiv.getAttribute("title")); // mydiv title
console.log(mydiv.getAttribute("custom")); // null

可以通过setAttribute()方法设置class,但不能通过属性设置class,因为class是关键字,需要className进行属性设置;

var mydiv=document.getElementById("mydiv");
mydiv.setAttribute("class","att_class");
// mydiv.class="att_class";  // 无效,class是保留字
mydiv.className="att_class";
console.log(mydiv.class);  // undefined
console.log(mydiv.className);  // att_class

removeAttribute(attrName)方法:

用于从指定的元素彻底删除元素的特性;

此方法不仅会清除特性的值,而且也会从元素中完全删除特性;

此方法并不常用,但在序列化DOM元素时,可以通过它来确切地指定要包含哪些特性;

var mydiv=document.getElementById("mydiv");
mydiv.removeAttribute("class");
mydiv.removeAttribute("style");
mydiv.setAttribute("custom","custom_value");
mydiv.removeAttribute("custom");
mydiv.title="mydiv title";
mydiv.removeAttribute("title");

hasAttribute(attrName)方法和hasAttributes()方法:

用于检测特性是否存在;其中hasAttribute()需要一个特性参数,判断该元素是否包含有指定的特性,而hasAttributes()检测的是否有特性,具体是什么特性,则不是它所关心的了;

var mydiv=document.getElementById("mydiv");
console.log(mydiv.hasAttribute("title"));
if(!mydiv.hasAttribute("align"))
    mydiv.setAttribute("align","center");
console.log(mydiv.hasAttributes());  // true

当属性为布尔值时,hasAttribute()方法特别有用,比如HTML表单的disabled属性,只要判断它有没有这个属性即可,不用管它的值;

attributes属性:

返回该元素所有属性节点的一个实时集合,该集合是一个NamedNodeMap对象,是一个只读的类数组对象,只有Element类型拥有;该属性与NodeList类似,也是一个动态的集合;也可以使用索引方式访问,并且可以枚举;

元素的每个特性都由一个Attr节点表示,Attr对象是一个特殊的Node,不会像普通的Node一样去使用;Attr的name和value属性返回该属性的名字和值;

每个Attr节点都保存在NamedNodeMap对象中;此节点都有nodeName、nodeValue等属性,nodeName就是特性的名称,nodeValue就是特性的值;

var mydiv=document.getElementById("mydiv");
console.log(mydiv.attributes);
console.log(mydiv.attributes[1]);
console.log(mydiv.attributes.title);
console.log(mydiv.attributes.custom);
mydiv.attributes.title="wangwei";  // 无效
console.log(mydiv.attributes[1].nodeType);  // ATTRIBUTE_NODE
console.log(mydiv.attributes[1].nodeName);
console.log(mydiv.attributes[1].nodeValue);

NamedNodeMap对象:

表示一个无顺序的属性节点 Attr 对象的集合;其是类数组对象,同时也是动态的;

属性和方法:

length属性:返回映射(map)中对象的数量;

getNamedItem(name):返回给定名称name的属性节点;

item(pos):返回位于数字pos位置处的节点;(注:各个浏览器会返回不同的顺序);

setNamedItem(node):向列表中添加或替换特性节点;

removeNamedItem(name):从列表中移除特性为name的节点;与removeAttribute()相同,但其会返回被删除的特性(Attr)节点;

可以通过attributes属性使用方括号直接访问特性;

var mydiv=document.getElementById("mydiv");
console.log(mydiv.attributes);
console.log(mydiv.attributes.item(1));
console.log(mydiv.attributes.getNamedItem("name"));
console.log(mydiv.attributes[1]);
console.log(mydiv.attributes["name"]);
mydiv.attributes["id"].nodeValue="newID";
mydiv.attributes.getNamedItem("name").nodeValue="newName";
var deleteStyle=mydiv.attributes.removeNamedItem("style");
console.log(deleteStyle);
var yourdiv=document.getElementById("yourdiv");
yourdiv.attributes.setNamedItem(deleteStyle);
var attr=document.createAttribute("dir");
attr.nodeValue="ltr";
mydiv.attributes.setNamedItem(attr);

使用attributes属性较麻烦,因此使用getAttribute()、removeAttribute()和removeAttribute()方法比较常用;但在遍历元素的特性时,attributes属性比较方便;

遍历attributes属性:

在需要将DOM结构序列化为XML或HTML字符串时,多数都会涉及遍历元素特性;

// 迭代元素的所有特性,构造成name=”value” name=”value”这样的字符串格式
var mydiv=document.getElementById("mydiv");
function outputAttributes(element){
    var pairs=new Array();
    for(var attr in element.attributes){
        if(element.attributes[attr] instanceof Attr){
            // console.log(attr + element.attributes[attr]);
            var attrName=element.attributes[attr].nodeName;
            var attrValue=element.attributes[attr].nodeValue;
            // console.log(attrName);
            pairs.push(attrName + "=\"" + attrValue + "\"");
        }
    }
    // 或者使用for循环
    // for(var i=0,len=element.attributes.length; i<len; i++){
    //     var attrName=element.attributes[i].nodeName;
    //     var attrValue=element.attributes[i].nodeValue;
    //     // console.log(attrName);
    //     pairs.push(attrName + "=\"" + attrValue + "\"");
    // }
    return pairs.join(" ");
}
console.log(outputAttributes(mydiv));

创建Element元素:

document.createElement(tagName)方法:用于创建一个由标签名称tagName指定的HTML元素,如果用户代理无法识别tagName,则会生成一个未知 HTML 元素;

该方法只接受一个参数,即要创建元素的标签名;此标签名在HTML中不区分大小写,在XML(包括XHTML)中,是区分大小写的;

在创建新元素的同时,也为新元素设置了ownerDocument属性;同时还可以操作元素的特性,为它添加子节点,以及执行其他操作;

新创建的元素,必须添加到文档树中,才能显示出来,可以利用appendChild,insertBefore()或replaceChild()方法;

var div=document.createElement("div");
div.innerHTML="<h2>零点程序员</h2>";
div.id="outerDiv";
div.className="outerDiv";
div.setAttribute("style","color:green;");
console.log(div.ownerDocument);
document.body.appendChild(div);
var h3=document.createElement("h3");
h3.setAttribute("onclick","alert('this is zeronetwork');");
h3.innerText="zeronetwork";
div.insertBefore(h3, null);

Element的子节点:

元素可以有任意数目的子节点和后代节点,这些子节点可能是元素、文本、注释处处理指令;但HTML中的空白也会被解析为文本节点;因此在执行某项操作时,要先检查一下nodeType属性;

<!-- 结构 -->
<ul id="myList">
    <li>HTML</li>
    <li>CSS</li>
    <li>Javascript</li>
</ul>
<!-- 或者删除空白 -->
<ul id="myList"><li>HTML</li><li>CSS</li><li>Javascript</li></ul>
// js代码
var myList=document.getElementById("myList");
console.log(myList.childNodes.length);
for(var i=0,len=myList.childNodes.length; i<len; i++){
    if(myList.childNodes[i].nodeType==Node.ELEMENT_NODE)
        console.log(myList.childNodes[i].nodeName);
}
// 或者
// for(var n in myList.childNodes){
//     if(myList.childNodes[n].nodeType && myList.childNodes[n].nodeType==Node.ELEMENT_NODE)
//         console.log(myList.childNodes[n].nodeName);
// }

元素节点也支持getElementsByTagName() 方法,可以通过它获得某个特定的标签名的子节点或后代节点;

var myList=document.getElementById("myList");
var lis=myList.getElementsByTagName("li");
console.log(lis);  // dom.html:23 HTMLCollection(3)

自定义Element的方法:

Element和HTMLDocument等类型都像String和Array一样是类,它们不是构造函数,但它们有原型对象,可以自定义方法扩展它;

Element.prototype.next=function(){
    if(this.nextElementSibling) return this.nextElementSibling;
    var sib=this.nextSibling;
    while(sib && sib.nodeType !==1) sib=sib.nextSibling;
    return sib;
}
console.log(document.getElementById("mydiv").next());

Web前端开发之Javascript-零点程序员-王唯

、深浅拷贝的区别有哪些?

要说 js 的深浅拷贝,就不得不提 js 的两大数据类型:基本数据类型和引用类型。基本数据类型的变量名和值都存储在栈中,对于引用类型的变量名存储在栈中,而值存储在堆中。由于存储方式不同,所以导致了他们复制的时候方式不同。

浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精准拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另外一个对象。

深拷贝是将一个对象从内存中完整的拷贝一份出来,从内存堆中存放一个新的对象。这是两个对象,所以修改其中一个,另外一个不会受影响。

深浅拷贝主要针对的是引用类型,简单数据类型不受影响。

相关笔试题

var person={
 name:"前端人",
 hobby:['学习','敲代码','潜水']
}
function copy(source){
 var newObj=new Object()
 for(var i in source){
  if(source.hasOwnProperty(i)){
   newObj[i]=source[i]
   }
  }
 return newObj
}
var p1=copy(person);
p1.name="Web Person"
console.log(person.name)
console.log(p1.name)
p1.hobby=["内卷"]
console.info(person.hobby)
console.info(p1.hobby)
/*运行结果:
前端人
 Web Person
["学习", "敲代码", "潜水"]
["内卷"]
*/

2、js 数据类型有哪些?

js 数据类型一共有 8 种,分为两大类:基本类型和引用类型。

它们的数据类型分别为:

基本类型:string、number、boolean、null、undefined、symbol、bigint

引用类型:object

相关面试题

// 注意:其他类型与数值进行相加时,其他类型的转为 number 类型
console.log( true+1 ) // 2
console.log( undefined +1 ) // NaN

console.log( null ) //object
console.log( undefined ) // undefined

3、延迟加载 js 的方式有哪些?有什么区别呢?

共有 6 种方式,分别为:

  • async
  • defer
  • js 最后加载
  • 利用 setTimeout
  • 动态创建 DOM 的方式
  • 使用 jQuery 的 getScript 方法

它们的区别介绍:

1、async:为 <script>标签定义了 async 属性。async 和 html 解析是同步的,不是顺次执行 js 脚本,谁先加载完成先执行谁。

<script  async type="text/javascript" src="demo1.js" ></script>
<script  async type="text/javascript" src="demo2.js" ></script>

2、defer 会等到 html 解析完成之后再执行 js 代码,如果有多个脚本时,会按照顺序依次执行脚本。

<script  defer type="text/javascript" src="demo1.js" ></script>

3、js 最后加载

把 js 外部引入的文件放置在页面的底部,让 js 最后加载,从而加快页面加载速度。

4、利用 setTimeout

5、动态创建 DOM 的方式

var element=document.createElement("script");  
element.src="box.js";  
document.body.appendChild(element);

这种方式通过操作动态加载 js 文件,不触发的时候不加载,减少页面文件大小,加快加载速度。

6、使用 jQuery 的 getScript 方法

$.getScript( "box.js",function(){//回调函数,成功获取文件后执行的函数  
      console.log("脚本加载完成")  
});

相关面试题:

<!doctype html>
<html>
 <head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <script type="text/javascript" src="box.js"></script>
 </head>
 <body>
  <div id="box"></div>
 </body>
</html>

//box.js 代码如下
console.log( document.getElementById('box') )  // null

box.js 想正常获取元素 box ,并进行一系列操作应该如何延迟加载 js 文件呢?

4、你对作用域的认识有多少?

作用域通俗地讲,就是指一个变量的作用范围。分为全局作用域和函数作用域。

全局作用域

  • 页面打开时被创建,页面关闭时被销毁。
  • 编写在 script 标签下的变量和函数,作用域为全局,页面的任意位置都可以访问
  • 有全局对象 window ,代表浏览器窗口,全局作用下的变量和函数作为 window 的属性和方法

函数作用域(局部)

  • 函数是被调用时创建的,执行完毕之后销毁。
  • 函数每调用一次,变量和函数就会重新创建一次,它们之间是相互独立的
  • 在函数作用域内可以访问到全局变量或函数,但是在函数外无法访问函数作用域内的变量
  • 函数作用域内访问变量,会在自身作用域内寻找,若没有则会向上一级作用域内查找,一直到全局作用域。

函数在被调用的时候会先进行预编译:

全局作用域预编译:

  • 创建上下文 GO 对象。
  • 找变量声明,将变量名作为 GO 对象的属性名,值为 undefined
  • 找函数式声明,将值赋予函数体

函数作用域预编译:

  • 创建上下文 AO 对象
  • 将形参和实参作为 AO 对象的属性,赋值为 undefined
  • 实参和形参相统一
  • 在函数体内找函数声明,将值赋予函数体。

相关面试题:

<script type="text/javascript">
 function fn(a,c){
  console.log(a)
  var a=12
  console.log(a)
  console.log(c)
  function a(){ }
  if(false){
   var d=34
  }
  console.log(d)
  console.log(b)
  var b=function(){}
  console.log(b)
  function c(){}
  console.log(c)
 }
 fn(1,2)
</script>
// 运行结果:
/*
function a(){}
12
function c(){}
undefined
undefined
function (){}
function c(){}
*/

5、null 和 undefined 的区别。

null 和 undefined 两个都表示无的值。

作者设计 js 的时候,借鉴的 java 语言先设计的 null 。null 使用的时候会被隐式转化成 0,不容易发现错误。

console.log( number(null) ) //0

undefined 是为了填补 null 的坑。所以后来又新增了 undefined 。

console.log( number(undefined) ) //NaN

6、new 操作符具体做了什么?

  • 创建了一个空对象。
  • 将空对象的原型指向于构造函数的原型。
  • 将空对象作为构造函数的上下文。
  • 对构造函数有返回值的处理判断。

实现 new 操作符的方法:

function create( fn,...args ){
 var obj={}
 Object.setPrototypeOf( obj,fn.prototype )
 var resault=fn.apply(obj,args)
 return (resault instanceof Object) ? result : obj
}

7、为什么会有闭包?它解决了什么问题?

7.1、什么是闭包?

闭包就是函数嵌套函数,通过函数内的函数访问变量的规则,实现外部访问函数内的变量。

7.2、闭包的特点:

  • 函数嵌套函数。
  • 函数内部可以引用函数外部的参数和变量。
  • 参数和变量不会被垃圾回收机制回收。

实例3:闭包解决问题

var liArr=document.getElementsByTagName('li')
for(var i=0;i<liArr.length;i++){
 (function(i){
  liArr[i].onclick=function(){
   console.log('点击元素',liArr[i])
  }
 })(i) 
}

7.3、闭包优点:

  • 保护变量安全,实现封装,防止变量声明冲突和全局污染。
  • 在内存当中维持一个变量,可以做缓存。
  • 匿名函数自执行函数可以减少内存消耗。

防抖和节流就是闭包的经典应用。

7.4、闭包缺点:

  • 变量会驻留在内存中,造成内存损耗问题。解决办法:把闭包函数设置为 null 。
  • 内存泄漏

8、防抖和节流,你了解多少?

8.1、什么是防抖函数?

当持续触发事件,一定时间内没有再触发事件,事件处理函数才会执行一次,如果在设定的时间到来之前又触发了事件,就会重新计时。

防抖函数常见的实际应用:使用 echart 的时候,浏览器 resize 时,需要重新绘制图表大小,还有典型的输入框搜索应用。

8.2、节流函数是什么?

当持续触发事件的时候,保证一段时间内只调用一次事件处理函数,一段时间内,只允许做一件事情。

防抖和节流主要是用来限制触发频率较高的事件,再不影响效果的前提条件下,降低事件触发频率,减小浏览器或服务器的压力,提升用户体验效果。

9、数组去重有几种方法?

方法1: new set()

return Array.from(new Set(arr))
// 或
return [...new Set(arr)]

方法2:使用两次循环

for(var i=0,len=arr.length;i<len;i++){
 for(var j=i+1,len=arr.length;j<len;j++){
  if( arr[i]===arr[j] ){
   arr.splice(i,1)
   j--;
   len--
  }
 }
}
return arr

方法3:indexOf 实现

let arr1=[]
for(var i=0;i<arr.length;i++){
 if( arr1.indexOf(arr[i])===-1 ){
  arr1.push(arr[i])
 }
}
return arr1

方法4:includes 实现

let arr1=[]
for(var i=0;i<arr.length;i++){
 if( !arr1.includes(arr[i]) ){
  arr1.push(arr[i])
 }
}
return arr1

方法5:filter 实现

array.indexOf(item,start) start 表示开始检索的位置。

return arr.filter(( item, index )=>{
 return arr.indexOf( item, 0 )==index
})

10、call、bind 和 apply 的区别

三者都是改变函数执行的上下文,即改变 this 指向。

它们之间的区别为:

  • call 和 apply 会立即执行,bind 返回的是一个函数,需调用后执行。
  • 第二参数是传入要执行的方法中的参数,call 和 bind 是独立传递参数,apply 是以数组传递参数的

使用场景:
1、需要改变某个函数的this指向时
2、当参数较少时可以使用call,参数较多可以使用apply以数组的方式传递
3、当需要重复调用时,可以使用bind新定义一个方法

11、js 判断变量是不是数组,你能写出几种方法?

方法1:isArray

var arr=[1,2,3]
console.log(Array.isArray(arr))    

方法2:instanceof

var arr=[1,2,3]
console.log( arr instanceof Array )
console.log( arr instanceof Object )

该方法不够严谨。

方法3:prototype

console.log( Object.prototype.toString.call(arr).indexOf('Array')>-1 )

方法4:isPrototypeOf

console.log( Array.prototype.isPrototypeOf( arr ) )

方法5:constructor

console.log(arr.constructor.toString().indexOf('Array')>-1 )

12、slice 是干嘛的? splice 是否会改变原数组?

slice 是用来截取字符串的,返回一个新数组,但不会影响原数组。

使用语法:

arr.slice( start , end )

截取 arr 数组,从 start 开始到 end 结束,第二个参数是可选参数,没有时从 start 开始截取到结尾。

如果 start 参数是负数时,就会从 arr.lengtn + start 开始截取到结束。

var arr=['a','b','c','d','e']
console.log( arr.slice(-3) ) // ["c", "d", "e"]
console.log(arr)  //["a", "b", "c", "d", "e"]

splice 是一个更强大的方法,可以添加、删除、替换数组元素,返回的是被删除元素,它的操作会改变原数组。

使用语法:

splice( start, n, new )

从 start 开始,删除 n 个元素,然后把 new 添加到 start 元素之后。第三个参数为可选参数

  • n 为 0 且第三个参数不为空时,表示添加新元素到 start 之后。
  • n 不为 0 且第三个参数不为空时,表示把 start 之后的 n 个元素替换成 new 。
  • n 不为 0 且第三个参数为空时,表示删除 start 后的 n 个元素。
var arr=['a','b','c','d','e']
var ar=arr.splice( 1, 1 ,'f','g')
console.log('ar',ar)    // ["b"]
console.log('arr',arr) //  ["a", "f", "g", "c", "d", "e"]

13、==和===有什么不同?

==比较的是值,===除了比较值,还比较类型。

console.log( [1,2]=='1,2'  )       // true
console.log( [1,2]==='1,2'  )  //false

valueOf 方法返回 Math 对象的原始值,通常由 javascript 在后台自动调用,并不显示的出现在代码中。

console.log([1,2].valueOf()) //[1,2]
console.log('1,2'.valueOf()) //[1,2]
// 所以
console.log( [1,2]=='1,2'  )  // true

不管是字符串和数字比较,还是布尔值和数字比较,都会使用 valueOf 隐式转换。

总结:==需要使用 valueOf() 进行隐式转换,所以性能差。===会避开一些不必要的麻烦。

14、this 的指向

大厂笔试题:

var name='window name'
var p1={
 name:'p1 name',
 showName:function(){
  console.info(this.name)
 }
}
var fn=p1.showName
fn()
p1.showName()
var p2={
 name:'p2 name',
 showName:function(fun){
  fun()
 }
}
p2.showName(p1.showName)
p2.showName=p1.showName
p2.showName()
/*
运行结果:
window name
 p1 name
 window name
 p2 name
*/

这是一道关于 this 指向的面试题,接下来我们就说说 this 是如何指向的?

this 对象是运行时基于函数的执行环境绑定的:

  • 在全局函数中,this 等于 window 。
  • 函数上下文调用,严格模式下 this 为 undefined ,非严格模式下,this 指向 window 。
  • 当函数被作为某个对象的方法被调用时,this 等于那个对象。如果使用 call apply 改变当前 this 时,将会指向为传递过来的那个 this 。
  • 匿名函数的执行环境具有全局性,因此 this 指向 window。
  • 构造函数内的 this 指向创建的实例对象。
  • dom 事件处理函数,this 指向触发该事件的元素。
  • setTimeout 和 setInterval 中的 this 指向全局变量 window

15、js 中的继承有哪些方式呢?

第 1 种:原型链继承

function Parent(){
 this.name="前端人"
}
Parent.prototype.showName=function(){
 console.log(this.name)
}
function Child(){}
 //原型链继承   
Child.prototype=new Parent()
var p=new Child()
console.dir(p.name) //前端人

特点:

  • 实例的是子类的实例,也是父类的实例。
  • 父类新增原型方法和属性,子类都能访问到。
  • 简单,方便实现

第 2 种:借用构造函数

function Animal (name) {
 this.name=name || 'Animal';
 this.sleep=function(){
  console.log(this.name + '正在睡觉!');
 }
}
Animal.prototype.eat=function(food) {
 console.log(this.name + '正在吃:' + food);
};
function Cat(name){
 Animal.call(this);
 this.name=name || 'Tom';
}
// Test Code
var cat=new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

特点:

  • 创建子类时,可以向父类传递参数。
  • 可以实现多继承,call 多个父类对象。
  • 解决方法1中,子类实例共享父类引用属性的问题。

还有组合式继承、ES6 的继承 和 寄生组合继承等等。每种继承方式都有各自的特点和缺点。

16、严格模式与非严格模式的区别,你了解多少?

JavaScript 语言是一门弱类型语言,存在许多类型错误,因此 ES6 引入了严格模式概念。

如果不加 ‘use strict’ 常规模式下就是属于非严格模式。

严格模式

在 js 文件顶部添加 ‘use strict’ 就属于严格模式,严格模式也可以指定在函数内部。

<script>
 'use strict'  
 //或者函数内部
 (function(){
  'use strict'
 })()
</script>

严格模式,是为 js 定义来了一种不同的解析与执行模型,在严格模式下,ECMAScipt 3 中一些不解和不确定的行为将得到处理,而且会对不安全的操作会抛出异常。‘use strict’ 会告诉浏览器引擎可以切换到严格模式执行。

严格模式与非严格模式区别

严格模式

非严格模式

变量必须声明才能赋值

变量不进行声明,可直接赋值

不能使用 delete 字符删除变量或对象

可以使用 delete 删除

函数参数变量名不允许重复

变量名重复,获取最后最后那个值

普通函数内的 this 为 undefined

普通函数内的 this 为 window

不允许使用八进制

允许任意进制

eval 和 arguments 当做关键字,不能被赋值和用作变量名

可以使用 eval 、arguments 作为变量名

call、apply 传入 null undefined 保持原样不被转为window

默认转为 window 对象

限制对调用栈的检测能力,访问 arguments.callee 会抛出异常

arguments.callee 运行正常

17、隐式转化相关面试题

console.log( '2'>10 ) //false
console.log( '2'>'10' ) //true
console.log( 'abc'>'b' ) //false
console.log( 'abc'>'aab' ) //true
console.log( undefined==null ) //true
console.log( NaN==NaN )//false
console.log( []==0 ) //true
console.log( ![]==0 ) //true
console.log( []==[] ) //false
console.log( {}=={} ) //false
console.log( {}==!{} ) //false

18、事件循环机制相关面试题。

阿里面试题1:

<script type="text/javascript">
 var p=new Promise(resolve=>{
  console.log(4)
  resolve(5)
 })
 function f1(){
  console.log(1)
 }
 function f2(){
  setTimeout(()=>{
   console.log(2)
  },0)
  f1()
  console.log(3)
  p.then(res=>{
   console.log(res)
  })
 }
 f2()
</script>
// 运行结果 4 1 3 5 2
// 如果已经了解事件运行机制,就可以跳过该问题了

事件循环机制,event-loop 。包含三部分:调用栈、消息队列、微任务队列。

事件循环开始的时候,会从全局一行一行的执行代码,遇到函数调用的时候,就会压入调用栈中,当函数执行完成之后,弹出调用栈。

// 如:代码会一行一行执行,函数全部调用完成之后清空调用栈
function f1(){
 console.log(1)
}
function f2(){
 f1()
 console.log(2)
}
f2()
// 执行结果 1 2

如果遇到 fetch、setInterval、setTimeout 异步操作时,函数调用压入调用栈时,异步执行内容会被加入消息队列中,消息队列中的内容会等到调用栈清空之后才会执行。

// 如:
function f1(){
 console.log(1)
}
function f2(){
 setTimeout(()=>{
  console.log(2)
 },0)
 f1()
 console.log(3)
}
f2()
// 执行结果 :1 3 2

遇到 promise、async、await 异步操作时,执行内容会被加入微任务队列中,会在调用栈清空之后立即执行。

调用栈加入的微任务队列会立即执行。

如
let p=new Promise(resolve=>{
 console.log('立即执行')
 resolve(1) //在 then 调用中执行
})

微任务队列中内容优先执行,所以比消息队列中的内容执行得早。

了解这些知识后,再试一下最前面的那道面试题,应该就没什么问题了。

20、前端领域内,你比较擅长什么?

这个问题就留给读到最后,能够坚持学习的人,问问我们自己有什么是我们擅长的?在哪块领域是我们占据竞争优势的?

使用内置JavaScript的对象实例。

Document 对象

  • 使用 document.write() 输出文本

  • 使用 document.write() 输出 HTML

  • 返回文档中锚的数目

  • 返回文档中第一个锚的 innerHTML

  • 返回文档中表单的数目

  • 返回文档中第一个表单的名字

  • 返回文档中的图像数

  • 返回文档中第一个图像的ID

  • 返回文档中的链接数

  • 返回文档中的第一个链接的ID

  • 返回文档中的所有cookies的名称/值对

  • 返回加载的文档的服务器域名

  • 返回文档的最后一次修改时间

  • 返回加载的当前文档的URL

  • 返回文档的标题

  • 返回文档的完整的URL

  • 打开输出流,向流中输入文本

  • write() 和 writeln()的不同

  • 用指定的ID弹出一个元素的innerHTML

  • 用指定的Name弹出元素的数量

  • 用指定的tagname弹出元素的数量

  • 更多的Document 对象的例子,在我们的JavaScript 参考手册。

Anchor 对象

  • 返回和设置链接的charset属性

  • 返回和设置链接的href属性

  • 返回和设置链接的hreflang属性

  • 返回一个锚的名字

  • 返回当前的文件和链接的文档之间的关系

  • 改变链接的target属性

  • 返回一个链接的type属性的值

  • 更多的Anchor 对象的例子,在我们的JavaScript 参考手册。

Area 对象

  • 返回图像映射某个区域的替代文字

  • 返回图像映射某个区域的坐标

  • 返回一个区域的href属性的锚部分

  • 返回的主机名:图像映射的某个区域的端口

  • 返回图像映射的某个区域的hostname

  • 返回图像映射的某个区域的port

  • 返回图像映射的某个区域的href

  • 返回图像映射的某个区域的pathname

  • 返回图像映射的某个区域的protocol

  • 返回一个区域的href属性的querystring部分

  • 返回图像映射的某个区域的shape

  • 返回图像映射的某个区域的target的值

  • 更多的Area 对象的例子,在我们的JavaScript 参考手册。

Base 对象

  • 返回页面上所有相对URL的基URL

  • 返回页面上所有相对链接的基链接

  • 更多的Base 对象对象的例子,在我们的JavaScript 参考手册。

Button 对象

  • 当点击完button不可用

  • 返回一个button的name

  • 返回一个button的type

  • 返回一个button的value

  • 返回一个button所属表的ID

  • 更多Button 对象实例在我们的JavaScript 参考手册。

Form 对象

  • 返回一个表单中所有元素的value

  • 返回一个表单acceptCharset属性的值

  • 返回一个表单action属性的值

  • 返回表单中的enctype属性的值

  • 返回一个表单中元素的数量

  • 返回发送表单数据的方法

  • 返回一个表单的name

  • 返回一个表单target属性的值

  • 重置表单

  • 提交表单

  • 更多Button 对象实例在我们的JavaScript 参考手册。

Frame/IFrame 对象

  • 对iframe排版

  • 改变一个包含在iframe中的文档的背景颜色

  • 返回一个iframe中的frameborder属性的值

  • 删除iframe的frameborder

  • 改变iframe的高度和宽度

  • 返回一个iframe中的longdesc属性的值

  • 返回一个iframe中的marginheight属性的值

  • 返回一个iframe中的marginwidth属性的值

  • 返回一个iframe中的name属性的值

  • 返回和设置一个iframe中的scrolling属性的值

  • 改变一个iframe的src

  • 更多Frame/IFrame 对象实例在我们的JavaScript 参考手册。

Image 对象

  • 对image排版

  • 返回image的替代文本

  • 给image加上border

  • 改变image的高度和宽度

  • 设置image的hspace和vspace属性

  • 返回image的longdesc属性的值

  • 创建一个链接指向一个低分辨率的image

  • 返回image的name

  • 改变image的src

  • 返回一个客户端图像映射的usemap的值

  • 更多Image 对象实例在我们的JavaScript 参考手册。

Event 对象

  • 被按下的键盘键的keycode?

  • 鼠标的坐标?

  • 鼠标相对于屏幕的坐标?

  • shift键被按下了吗?

  • 哪个事件发生了?

Option and Select 对象

  • 禁用和启用下拉列表

  • 获得有下拉列表的表单的ID

  • 获得下拉列表的选项数量

  • 将下拉列表变成多行列表

  • 在下拉列表中选择多个选项

  • 弹出下拉列表中被选中的选项

  • 弹出下拉列表中被选中的选项的索引

  • 改变下拉列表中被选中的选项的文本

  • 删除下拉列表中的选项



Table, TableHeader, TableRow, TableData 对象

  • 改变表格边框的宽度

  • 改变表格的cellpadding和cellspacing

  • 指定表格的frame

  • 为表格指定规则

  • 一个行的innerHTML

  • 一个单元格的innerHTML

  • 为表格创建一个标题

  • 删除表格中的行

  • 添加表格中的行

  • 添加表格行中的单元格

  • 单元格内容水平对齐

  • 单元格内容垂直对齐

  • 对单个单元格的内容水平对齐

  • 对单个单元格的内容垂直对齐

  • 改变单元格的内容

  • 改变行的内容

如您还有不明白的可以在下面与我留言或是与我探讨QQ群308855039,我们一起飞!