Hexo


  • Home

  • Archives

Web前端的知识之旅哟——DOM的基本操作续

Posted on 2018-01-02

上一篇文章介绍了如何用DOM来查看元素,这一篇就把剩下的增删改操作介绍一下。

增加操作

1.创建元素节点 createElement

我们可以通过document.createElement(‘div’);这个方法来创建一个元素,里面的参数填写我们要创建的标签名称,像div、p、span等等。


1. var div = document.createElement(‘div’);
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这样我们先创建一个div。

2.创建文本节点 document.createTextNode

我们可以通过document.createTextNode(‘abcd’)方法来创建一个文本节点,里面的参数填写我们的文本内容。


1. var text = document.createTextNode(‘hello’);  
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这里我们再创建一个文本节点。

3.创建注释节点 document.createComment

我们可以通过document.createComment(‘comment’);方法来创建一个注释节点,参数写我们要写的注释内容。虽然我从来没用过…但是还是要介绍这个方法。


1. var comment = document.createComment(‘comment’);  
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这里我们再创建一个注释文本节点。

4.创建文档碎片 document.createDocumentFragment

这个方法可以创建一个文档碎片,我们在后面讲的有关提高性能的部分会用到这个方法。

插入操作

1.appendChild(child);

这个是父级调用的方法,它会将child元素插入到父级里面,而且是放到逻辑后面的位置上。


1. div.appendChild(text);
2. div.appendChild(comment);  
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这样div里面就会有一行文本一行注释。

这里如果是我们创建的元素,那么appendChild就是把新元素插入进去,但是如果是DOM里面已经存在的元素,那么appendChild就是把这个已经存在的元素给剪切掉,然后放到新的位置上。

2.insertBefore(a, b);

这个依然是父节点调用的方法,它的意思是将a插入到b前面的位置上,其中b要求是这个父级的子节点。这个方法可以记作为 insert a before b;


1. var span = document.createElement(‘span’);
2. div.insertBefore(span, comment);  
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这样我们就把一个span元素插入到了刚才的注释文本前面。

删除操作

removeChild

这个方法依然是父级调用的,参数就是要删除的子节点,其实实际上是剪切,这个方法会把我们删除掉的元素给返回,我们可以用一个变量去保存这个被删除的元素。


1. var div = document.body.remove(div);  
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这样我们的div变量就可以保存刚才被删除的div这个元素。

替换操作

replaceChild(new, origin)

这个方法同样是父级调用,用新的元素new来替换原来的origin元素,原来的被替换掉的元素可以被返回,我们可以像删除操作那样用一个变量来保存。

现在我们就可以通过以上这些方法来动态创建一棵DOM树了哟~

下面介绍一些Element节点的属性和方法。

属性

1.innerHTML

这个属性可以用字面意思来理解,元素里面的HTML结构。


1. div.innerHML = ‘<div>123</div>’;  
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

我们可以直接通过这个属性来改变元素内部的结构和内容,不过注意它会直接删除掉以前的所有结构,如果以前内容还有其他的节点的话,使用的时候就要小心了。

2.innerText/textContent

innerText老版本的火狐浏览器不兼容,textContent老版本的IE浏览器不兼容。

这个属性可以直接调出来元素内部的文本信息,若果这个元素还有很多的子元素的话,那么会把子元素里面的文本信息一起返回。

不过需要注意的是,如果我们要改写innerText或者textContent的话,它会像innerHTML一样,先把内部的所有html结构先删除掉,然后再写入text文本,因此里面有html结构的时候写入也要小心。

• 这里提一下,如果我们有很多字符串要添加到一个元素的内部的话,虽然用innerHTML或者innerText方法,但是却不是用innerHML += str的方法,因为+=操作符的效率极低,当字符串很多的时候会非常非常消耗性能。

遇到这种情况我们一般是用数组的join方法将字符串全部连接成一个字符串,然后一次性写入innerHTML。

方法

1.ele.setAttribute();

这个方法可以设置元素的属性(特性),比如class、id等一些行间属性。


1. div.setAttribute(‘id’, ‘demo’);  
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这个操作就可以给div这个元素设置一个叫做demo的id。

2.ele.getAttribute();

这个方法是获取元素的行间属性。

• 同样这里需要提一下,每个元素自带的行间属性都有自己特殊的功能,而我们通过自己给他们设置的属性并没有任何功能,因此我们可以来赋予他们功能和用处。

• 还有一点,我们在后面改变元素的样式的时候,通常不是直接修改他的css样式,而是事先写好它应该变成的样式,然后装进一个class里面,我们直接修改他的类名而不是css样式。

Web前端的知识之旅哟——DOM的基础练习代码

Posted on 2018-01-02

上一篇介绍了一下DOM的一些基础的知识,这里我整理了一些有关上一篇知识点的一些封装函数。

1.遍历元素节点树


1. function retChild(node) {
2.       var child = node.childNodes,
3.             len = child.length;
4.       for(var i = 0; i < len; i++){
5.             if(child[i].nodeType === 1) {
6.                   console.log(child[i]);
7.                   child[i].hasChildNodes() && retChild(child[i]);
8.             }
9.       }
10. }  
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

2.封装函数,返回元素e的第n层父节点


1. function retParent(e, n) {
2.       var n = n || 0; // 进行简单的容错检测
3.       if(n === 0) {
4.             return e;
5.       }
6.       for(var i = 0; e && i < n; i++) {
7.             e = e.parentNode;
8.       }
9.       return e;
10. }  
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

3.封装函数,返回元素e的第n个兄弟元素节点,如果n为正,返回后面的兄弟元素节点,n为负,返回前面的,n为0,返回自己


1. function retSibling(e, n){
2.       var n = n || 0;
3.       if(n === 0) {
4.             return e;
5.       }
6.       while(e && n != 0) {
7.             if(n > 0) {
8.                   if(e.nextElementSibling){ // 如果不是ie浏览器
9.                         e = e.nextElementSibling;
10.                   }else { // 如果是ie浏览器
11.                         e = e.nextSibling;
12.                         while(e && e.nodeType != 1) {
13.                               e=e.nextSibling;
14.                   }
15.            }
16.            n–;
17.       }else {
18.             if(e.previousElementSibling) {
19.                  e = e.previousElementSibling;
20.             }else {
21.                   e = e.previousSibling;
22.                   while(e && e.nodeType != 1) {
23.                   e=e.previousSibling;
24.                   }
25.            }
26.            n++;
27.            }
28.       }
29.       return e;
30. }  
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

4.封装函数,实现children功能,最好哎原型链上编程


1. Element.prototype.getChildren() {
2.       var child = this.childNodes,
3.             len = child.length,
4.       obj = {
5.             ‘length’: 0,
6.             ‘push’: Array.prototype.push
7.       }
8.       for(var i = 0; i < len; i++){
9.             if(child[i].nodeType === 1) {
10.                   obj.push(child[i]);
11.             }
12.       }
13.       return obj;
14. }  
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

5.封装是否有元素子节点的方法


1. Element.prototype.hasChildren = function () {
2.       var child = this.childNodes,
3.             len = child.length;
4.       for( var i = 0; i < len; i++){
5.             if(child[i].nodeType == 1) {
6.                   return true;
7.             }
8.       }
9.       return false;
10. }  
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

上面的代码都是我自己写的,可能有些地方不是最优的解法,大家看看就好~

 

Web前端的知识之旅哟——DOM!

Posted on 2018-01-02

到上一篇文章结束我感觉ECMAScript的部分应该差不多了,所以接下来开始总结DOM的知识点了哟~

什么是DOM

什么叫做DOM呢?

• DOM的全称是Document Object Model 文档对象模型,DOM定义了表示和修改文档所需的对象、这些对象的行为和属性以及这些对象之间的关系。

• DOM对象即为宿主对象,由浏览器厂商定义,用来操作html的css功能的一类对象和集合。不过浏览器厂商之间大部分都遵循w3c标准。

• 简单来说,DOM就是用来操作html和css的,它是一系列对象的集合。

DOM如何操作HTML

document代表整个文档,它也是一个dom元素,我们dom对html的操作,即使对html的增删改查。下面我就介绍一下dom都是如何增删改查html的。

那么我们如何查看元素节点?

我们知道css中有id、class、标签等选择器,同样,我们的document对象上也定义了很多类似的方法来查看元素节点。

 • getElementById

