前言
前端世界如此喧嚣,能进阶的何其稀少。大家好,你们的闰土哥在沉寂了数月之后又回来了!(此处应有掌声~~~)
前段时间在群里关于“闰土去哪儿了”的话题,让我既欣喜又尴尬。欣喜的是大家还记得我,尴尬的是我竟不知道该如何回复。原因有二,一是我换了家公司继续撸代码,二是那段时间我沉淀了一下自己的技术和生活。闰土这次回来是带着满满的干货想与大家分享,如果你在这里能学到一点点知识,收获一丝丝感悟,那闰土便知足了。(YY:按照惯例,此刻该听到搬小板凳的声音了,嘻嘻~)
正文
开门见山,这次闰土要讲讲JavaScript进阶。废话不多说,先拿变量开刀。
在我们前端日常的JavaScript编码中,总避免不了声明变量。那变量是什么呢?我们前端人员都知道,变量其实就是一个容器,用来存放各种不同的数据类型的值,包括基本类型值和引用类型值。基本类型值有五种,参加过前端面试的人想必都能倒背如流,分别是Undefined、Null、Boolean、Number和String。
定义基本类型值和引用类型值的方式是类似的,就是创建一个变量并为该变量赋值。先来看看下面的栗子:
var person = new Object();
person.name = 'runtu';
console.log(person.name); // "runtu"
在上面的例子中我们创建了一个对象并将其保存在了变量 person 中。然后,我们为该对象添加了一个名为 name 的属性,并将字符串值“runtu”赋给了这个属性。紧接着,又通过console.log()函数访问了这个新属性。如果person这个对象不被销毁或者这个属性不被删除,那么这个属性将一直存在。
也就是说,对于引用类型的值,我们可以为其添加/修改/删除属性和方法,但是我们不能给基本类型的值添加属性,尽管这样做不会导致任何错误(我们建议不这么写,因为写了也没用 Orz)。
接下来我们再聊聊复制变量值。
先来看看下面的栗子:
var age1 = 26;
var age2 = age1;
在以上代码中,age1中保存的值是26,是基本类型值。当使用age1的值来初始化age2时,age2中也保存了值26,但该值只是age1的一个副本,所以,这两个变量可以参与此后任何操作而不会相互影响。
看完了复制基本类型值,我们再来看下一个栗子:
var person1 = new Object();
var person2 = person1;
person1.name = 'runtu';
console.log(person2.name); // "runtu"
在这里,变量person1保存了一个对象的新实例。然后这个值被复制到了person2;换句话说,person1和person2都指向同一个对象。这样一来,当为person1添加name属性后,person2也可以访问到这个属性。
到这里,我们就可以适当的总结一下,当一个变量复制另一个变量的引用类型值时,这个值的副本其实是一个指针,而这个指针则指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响到另一个变量。
接下来我们讲讲传递参数,重点来了,该划重点的划下,这是必考题。
函数传参,相信很多前端老司机都已经耳熟能详了,可能更多的前端新人小白们还是懵懵懂懂的,这里我说下。在ECMAScript中所有函数的参数都是按值传递的,也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样,原理是一样的。
接下来这句话可能有点绕,基本类型值的传递和基本类型变量的复制一样,同样的,引用类型值的传递和引用类型变量的复制是一样的。有不少工作了两三年的前端er在这个点上可能也会感到困惑。为什么呢?这个知识点可能比较晦涩难懂,因为像咱们平时访问变量有按值和按引用这两种方式,而参数只能按值传递。照样举个栗子:
function addSix(num){
num += 6;
return num;
}
var count = 20;
var result = addSix(count);
console.log(count); // 20,没有变化
console.log(result); // 26(有没有感觉这个数字出现频率较高,嘻嘻~)
这里的函数addSix()有一个参数num,而参数实际上是函数的局部变量。在调用这个函数时,变量count作为参数被传递给函数,于是数值20被复制给参数num。在函数内部,参数num的值被加上了6,但是这一变化不会影响外部的count变量,参数num和变量count素昧平生互不相识。假如num是按引用传递的话,那么变量count的值也将变成30,从而反映函数内部的修改。
当然使用数值等基本类型值来说明按值传递参数比较简单,但如果使用对象,那么问题就不那么浅显易懂了。闰土再举一个栗子:
function setName(obj){
obj.name = 'runtu';
obj = new Object();
obj.name = 'shaonian';
}
var person = new Object();
setName(person);
console.log(person.name); // runtu
有小白会问:console出来的怎么不是 “shaonian” 呢?
这是一个很经典的问题,你想如果person是按引用传递的,那么person就会被自动修改为指向其name属性值为“shaonian”的新对象。但是,重点来了。当接下来再访问person.name时,显示的值仍然是“runtu”。这说明即使在函数内部修改了参数的值,但原始的引用仍然保持不变。实际上当在函数内部重写obj时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。
所以,结论就是,所有的函数传参都是按值传递的。(又该划重点了,咳~咳~)
备注:本文参考红宝书,如有雷同,纯属拷贝。