document.getElementById(‘id’);方法是通过元素的id来选择出相对应的元素的,因为id是唯一标示,所以方法名中是Element。

值得注意的是,在ie8以下的浏览器中,不区分大小写,而且标签的name属性也可以被当做id被选择出来。

<div name=”demo”></div>

var div = document.getElementById(‘demo’);

这里同样把这个div选择出来了。

 • getElementsByClassName

document.getElementsByClassName(‘class’); 获取到的是一个类数组,因为很多元素都可以有一个类名。我们可以通过[]的方式来选择到具体的哪一个元素。

<div class=”demo”></div>

<div class=”demo”></div>

var div = document.getElementsByClassName(‘div’)[1];

这样我们就可以选择到第二个div了。

不过如果我们碰到这种情况该怎么办?

<div class=”demo”></div>

<div class=”demo demo1″></div>

<div class=”demo1″></div>

我们改如何选择出来第二个div?

这里,我们的getElementsByClassName其实后面可以填写多个类名。

var div = document.getElementsByClassName(‘demo demo1’)[0];

这样,我们就可以选择出来第二个div了。

但是值得注意的是,ie8及以下的版本中没有这种方法。

 • getElementsByTagName

document.getElementsByTagName(‘div’);这个方法是可以选择出来具体某一种元素的集合,像前面这一段就可以选择出全部的div集合,当然也是一个类数组。

这个方法所有版本的浏览器都兼容。

 • getElementsByName

document.getElementsByName();需要注意的是,只有部分标签的name可以生效,比如表单、表单元素、img、iframe等。

<input name=”123″/>

document.getElementsByName(‘123’)[0];

同样是选择出来一组,不过这个方法不是很常用。

 • 这里面我们我们最常用的是id和tag,因为全版本都支持。

 • querySelector()

 • querySelectorAll()

这两个方法通常放在一起说。

我们知道选择元素最强的是css,而这两个里面写的参数就是我们css选择器的写法。

document.querySelector(‘div p #demo .demo);

不过querySelector永远选择一组里面的第一个,所以返回的不是一个类数组而是一个具体的元素。

而我们如果要返回一个类数组的集合的话,那么就用第二个querySelectorAll()方法。

不过这两个方法的问题在于,他们返回的不像前面四个是一个实时改变的元素,而是一个副本。当我们用这两个方法选择出来元素之后,我们把本身那个元素修改一下,会发现我们选择出来的那个元素没有变化。

<div class=”content”>111</div>

<div class=”content”>222</div>

<div class=”content”>333</div>

var div = document.querySelectorAll(‘.content’);

var div1 = document.getElementsByClassName(‘content’)[0];

console.log(div);

div1.remove();

console.log(div);

我们发现两次打印出来的都是[div.content, div.content, div.content],也就是说我们实际删除的那个元素对用querySelector选择出来的那个副本没有影响。

不过,在ie7及以下的版本没有这两个方法。

我们以后提到的jQuery里面的选择器,虽然是基于Sizzle的,但是Sizzle是基于querySelector写的。

节点

节点类型

我们页面里面的节点类型很多,比如元素节点、文本节点、注释节点、属性节点等等。

我们可以通过nodeType属性来查看这个节点的类型是什么。而nodeType返回的是一些数字,下面介绍几个基础的类型和数字的对应关系:

元素节点——1

属性节点——2

文本节点——3

注释节点——8

document——9

DocumentFragment——11


节点的其他属性

 • nodeName

这个属性可以返回元素的标签名,以大写的形式表示,只读,不允许写入。

有几个特殊的节点返回的也不太一样:

文本节点–> #text

注释节点–> #comment

document节点–>#document

 • nodeValue

Text节点或者Comment节点的文本内容,可以读写

 • attributes

把元素的行间属性都读取出来,放到一个对象里面返回,对象里面的每一个属性都是一个节点,这个节点就是我们前面提到的属性节点。

注意:对象里面的属性叫做property,而元素里面的属性叫attributes,实际应该叫特性。

节点还有一个方法

hasChildNodes()可以检测是否有子节点

遍历节点树

• parentNode 查找父节点

<div>

      <p><strong></strong></p>

</div>

这里strong的父节点就是p,p的父节点是div,div的父节点是body,body的父节点是html,html的父节点是document,document的父节点是null,在后面就没有了。

• childNodes 子节点们

div.childNodes 没有说明类型,那么就是说这个方法是把所有的子节点都返回。

<div>

      <p><strong></strong></p>

</div>

还是这个例子,我们div里面的childNodes其实有3个,第一个是前面的空格——文本节点,第二个是中间的p标签——元素节点,第三个是最后的空格——文本节点。

• firstChild 第一个子节点

• lastChild 最后一个子节点

• nextSibling 下一个兄弟节点

• previousSibling 上一个兄弟节点

以上这些方法的兼容性都很好,所有的浏览器都支持,但是下面这些就不行了。

基于元素节点树的遍历

• parentElement 返回当前元素的父元素节点

在这个方法上面,html上面的父元素节点就不是document而是null了。

但是ie不支持这个方法。

• children 所有子元素节点

这个方法所有的浏览器都兼容。

• childElementCount

node.children.length === node.childElementCount

这个属性就是子元素节点的数量,不过我更常用前面的那个。

• nextElementSibling

• previousElementSibling

这两个方法分别是找上一个和下一个兄弟元素节点,但是ie都不兼容。

DOM结构树

dom

这个图表中说明了每一个对象的父级。

浏览器除了可以处理html页面外,还可以处理xml和xhtml等页面。

我们发现HTMLDocument继承自HTMLDocument.prototype,我们在原型上定义一个属性来测试一下。

HTMLDocument.prototype.abc = 123;

document.abc; // 123

document.getElementById(‘demo’).abc; // 报错

由此可见,HTMLDocument确实继承HTMLDocument对象的属性,但是相对的Element上面并没有这个方法。

这里的Node上面也还是有父级的,我们可以通过Node.prototype来查看,这里就不举例了。

下面是一些DOM结构树的总结:

1.getId方法定义在Document.prototype上,即Element节点上不能使用。

2.getElementByName方法定义在HTMLDocument.prototype上,非html中的document不能使用(xml document、Element);

3.getElementsByTagName方法定义在Document.prototype和Element.prototype上,也就是document和元素都可以用这个方法。

4.HTMLDocument.prototype上定义了一些常用的属性,body、head分别代指HTML文档中的<body><head>标签。

5.Document.prototype上定义了documentElement属性,指代文档的根元素,在html文档中,它总代指<html>元素。

6.getElementByClassName、querySelectorAll、querySelector在Document、Element类中均有定义。

今天的DOM知识就到这里结束了哟~

 

Web前端的知识之旅哟——类数组与严格模式

Posted on 2018-01-02

我们知道有两种数据叫做数组和对象,但是我们其实可以用对象来模拟出数组的效果,我们把这种对象叫做类数组。我们前面提到的arguments实参列表就是一个类数组。

类数组

类数组并不是一个数组,但是它可以表现出数组的特性。


1. var arrObj = {
2.       ‘0’: 1,
3.       ‘1’: 2,
4.       ‘2’: 3,
5.       ‘length’: 3,
6.       ‘push’: Array.prototype.push
7. }  
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这样我们就创造了一个类数组,现在它就可以表现出数组的特性了。


1. arrObj.push(4);
2. console.log(arrObj);
3. -> 
4. arrObj: {
5.       ‘0’: 1,
6.       ‘1’: 2,
7.       ‘2’: 3,
8.       ‘3’: 4,
9.       ‘length’: 4,
10.       ‘push’: Array.prototype.push
11. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

我们会发现它自动改变了length值,这就非常神奇了。

其实类数组的关键就在这个length属性上,如果没有length属性,那么就是一个普通的对象,即使有push方法也不能使用。

现在我们来模拟一下数组的push方法是怎么实现的:


1. Array.prototype.push = function (num ) {
2.       this[this.length++]. = num;
3. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

push方法就是在数组的最后添加一个值,也就是说length的位置加一个元素,然后把length加1,这样我们也就不难理解为什么有了length属性的对象可以调用数组的push方法了,因为它本身带有一个合法的length属性。

现在我们有一个这样的题:


1. var arrObj = {
2.      “3”: 1,
3.      “4”: 2,
4.      “a”: 3,
5.      “b”: 4,
6.      “length”:2,
7.      ‘push’: Array.ptototype.push
8. }
9. arrObj.push(3);
10. arrObj.push(6);
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

我们最后的arrObj是什么样子的?

仔细看一下push的方法之后,我们就知道了,最后的arrObj应该是这个样子的:


1. var arrObj = {
2.      “2”: 3,
3.      “3”: 6,
4.      “4”: 2,
5.      “a”: 3,
6.      “b”: 4,
7.      “length”:4,
8.      ‘push’: Array.ptototype.push
9. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这里的length是2,所以一开始push(3)的时候把arrObj[2]改成了3,因为本身没有2这个下标,所以添加一条属性2:3,然后length变成了3,push(6)之后,arrObj[3]变成了6,本身有3这一条属性所以覆盖,然后把length变成4。

try…catch

try {} catch (e) {} finally {}一般是用来检测可能出错的问题的。

我们把可能出错的代码块放入try里面,然后把如果出错会产生的反应代码放到catch里面,finally就是挡catch走完之后再走一下finally代码块,finally的用处不是很大。

catch的参数e一定要写上,系统会自动传进去错误信息,错误信息一共有以下6种:

1.EvalError eval()的使用与定义不一致

2.RangeError 数值越界

3.ReferenceError 非法或不能识别的引用数值

4.SyntaxError 发生语法解析错误

5.TypeError 操作数类型错误

6.URIError URI处理函数使用不当

其中3和4比较常见。

当try里面的代码出错了,try里面出错代码后面的代码就不会执行了,但是在try外面的代码还是正常执行的。


1. try {
2.       console.log(a);
3. }catch (e) {
4.       console.log(e); // ReferenceError: a is not defined(…)
5. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

ES5严格模式

es5的严格模式是一种全新的es5规范,在这个模式下,有一些es3的不标准的规则就不能使用了。

我们只要在代码的第一行写上“use strict”;这一行字符串就可以进入严格模式了,不会对不兼容严格模式的浏览器产生影响。

严格模式主要有两种用法:

1.全局严格模式

2.局部严格模式

全局模式就是我们在整个js代码的第一行写上字符串,而局部模式就是在函数里面的第一行写上字符串。

这里不推荐使用全局严格模式。

严格模式有什么作用?

1.当我们的代码进入严格模式之后,就不允许使用with函数,arguments.callee方法,func.caller属性。

2.变量赋值之前必须声明。

3.局部的this使用前必须被赋值,除了全局的this默认指向window,其他的默认都是undefiend。而且在非严格模式下,Person.call(null/undefined)之后,里面的this还是指向window,但是如果是严格模式的话,那么传递null,this就指向null,传递undefiend,this就指向undefiend。

4.拒绝重复属性和参数。不过有一些浏览器的属性名可以重复。

下面介绍一下with方法是什么。

with

with () {} 的作用是改变作用域链,它可以把括号里面的执行期上下文或者作用域放在自己的作用域链的最顶端。


1. var obj = {a: 123};
2. function test () {
3.       var a = 111;
4.       var b = 222;
5.       with (obj) {
6.             console.log(a); // 123
7.             console.log(b); // 222
8.       }
9. }
10. test();
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

本来如果没有with的话,在test函数里面作用域链的最顶端应该是自身,然后在下面才是window的作用域,但是用with之后,我们把obj放在了自身上面,成为了最上面的作用域,这样我们打印a,就会优先调用obj里面的a属性,如果有就用,没有的话再向下找第二层test函数的作用域。

那么今天的知识点就介绍到这里哟~

Web前端的知识之旅哟——克隆与数组

Posted on 2018-01-02

在开始介绍克隆的代码和数组的相关知识点之前,我在前面先介绍一下arguments.callee方法和func.caller属性。

• arguments.callee

这个方法是代指函数本身。

当我们在一些匿名函数或者立即执行函数里面进行递归调用函数本身的时候,由于这个函数没有名字,我们不能用函数名的方式调用,就可以用arguments.callee来调用。

一般当我们需要通过计算来进行初始化的时候,我们写一个立即执行函数,当这个立即执行函数还需要递归调用自身的时候,我们就是用arguments.callee方法。

• func.caller


1. function test () {
2.       console.log(test.caller);
3. }
4. function demo () {
5.       test()
6. }
7. demo(); // function demo(){}
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这是函数本身自带的一个属性,可以指出当前函数ude运行环境的函数引用,即这个函数是在哪个函数体里面执行的。

个人感觉这个东西并没有什么用……

克隆

克隆和我们前面所讲的继承有一些区别,克隆是复制出来一个一模一样的目标对象,而克隆又分为浅层克隆和深层克隆。

• 浅层克隆

克隆大致就是我们的源对象里面有什么属性,目标文件就有什么属性,依照这个原理,我们可以写出下面的代码:


1. function clone(src, tar) {
2.       var tar = tar || {}; 如果没有tar则默认是一个空对象
3.       for(var prop in src) {
4.             if(src.hasOwnProperty(prop)){
5.                   tar[prop] = src[prop];
6.             }
7.        }
8.       return tar;
9. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

当我们我的源对象src有一个自身的属性,就会在目标对象tar克隆一个对应的属性。

下面我们来测试一下:


1. var obj = {
2.       name: ‘scarlett’,
3.       age: 123
4. }
5. var obj2 = {}
6. clone(obj, obj2);
7. console.log(obj2.name);// scarlett
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

然后我们改变一下obj这个源对象的name:


1. obj.name = ‘test’;
2. console.log(obj2.name); // scarlett
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

我们可以发现,克隆出来的目标对象和本来的源对象之间没有关系了。

但是,当我们有一个属性是引用值(数组或者对象)的时候,按照我们这种克隆方式,只是把这个引用值的指向赋给了新的目标对象,也就是说,我们一旦改变了源对象或者目标对象的引用值属性,另一个也会跟着改变,这一点就是浅层克隆的缺点。

• 深层克隆

为了解决浅层克隆的引用值的问题,我们又需要一种深层克隆。

其实深层克隆的原理也很简单,我们只要不克隆引用值的引用,而是吧引用值也当做一个源对象,把里面的值一个一个的克隆进目标对象里面,不就解决了他们二者相同指向的问题了吗。


1. function deepCopy(src,tar){
2.       var tar= tar|| {} ;
3.       for (var prop in src){
4.             if (typeof (src[prop]) == ‘object’){
5.                    tar[prop] = (src[prop].constructor === Array ) ? [] : {};
6.                   deepCopy(src[prop],tar[prop]);
7.             }else {
8.                    tar[prop] = src[prop];
9.             }
10.       }
11.  
12.       return tar;
13. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这里我们运用了递归调用的方法,当我们检测到源对象里面的这个属性值是引用类型的,那么就在目标对象里面也创建一个引用类型的属性,如果原来是数组就创建数组,是对象就创建对象,然后分别将源对象里面的这个引用值和目标对象里面的引用值分别当做新的源对象和目标对象进行克隆,这样就是克隆的里面每一个值了,而不是把整个引用都克隆过去。

现在我们来测试一下:


1. var parent = {
2.       name : ‘ScarLet’,
3.       age : 123,
4.       sex : ‘male’,
5.       height : 190,
6.       money: [1,2,3,4,5]
7. }
8. var child = {};
9. deepCopy(parent,child);
10. console.log(child.money); //1 2 3 4 5
11. parent.money.push(10);
12. console.log(child.money); // 1 2 3 4 5
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这个时候目标对象和源对象的引用值之间就没有了关系,自己都是独立值,可以进行修改了。

数组

• 数组的声明

首先来介绍一下数组的声明方式,一共有两种:

1.字面量方式声明数组。


1. var arr [];
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

2.通过数组构造函数构造数组。


1. var arr = new Array(1, 2, 3, 4);
2. console.log(arr); // 1 2 3 4
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这种方式和字面量方式没有区别,但是要注意的是,如果我们在构造函数里面只写一个数字new Array(5);这个时候这个数字就不是第一个值是5的意思了,而是我们新创建的这个数组长度是5。


1. var arr = new Array(10);
2. console.log(arr); //10
3. document.write(arr); // 9个逗号 这里用console的话是一个空数组,看不到长度
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

• 数组的读写

js的数组是弱数据类型的数组,不像其他语言那样严格。

我们不可以溢出读


1. var arr = [1, 2];
2. console.log(arr[3]); // undefined
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

但是我们可以溢出写


1. arr[5] = 5;
2. console.log(arr); // 1,2,,,,5
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

数组的常用方法

数组的方法大致可以分类两类:不改变原数组的和改变原数组的。

1.改变原数组的

改变原数组的方法主要有:reverse,sort,push,pop,shift,unshift,splice

• reverse

reverse是使数组倒叙。

• push

push在数组最后的位置增加数据。


1. push(4, 5, 1);
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

• pop

pop从数组后面删除一位数据,同时返回这个被删除的数据,没有参数。

• shift

从数据的最前面删除一位数据,同时返回这个数据,没有参数。

• unshift

在数据的最前面添加数据,和push一样的用法。

• splice

这个方法是截取的意思,它有三个参数,第一个是截取开始的位置,第二个是截取的长度,第三个是参数是一组数据,代表我们要在截取的位置添加的数据。


1. var arr = [1, 2, 3, 4, 5];
2. arr.splice(1, 2, 100,188);
3. console.log(arr); // 1 100 188 4 5
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这样2和3就被截掉了。

如果我们不写要添加的数据的话,这个方法就变成了在数组中删除数据的作用了。

如果我们截取的长度是0,然后添加数据的话,这个方法就变成了在数据的特定位置添加数据的作用了。

• sort

这个方法是快速排序的意思,它将一种算法封装好了给我们使用。

我们可以在这个方法中传入一个参数,这个参数是一个函数,规定了我们的排序规则,否则就按照ASC码来排序。


1. arr -> 1 2 5 4 3
2. arr.sort -> 1 2 3 4 5
3. arr -> a c b d
4. arr.sort -> a b c d
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

如果我们数组中的元素是比较复杂的数据的话,我们就需要自己来定义排序的规则了。


1. arr.sort(function (x, y) {
2.       return x.age < y.age;
3. })
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这里的x、y代表的是数组里面任意的两位数据。

无论中间的规则怎么写,系统只关注函数最后的返回值是正数还是负数。

负数的时候,表示a在前面,b在后面。

正数的时候,表示a在后面,b在前面。

乱序排序:


1. function (a, b) {
2.       var num = Math.random() - 0.5;
3.      return num;
4. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

• 不改变原数组的

改变原数组的方法主要有:

concat,join

• concat

这个方法是连接的数组的作用。


1. arr1 = [1, 2];
2. arr2 = [2, 3];
3. arr = arr1.concat(arr2);
4. console.log(arr, arr1, arr2);// [1, 2, 2, 3],  [1, 2], [2, 3];
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

我们发现arr现在是1和2连接之后的数组,而且1和2本来的数组都没有改变。

当然我们如果要连接多个数组的话,那么concat里面的数组之间用逗号分隔即可。


1. arr3 = [5, 5];
2. arr = arr1.concat(arr2, arr3);
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

• join

这个方法是让数组的每一个数据以什么方式连接成字符串。


1. var arr = [‘a’, ‘b’, ‘c’];
2. var str = arr.join(‘-‘);
3. console.log(str); // a-b-c
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

我们可以用这个方法来进行大量字符串的连接工作,如果我们用+运算符去连接字符串的话,因为是栈操作,所以很消耗性能,我们可以先放进数组里面,然后用join连接成字符串即可。

同时,字符串中以一个split操作刚好和join操作相反。

split是把字符串以什么方式分割成数组。


1. var str = ‘a-b-c-d’;
2. var arr = str.split(-‘);
3. console.log(arr); // a b c d
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

数组去重问题

这里有一个我们必须要掌握的方法——数组去重方法。

把数组中重复的元素去掉。


1. Array.prototype.unique = function () {
2.       var len = this.length,
3.       arr = [],
4.       obj = {};
5.       for (var i = 0; i < len; i++) {
6.             if (!obj[this[i]]) {
7.                   obj[this[i]] = 1;
8.                   arr.push(this[i]);
9.             }
10.       }
11.       return arr;
12. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这里我们运用了一个简单的哈希结构。当我们数组中的这个数据出现过一次之后,我们就在obj中将这个元素的值的位置标记成1,后面如果出现相同的属性值,因为这个位置已经是1了,所以就不会添加到新数组里面,从而达到了去重的效果。


1. var arr = [1,1 ,1 ,2 ,3 ,4 ,2, 5, 6, undefined, undefined, null, 9, null];
2. console.log(arr.unique());//[1, 2, 3, 4, 5, 6, undefined, null, 9]
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

ES5的数组方法

刚才我们介绍的那些常用的数组方法都是ES3中定义的,ES5中出现了一些新的方法。

• forEach

这个方法会改变原数组,它让数组中的元素从头到尾遍历一遍,每一个都调用一下我们在forEach里面传递的方法,中间不会停止。


1. var arr = [1, 2, 3, 4];
2. arr.forEach(function () {
3.       arr[i] += 1;
4. })
5. console.log(arr); // 2 3 4 5
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

• map

这个方法和forEach很像,只不过forEach会改变原数组,而map不会改变原数组,而是返回一个新的数组,它也是让传递一个指定的方法,让数组中的每一个元素都调用一遍这个方法。不过记得map方法最后有返回值。


1. var arr = [1, 2, 3];
2. var test = arr.map(function (x) {
3.       return x * x;
4. });
5. console.log(test); // 1 4 9
6. console.log(arr); // 1 2 3
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

• filter

这个方法是过滤的作用,它同样不会改变原数组,而是返回一个原数组的子集。我们同样会传递一个方法,每一个元素都会调用一下这个方法,但是只有返回true的元素才会被添加到新数组里面,返回false的不会被添加到新数组里面。


1. var a = [1 ,2 , 3, 4, 5];
2. var b = a.filter(function (x) {
3.       return x > 2
4. });
5. console.log(b); // 3 4 5
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

同时,filter()会跳过稀疏数组里面缺少的元素,它的返回数组总是稠密的。


1. var arr = [1,,,,,,3,4];
2. var b = arr.filter(function () {
3.       return true;
4. })
5. console.log(arr); // 1 3 4
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

• every 和 some

这两个方法是数组的逻辑判定,他们对数组应用指定的函数进行判定,返回true或者false。

every是如果每一个元素经过传递的方法的判定之后都返回true,那么最后才返回true。

some是只要有一个元素返回true,那么就返回true。


1. var arr = [1 ,2 ,3];
2. console.log(arr.some(function (x) {return x<3;})); true
3. console.log(arr.every(function (x) {return x<3;})); false
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

• reduce 和 reduceRight

reduce()和reduceRight()方法使用指定的函数将数组元素进行组合,最后变成一个值,reduce是从左向右,reduceRight是从右向左。有两个参数,第一个是方法,第二个是可选参数,即我们最后的这个值的初始值。

当我们没有设置初始值的时候,用数组的第一个元素的值作为初始值。不过当数组为空的时候,不带初始值就会报错。

当我们的数组只有一个元素并且没有指定初始值,或者有一个空数组并且指定一个初始值的情况下,reduce只是简单地返回那个值,而不会调用函数。


1. var arr = [1, 2, 3];
2. var sum = a.reduce(function (x, y) { return x + y}, 0);// 0 + 1 + 2 + 3 = 6;
3. var temp = [1];
4. var temoOut = a.reduce(function (x, y) {return x x y}); // 1 不会调用这个函数,因为数组只有一个值,除非我们设置一个初始值
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

数组类型的检测

在ES5中,我们有一个isArray()方法来检测是否是数组。

但是在es5之前,我们要检测数据是否是数组类型是很麻烦的。

typeof运算符,数组和对象都会返回object,因此无法区分数组和对象。

constructor和instanceof操作符到现在为止是好用的,但是它们都存在潜藏问题:

我们的web浏览器中可能有多个窗口或者窗体,每一个窗体都有自己的js环境,有自己的全局对象。并且,每个全局对象有自己的构造函数,因此一个窗体中的对象将不可能是另外窗体中的构造函数的实例。窗体之间的混淆并不常发生,但这个问题已经证明constructor和instanceof都不能真正可靠的检测数组类型。

这个时候我们就需要这样的代码来检测了:

Object.prototype.toString.call(arr) === ‘[object Array]’ 

这是最可靠的检测是否是数组类型的方法。

今天的知识点就到这里哟~

Web前端的知识之旅哟——对象的枚举与this

Posted on 2018-01-02

命名空间

在进入正文之前,我们先提一下命名空间的概念。

由于我们的一个项目是很多人开发的,每个人都有一套自己的变量,为了防止人和人之间变量的互相干扰,我们经常把自己的变量都放到相对应的模块里面,模块和模块之间是没有关系的,这样就算变量名相同也不会互相干扰。我们经常用对象来实现这种模块的作用。


1. var spacename = {
2.       department1: {
3.            person1: {},
4.            person2: {},
5.            ……
6.       },
7.       department2: {
8.            person1: {},
9.            person2: {},
10.            ……
11.       }
12.       ……
13. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

对象的枚举

在介绍对象的枚举方法之前,我们要理解一个概念。

查看对象属性我们知道可以用obj.name这样的点操作符来查看,但是我们还有一种其他的方式:

obj[‘name’]

这种方法类似数组的查看,但其实事实是数组模仿了对象的查看方式。

这种方法是在系统底层里面的查看对象属性的写法,我们常用的点操作符obj.name在系统底层其实也转化成了obj[‘name’]这种形式。

下面来介绍一下对象的枚举方法:

for-in操作符

我们知道要枚举一个数组的所有元素,只要用一个for循环从头到尾遍历一遍就可以了。

但是对象并不能用for循环来遍历属性,所以这里我们就要使用for-in操作了。


1. var obj = {
2.       name: ‘scarlett’,
3.       age: 18,
4.       sex: ‘female’
5. }
6. for(var prop in obj) {
7.       console.log(prop + ‘: ‘ + obj[prop]);
8. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

我们for-in循环会按照属性的顺序取出属性名然后 赋给prop,所以我们打印的prop都是属性名,obj[prop]则是相对应的属性的值。

注意:这里我们不能写成obj.prop的形式,因为在系统底层会转化成obj[‘prop’]的形式,但是我们并没有prop这个属性,它应该是一个变量,所以会打印出来undefined,这里我们必须使用obj[prop]来调用。

在es3和es5的非严格模式下,for-in循环都会把原型里面的属性一起打印出来,es5的严格模式不会。

下面我们来介绍三种操作符:

1.hasOwnProperty

这个操作符的作用是查看当前这个属性是不是对象自身的属性,在原型链上的属性则会被过滤掉。如果是自身的,就返回true,否则返回false。


1. function Person () {
2.       this.name = ‘scarlett’
3. }
4. Person.prototype = {
5.       age:18
6. }
7. var oPerson = new Person();
8. for(var prop in oPerson) {
9.       if (oPerson.hasOwnProperty(prop)) {
10.             console.log(oPerson[prop];
11.       }
12. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这样,我们的for-in循环就会只打印自身的name属性而不会去打印原型上的age属性。

2.in操作符

这个操作符的作用是查看一个属性是不是在这个对象或者它的原型里面。


1. ‘name’ in oPerson; // true
2. ‘age’ in oPerson; // true
3. ‘sex’ in oPerson; // false
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

3.instanceof操作符

这个操作符的作用是查看前面的对象是不是后面的构造函数构造出来的,和constructor挺像的。


1. oPerson instanceof Person; // true
2. oPerson instanceof Object; // true
3. {} instanceof Object; // true
4. {} instanceof Person; //false
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

也可以理解为:后面的构造器的原型是否在前面对象的原型链上。

this

1.预编译过程中 this–>window

2.全局作用域里面 this–>window

3.call/apply可以改变this指向

4.obj.func() func()里面的this指向obj


1. var obj = {
2.       height:190,
3.       eat: function () {
4.             this.height++;//eat在没有执行之前,谁也不知道this指向谁
5.       }
6. }
7. obj.eat();//后面的方法,谁调用它,里面的this就指向谁。
8. eat.call(obj);//eat的this指向obj
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这一些就是name的全部知识点,当我们能够理解下面这一段样例代码中的this的指向问题了,那么大家应该就掌握了this的所有问题了。


1. var name = ‘222’;
2. var a = {
3.       name:‘111’,
4.       say:function () {
5.            console.log(this.name);
6.       }
7. }
8.  
9. var fun = a.say;
10. fun(); 
11. a.say(); 
12. var b = {
13. name: ‘333’,
14. say: function (fun) {
15.             fun();
16.       }
17. }
18.  
19. b.say(a.say); 
20. this–>b;
21. fun();预编译过程 fun里面的this指向window
22. b.say = a.say;
23. b.say();
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

第一处打印调用fun(),这里其实就是把a.say这个函数的函数体赋给了fun这个函数,相当于在全局空间下写了一个func函数,里面的代码就是a.say里面的代码,所以此时的this指向window,因此打印222。

第二处打印调用了a.say(),按照我们前面说的谁调用函数,函数里面的this就指向谁,因此这里的this指向a,所以打印111。

第三处比较复杂,它调用了b.say(a.say))。这里其实是吧a.say这个函数体替换了原本b中fun的地方,我们在调用b.say()这个方法的时候,里面的this是指向b的,但是这个this并不在fun里面而是在say里面


1. say: function (fun) {
2.       this;// b
3.       fun();
4. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

其实的结构应该是这样的,所以fun里面的this和我们所想的指向b的那个this不是一个东西,fun里面的this是在预编译阶段的指向window的,因此这里也打印222。其实和第一种直接在全局调用a.say的函数体方法差不多。

第四个b.say = a.say; b.say();其实也第二种一模一样的意思,不过此时的this指向b而已,因此打印333。


1. var name = ‘222’;
2. var a = {
3.       name:‘111’,
4.       say:function () {
5.            console.log(this.name);
6.       }
7. }
8. var fun = a.say;
9. fun();  // 222
10. a.say();  // 111
11. var b = {
12. name: ‘333’,
13. say: function (fun) {
14.             fun();
15.       }
16. }
17. b.say(a.say);  // 222
18. this–>b;
19. fun();预编译过程 fun里面的this指向window
20. b.say = a.say;
21. b.say(); // 333
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

Web前端的知识之旅哟——继承

Posted on 2018-01-02

this的一些问题

前面我们知道函数内部的this默认的指向是全局对象window,那么我们有什么办法可以改变this的指向吗?

现在我们就可以使用call/apply来改变this指向。

call/apply

作用:改变this指向。

区别:后面传的参数形式不同。

我们现在写一个函数


1. function person () {
2.       this.name = ‘scarlett’;
3.       console.log(this);
4. }
5. person(); // window
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这个时候this指向window,name属性自然也就是window上面的全局属性。

现在我们来尝试使用一下call,看看有什么效果。


1. var obj = {};
2. person.call(obj); // Object {name: “scarlett”}
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

我们可以看到,当使用了call方法之后,person函数内部的this就指向了我们传递的这个对象。现在这个函数就可以为obj来赋属性值了。

如果这个函数还有参数的话,我们只要把实参的值写在call后面并且用逗号间隔开就可以了。


1. function person(name, age) {
2.       this.name = name;
3.       this.age = age;
4. }
5. var obj = {};
6. person.call(obj, ‘scarlett’, 18);
7. console.log(obj.name); // scarlett
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

apply和call基本没有区别,唯一的区别是call后面的参数是一个一个传的,而apply后面的参数是放进一个数组里面然后传进去的。

还是上面那个例子,如果用apply来传递实参的话,将是下面这种形式。


1. person.apply(obj, [‘scarett’, 17]);
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

继承

继承是这篇文章的重点部分。

首先介绍一下继承发展史。

第一阶段:传统形式。

这个阶段使用的继承方式就是我们上一篇文章介绍的那一种原型链式的继承。

但是这种继承有一个缺点就是它会继承过多没有用的属性,造成大量的浪费。

第二阶段:借用构造函数。


1. function Foo(name, age) {
2.       this.name = name;
3.       this.age = age;
4. }
5. function Son(name, age) {
6.       Foo.call(this, name, age);
7. }
8. var son = new Son(‘son’, 123);
9. console.log(son.name); // son
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这种方式就是利用了call和apply可以改变this指向的特点,通过构造函数来间接的构造子对象。

但是这种方式有两个缺点:

1.严格来说,这种方式不属于继承,也访问不了原型的原型。

2.每次构造一个对象都要走两个构造函数,效率很低。

第三阶段:共享原型。


1. Son.prototype = Foo.prototype;
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这种方式就是让父子构造函数的原型都一样,虽然这种方法让子构造函数可以访问原型链,也不用走两个构造函数了,但是缺点也很明显:没办法改变子类的原型,一改就两个一起改了。

第四阶段:圣杯模式。

这个阶段也是最终阶段,当然也是我们目前使用的方式。


1. function inherit (C, P) {
2.     function F () {}
3.     F.prototype = P.prototype;
4.     C.prototype = new F();
5. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这里我们利用了一个中间函数F来沟通P和C的原型,这样我们改变C的原型的时候只会影响F而不会影响P,而F这里并没有任何的用处。


1. function Child () {}
2. function Parent () {}
3. Parent.prototype.name = ‘scarlett’;
4. inherit(Child, Parent);
5. console.log(new Child().name); //scarlett
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

但是现在我们还存在一个问题,当我们想查看Child构造出来的对象的构造函数的时候,它并不打印Child函数而是Parent函数。


1. console.log(new Child().constructor); //function Parent () {}
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

所以我们前面的工作还不完善,还需要记录子类的构造函数。


1. C.prototype.constructor = C;
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

同时我们为了保存一下它的父类,也用一个uber来记录一下父类。


1. C.prototype.uber = P; // 因为super是保留字我们不能使用,所以用了一个uber
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

所以我们最终的形式就变成了这个样子:


1. function inherit(C,P){
2. function F () {}
3.       F.prototype = P.prototype;
4.       C.prototype = new F();
5.       C.prototype.constructor = C;
6.       C.prototype.uber = P;
7. }
8. console.log(new Child().constructor);// Child
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

不过,这种写法虽然成功了,但是还是有一点点小瑕疵,这里面的F函数只是起到一个搭桥的功能,并没有什么实质性的作用,那么我们能不能舍弃掉这个东西呢?

虽然真的舍弃掉是不可能的,但是我们可以通过一定的方法来规避这个函数,让他不出现在我们的继承函数中。


1. var inherit = (function (){
2.       var F = function(){};
3.       return function (C,P){
4.             F.prototype = P.prototype;
5.             C.prototype = new F();
6.             C.prototype.constructor = C;
7.             C.prototype.uber = P;
8.       }
9. }());
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这样通过立即执行函数和闭包,我们既可以使用F函数,又可以不让它出现在我们的真正的继承函数中(即返回的那个函数)。

这种方法很重要,希望大家都记这一种方法!

 

Web前端的知识之旅哟——(补之前遗漏的)函数与作用域与闭包

Posted on 2018-01-02

写完预编译之后才发现忘记写关于函数和闭包部分的知识点了……所以这里补上。

函数部分

我们的函数声明有两种方式:

1.var demo = function () {} 函数表达式

2.function demo () {} 函数声明

3.var demo = function xxx() {} 命名函数表达式

其实第一种和第三种相比较,第一种叫做匿名函数表达式,但是平时我们基本都使用第一种,很少使用第三种,因此第一种我们就简称为函数表达式了。

• 每一个函数里面都有一个类似数组的类数组属性arguments,这个属性里面存的就是实参。

arguments[0]就可以查看我们传递的第一个实参了。

函数有一个length属性,这个length储存的是形参的数量。

• 每一个函数都会有一个return,如果不写的话函数会自动加上一个return;

return的功能有两个:

1.返回这个函数的执行结果。

2.终止函数的执行。


1.  
2. function test (a, b) {
3.    console.log(a + b);
4.    return ;
5.    console.log(‘hello’);
6. }
7. test(1, 2); // 3 没有打印hello
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

作用域

定义:变量(变量作用域又称为上下文)和函数生效(能被访问)的区域。

javascript的函数,是可以产生作用域的!!

es5中的作用域大概只有全局作用域和函数作用域两种,es6中新添加了块级作用域。


1.  
2. var demo = 123; //全局变量
3. function test () {
4.   var demo = 234;// 局部变量
5.   console.log(demo);
6.   var demo1 = ‘hello’;
7. }
8. test();//234  就近打印局部变量,没有局部变量的时候才会打印全局的。
9. console.log(demo1); // 报错 我们的全局作用域无法访问函数的局部作用域
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这个函数作用域就好像一个屋子,我们在屋子里面能够用屋子外面的东西,但是外面却不能闯进屋子里面使用屋子里面的东西。

• 有一点要注意的是,如果在函数作用域里面声明变量没有用var的话,那么就声明了一个全局变量。

• 同时,两个不同作用域(除了全局作用域)之间是不能互相访问的。


1.  
2. function demo1 () {
3.       var str1 = ‘abc’;
4. }
5. function demo2 () {
6.       console.log(str1);
7. }
8. demo2(); // 报错
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

作用域链

既然函数存在函数作用域,函数又可以嵌套,那么作用域之间自然就会产生嵌套关系,这个时候就产生了作用域链。

当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)来保证对执行环境有权访问的变量和函数的有序访问。作用域第一个对象始终是当前执行代码所在环境的变量对象。


1.  
2. function demo () {
3. var demo_a = 1;
4. function test () {
5. var demo_a = 2;
6. console.log(demo_a);
7. }
8. test();
9. }
10. demo();
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

在这个例子中,demo1运行的时候,首先创建了一个demo的作用域,但是window本身还有一个全局作用域,这就让demo产生了一个作用域链。本着对执行环境的有权和有序访问,每个函数的自身的作用域总是在作用域链的最顶层,下一层是这个函数的父级函数的作用域,再下面是父级的父级的作用域,直到全局作用域。

因此这个例子中的test函数执行时候打印的demo_a是它本身的作用域中的demo_a,而不是demo函数作用域下的demo_a,如果test函数作用域中没有demo_a这个变量的话,系统才会沿着作用域链向下找到demo作用域中的demo_a变量。

闭包

闭包是一个非常非常重要的知识点,我在网上看到过许多的定义,但是都有点繁琐,这里的知识点是我简单化的总结的。

什么是闭包?

我的理解是,闭包就是能够读取其他函数内部变量的函数。

我们前面提到过,不同作用域之间不能够互相访问,但是我们如果在一个函数内部再定义一个函数,并且这个内部函数与外部函数的变量有关联,那么我们就可以通过返回这个内部的函数,然后来访问外部函数里面的变量。

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。


1.  
2. function a () {
3.       var demo1 = 123;
4.       add = function () {
5.             demo1++;
6.       }
7.       return function () {
8.             console.log(demo1);
9.       };
10. }
11. var demo = a();
12. demo(); // 123
13. add();
14. demo(); // 124
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

当函数执行完之后,函数的执行上下文就会被销毁,自然我们就无法访问里面的变量了,但是我们这个函数返回了一个依赖于这个函数的新函数,也就是说这个没有被销毁的新函数的作用域链中还存在着对原本函数的作用域的引用,就导致我们原本的函数的上下文不会被销毁,我们称返回的这个新函数是原本函数的闭包函数。

在上面的例子中,a函数内部有一个全局的函数add和一个局部变量demo1,我们这个把返回函数给了一个全局变量demo进入到了内存中,但是由于这个返回的新函数依赖于本来的a函数,这就导致本来的a函数的上下文不会被销毁。

这里我们的打印函数一共运行了两次,都能打印出来值,说明a函数的demo1变量在函数执行完之后并没有被销毁而是存到了内存中。

其次,add的值是一个匿名函数,而这个匿名函数本身也是一个闭包,所以add相当于是一个setter叠加器,可以在函数外部对函数内部的局部变量进行操作。

使用闭包的注意点

1.由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。

解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2.闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便改变父函数内部变量的值。

这里我们再总结一下什么是闭包:

当内部函数在定义它的作用域的外部被引用时,就创建了该内部函数的闭包 ,如果内部函数引用了位于外部函数的变量,当外部函数调用完毕后,这些变量在内存不会被释放,因为闭包需要它们。

当我们能理解下面这个代码的时候,差不多就理解了闭包了:


1.  
2. var name = ‘global’;
3. var obj = {
4.       name: ‘obj’,
5.       getName: function () {
6.             return function () {
7.                   console.log(this.name);
8.             }
9.       }
10. }
11. obj.getName()(); // global
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

累加器的例子:


1.  
2. function a () {
3.        var num = 1;
4.       function addNum () {
5.             num++;
6.             console.log(num);
7.       }
8.       return addNum;
9. }
10. var demo = a();
11. demo(); // 2
12. demo(); // 3
13. var demo1 = a();
14. demo1(); // 2
15. demo1(); // 3
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

立即执行函数

立即执行函数是解闭包的一个重要方法。但是注意闭包是没有办法解除的,我们只能通过另一个新闭包来消除上一个闭包的影响。

定义:立即执行函数不需要被定义,直接执行,执行完毕之后直接释放。

经常被用作初始化。

立即执行函数的写法:

1.(function (a) {})(num);

2.(function (a) {} (num))

传递的参数是a,a的实参值是num,num是我们在外面定义的变量。

这两种写法功能完全一样,但是标准一点的写法是第二种。

• 这里有一个小知识点:函数声明不能被执行,但是函数表达式可以被执行。


1.  
2. function test() {} ();// 错误
3. var test = function () {} ();// 这个是可以被执行的
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

第一种加一个括号变成立即执行函数就可以执行了。


1.  
2. (function () {} ());  // 正确
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

因为这种写法不是函数声明,所以不需要函数名字,因此就成为了立即执行函数。

立即执行函数的应用

现在我们有一个要求:我们现在有一个函数returnB,这个函数内部有一个数组arr,这个arr每一个值存的是一个函数并且函数的作用是输出相对应的数组下标值。

我们一开始的写法可能是这个样子的:


1.  
2. function returnB() {
3.       var arr = [];
4.       for (var i = 0; i < 10; i ++) {
5.             arr[i] = function () {
6.             console.log(i);
7.             }
8.       }
9.       return arr;
10. }
11. var save = returnB();
12. for (var i = 0; i < 10; i ++) {
13.       savei;
14. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

但是最后会输出10个10,并不是我们所想的0-9。

这是因为我们打印i的这一行代码所在的函数的自身作用域中不存在i这个变量,而在它的父级函数returnB中才有i这个变量,这个打印函数是一个闭包函数,而且这arr[0]-arr[9]这10个值都是这一个闭包函数。因此当for循环执行完i变成10之后,我们开始后面的依次触发每一个save[i]函数的时候,函数寻找的i是作用域链中的第二层作用域,也就是父级函数returnB的作用域,而在这个作用域中的i都已经变成10了。

这个时候10个打印函数都共用一个i值。

那么如何更改可以让它输出0-9呢?这里就需要利用我们的立即执行函数来产生新的闭包以消除我们共用一个闭包值导致的问题。(注意不是消除闭包)


1.  
2. function returnB() {
3.       var arr = [];
4.       for (var i = 0; i < 10; i ++) {
5.             (function (n) {
6.                    arr[n] = function () {
7.                           console.log(n)
8.                    };
9.             }(i))
10.       }
11. return arr;
12. }
13. var save = returnB();
14. console.log(save);
15. for (var i = 0; i < save.length; i ++) {
16.       savei;
17. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

我们立即执行函数执行之后,会产生一个新的作用域,我们把i的具体的0-9这10个数分别作为参数传了进去,也就是说每一个作用域里面的的n都是不一样的。里层的打印函数因为没有n值,所以要向上找立即执行函数的作用域里面的n值。这样最后就会产生10个闭包,因为每一个立即执行函数都是一个新作用域。每一个闭包都是利用刚才立即执行函数的变量n,而每一个立即执行函数的变量n都是不同的,这样就解决了刚才共用闭包导致都打印10的问题了。

今天的闭包和立即执行函数其实很重要的哟~希望对大家能有所帮助!

Web前端的知识之旅哟——原型与原型链

Posted on 2018-01-02

原型

1.原型的定义:原型是function对象的一个属性,它定义了构造函数制造出来的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。


1. function Person() {} 
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

我们先定义一个构造函数,Person.prototype这个属性就是这个构造函数的原型,这个属性是天生就有的,并且这个属性的值也是一个对象。

我们可以在prototype上面添加属性和方法,每一个构造出来的对象都可以继承这些属性和方法。


1. Person.prototype.name = ‘scarlett’;
2. Person.prototype.age = 17;
3. var oPerson = new Person();
4. console.log(oPerson.name); // scarlett
5. console.log(oPerson.age); // 17
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

虽然每一个对象都是独立的,但是他们都有共同的祖先,当我们访问这个对象的属性的时候,如果它没有这个属性,就会向上找到它的原型,然后在原型上访问这个属性。


1. Person () {
2.       this.money = 100;
3. }
4. Person.prototype = {
5.       money: 200;
6. }
7. var oPerson = new Person();
8. console.log(oPerson.money); // 100
9. delete oPerson.money;
10. console.log(oPerson.money); // 200
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这里我们oPerson对象因为自身有一个money属性,所以就不会到原型上去寻找money属性,而是查询自身的money属性,因此打印的是100,但是当我们删除了自身的money属性之后,它就会到原型上去寻找money这个属性,因此就打印200。


1. oPerson.money = 1000;
2. console.log(oPerson.money); // 1000
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

当我们再次给对象添加属性之后,打印money属性就是自身的属性。

2.利用原型特点和概念,可以提取公有属性。

我们可以把每一个对象都有的公有属性不写在构造函数里面,而是提取到原型上,这样当我们用构造函数构造大量的对象的时候就不需要走多次构造函数里面的赋值语句了,而只需要走一遍,每一个对象调用属性的时候直接上原型上查找就可以了。

3.对象如何查看原型

前面我们提到了构造函数可以通过.prototype的方法来查看构造函数的原型,那么我们怎么查看对象的原型呢?

我们前面提到过用构造函数构造对象的时候,会隐式创建一个this对象,这个this对象里面有一个默认的属性叫做proto属性,这个属性的值就是指向的这个对象的原型。


1. var this = {
2.       // xxx
3.       __proto: Person.prototype; // 刚才Person构造函数的例子
4. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

当查找的属性是自身没有的属性的时候,就会先查找proto这个属性,然后这个属性指向了原型,所以就到原型上面继续查找属性了。


1. Person.prototype = 100;
2. oPerson.proto.money // 100
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

注意:prototype是函数的属性,proto是对象的属性。

4.对象如何查看构造自身的构造函数

在prototype里面,有一个隐式的属性叫做constructor,这个属性记录的就是对象的构造器,里面存的就是构造函数。

console.log(oPerson.constructor); // Person();

原型链

有了原型,原型还是一个对象,那么这个名为原型的对象自然还有自己的原型,这样的原型上还有原型的结构就构成了原型链。


1. Gra.prototype.firstName = ‘scarlett’;
2. function Gra () {
3.       this.name = ‘grandfather’;
4.       this.sex = ‘male’;
5. }
6. var grandfoo = new Gra();
7. grandfoo.word = ‘hello’
8. Foo.prototype = grandfoo;
9. function Foo () {
10.       this.age = 18;
11.       this.money = 100;
12. }
13. var father = new Foo();
14. function Son () {
15.       this.name = ‘son’;
16. }
17. Son.prototype = father;
18. var son = new Son();
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

Foo创造出来的每一个对象都继承自grandfoo这个对象,son的每一个对象都继承自father这个由Foo创造出来的对象,这样son就可以继承上面Foo和Gra的所有属性。

当我们查找son上的属性的时候,如果son自身有属性,那么就打印出来,如果没有,就向上查找原型father,如果father上面还有这个属性,那么继续向上查找grandfoo,如果有就输出,如果没有就返回undefined。


1. console.log(son.name) // son
2. son.money //100
3. son.age// 18
4. son.sex = ‘male’;
5. son.firstName // scarlett
6. son.word // hello;
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这种链式的查询结构就叫做原型链。

那么原型链有没有终点?我们写的Gra是不是这个原型链的终点?


1. console.log(Gra.prototype); // Object{‘name’: ‘scarlett’}
2. console.log(Gra.prototype.proto); //Object{}
3. console.log(Gra.prototype.proto.proto); // null
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

有测试可以看出,其实我们的Gra.prototype上面还有原型,这个原型是一个空对象,这个空对象上面就没有了。

其实,绝大部分的对象最终都会继承自Object.prototype这个对象。


1. var obj = {};
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

我们没有规定原型的对象,它们的原型就是Object.prototype。


1. var obj = new Object();
2. console.log(obj.proto); // Object{}
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

由此可见,原型链的终点一般是Object.prototype;

但是并不是所有的对象都有原型。

我们上一篇提到过第三种构造对象的方法,使用Object.create方法。

Object.create()方法需要写一个参数,这个参数就是我们这个对象的原型。如果我们想要构造和var obj = {};一样的空对象,那么就需要写:


1. var obj = Object.create(Object.prototype);
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

当然,我们也可以写一个自定义的属性,让它成为原型。


1. var obj = Object.create({name: ‘scarett’);
2. console.log(obj.name); //scarlett
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

但是,当我们写参数为null的时候,我们就构造出来了一个没有原型的对象。


1. var obj = Object.create(null);
2. console.log(obj.proto); // undefined
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

undefined null也都没有原型。它们之所以能打印出来,是因为不调用任何方法的,直接打印出来。

原型链上属性的增删改查

其实我们前面一直在使用着这些方法,这里说一下原型上的引用值。当我们通过一个对象改变了原型上的引用值类型的属性的话,那么所有对象的这个属性的值都会随之更改。


1. Person.prototype.arr = [1, 2, 3];
2. var person1 = new Person();
3. var person2 = new Person();
4. person1.arr.push(4);
5. console.log(person2.arr); // 1 2 3 4
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

删除


1. Person.ptototype.name = ‘father’;
2. Person () {
3.       this.name = ‘son’;
4. }
5. delete person.name;
6. person.name// father
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这个时候person对象上面没有了name属性,那么依据我们前面说的当自身没有这个属性的时候就会向原型查询这个属性的说法,我们再次删除这个属性是不是就可以删除原型上的属性了?


1. delete person.name;
2. person.name // father
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

然而事实并没有,由此可见,对象并不能删除原型上的属性。


1. Person.prototype.name = ‘scarlett’;
2. Person.prototype.sayName = function () {
3.       console.log(this.name);
4. }
5. function Person() {
6.       this.name = ‘son’;
7. }
8. var oPerson = new Person();
9. oPerson.sayName();// son
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这里提一下这个this的指向。谁调用这个方法,这个方法中的this就指向这个调用它的对象。

所以这里的this指向的是oPerson这个对象,这个对象的name属性值是son。

那么今天的知识点就介绍到这里哟~

 

Web前端的知识之旅哟——对象与构造函数与包装类

Posted on 2018-01-02

我们前面已经接触过对象这一种数据,这一篇着重介绍一下对象的有关知识哟~

对象创建方法

对象的创建方法有三种:

1.对象字面量

我们前面的对象创建方法都是使用的字面量的方法创建的。


1. var obj = {};
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这样的方式就叫做字面量,也是我们创建对象最简单最常用的方法。

对象里面有属性,属性之间用逗号分隔,每一条属性都有属性名和属性值,属性名和属性值之间用分号分隔。

2.构造函数

构造函数也分两种:系统自带的构造函数和我们自定义的构造函数。

• 系统自带的构造函数

创建对象的构造函数是Object()。


1. var obj = new Object();
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

通过这条语句,我们就创建了一个空对象。

它的作用和var obj = {};的作用是一样的。

系统自带的构造函数还有很多,比如Number()、String()、Boolean()、Array()这些都是构造函数。

• 自定义构造函数

自定义的构造函数是我们平时最常用的一种构造函数。

构造函数也是正常的函数,我们为了区分它和别的正常函数,把构造函数的首字母大写。


1. function Person() {}
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

上面就声明了一个构造函数Person。

有了构造函数之后,我们就可以用new操作符来创建对象了。


1. var oPerson = new Person();
2. typeof oPerson; // object
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这样我们也创建了一个对象oPerson,不过现在这个对象是空对象,因为我们的构造函数什么都没有写,我们也没有给这个对象添加任何属性。

另外,用new操作符创建出来的对象,尽管都是使用的同一个构造函数,但是之间是没有关联的。


1. var person1 = new Person();
2. var person2 = new Person();
3. person1.name = ‘111’;
4. console.log(person2.name); // undefined
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

person1 和 person2 之间没有关系,它们两个是单独的对象。

我们可以在构造函数里面写一些对象天生就有的默认属性。


1. function Person () {
2.       this.name = ‘scarlett’,
3.       this.age = 17
4. }
5. var person = new Person();
6. person.name; // scarlett
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

当然构造函数既然是函数,那么就可以传参数。


1. function Person (name, age) {
2.       this.name = name;
3.       this.age = age;
4. }
5. var person = new Person (‘scarlett’, 18);
6. person.age; // 18
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

创建对象的时候,只有new才会有this。

这里有一个重点,为什么我们通过new操作符可以创建互相独立的对象呢?

其实,当我们用new操作符的时候,这个new在我们的构造函数里面隐式创建了一个this对象,并且最后返回了这个this对象,这也就是为什么我们通过new可以最后创建一个对象的原因了。


1. function Person (name) {
2.       //var this = {};
3.       this.name = name;
4.       //return this;
5. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

如果我们在构造函数首行手动创建一个对象,比如that对象,然后最后返回了that,那么里面的this就没有用了,我么要为属性赋值就要用that了。


1. function Person (name) {
2.       var that = {
3.             name: ‘scarlett’
4.       }; 
5.       that.name = name;
6.       return that;
7. }
8. var person = new Person(‘demo’);
9. person.name; // demo
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这样默认的name值就是scarlett。

重点:如果我们最后返回的对象,那么this就失效,但是如果最后显示返回的是原始值,那么this还是有效的。


1. function Person () {
2.       var that = {};
3.       that.name = ‘that’;
4.       this.name = ‘this’;
5.       return 123;
6. }
7. var person = new Person();
8. person.name; // this
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

3.Object.create(原型)

这种方法涉及到原型的知识点了,我们放在后面介绍原型的时候再介绍。

属性的增删改查

其实属性的修改我们前面也都接触过了。

1.增


1. var obj = {};
2. obj.name = ‘scarlett’;
3. obj.name; // scarlett
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

我们可以通过对象名+点+属性名的方法来给对象添加新的属性并且赋值。

这个时候如果我们调用obj.age属性的话,这个属性并不存在,但是obj这个对象是存在的,因此浏览器不会报错,只会打印undefined。

2.改

修改的操作和增加的操作其实是一样的,只要调用相同的属性名然后赋一个新的值就可以了。


1. var obj = {
2.       name: ‘scarlett’
3. }
4. obj.name = ‘demo’;
5. obj.name; // demo
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

3.查

其实我们前面一直都在使用查看属性的功能…

obj.name = ‘demo’;

console.log(obj.name);//demo

这就是查看属性的方法。

4.删

删除属性的操作我们需要借助delete操作符,这个操作符的作用就是来删除属性的。


1. var obj = {
2.       name: ‘’scarlett‘
3. }
4. obj.name; // scarlett
5. delete obj.name;
6. obj.name; // undefined
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

包装类

我们前面提到过,原始值是不可以改变的,只有对象才有属性和方法,那么这个又是个什么东西?


1. var str = ‘abcd’;
2. console.log(str.length); // 4
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

按理说这个字符串原始值是没有属性的,但是这里确实可以查看length这个属性。

这里就涉及到一个叫做包装类的东西了。


1. str.length
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

我们在调用执行这一行代码之前,程序会自动把str包装成一个字符串对象,在这一行代码执行完毕之后再销毁这个字符串对象。


1. var str = ‘abcd’;
2. // var str1 = new String(‘abcd’);
3. str.length; // str1.length
4. // 消除str1
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这里的str.length的str其实是上一句的包装好的str1,str1是对象,上面有属性和方法,然后就可以打印length属性了,在执行完str.length这行代码之后,str1这个对象就被销毁了。

这也就是为什么我们在执行str.length = 2;这句话之后,再打印str.length;还是4的原因了。


1. var str = ‘abcd’;
2. // var str1 = String(‘abcd’);
3. str.length = 2; // str1.length = 2;
4. //销毁str1
5. console.log(str); // abcd 长度还是4
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

系统在执行str.length = 2这句话的时候,先创建了一个String对象,这个对象的值是‘abcd’,我们改变的length其实是这个隐式创建的对象的length值,并不是改变了我们的原始值str,当str.length = 2;这句代码执行完之后,隐式创建的字符串对象就被销毁了,所以我们后面打印的str.length还是4。


1. var str = ‘abcd’;
2. str.len = 2;
3. console.log(str.len); // undefined
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

其他类型的数据也是一样的,当我们给原始值加属性的时候,都是先隐式包装成对象,然后赋完属性值之后再销毁这个对象。


1. var bool = true;
2. bool.len = 4;
3. console.log(bool.len); // undefined
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

今天的知识点就介绍到这里哟~

1234

John Doe

31 posts
© 2018 John Doe
Powered by Hexo
|
Theme — NexT.Muse v5.1.3