浅析赋值表达式–JS基础核心之一

赋值表达式,编程中最基础的内容,但也是很重要的一个内容,其他的编程语言暂且不说,这里就以JS作为实例,来说明一下赋值表达式的重要性,如何才能更好的以赋值表达式的概念,来解释一些相对复杂而又难以想象的问题。

表达式

按我当前对JS的理解,JS就是完全可以分为两种状态:函数,以及表达式。这种说法并不权威,只是我个人对这个的看法。

之所有分为函数和表达式是根据JS的解析时间分的,函数会在预编译的时候进行解析,而表达式只有当执行到对应的JS代码时,才会解析。

函数这里不多说(主要是,对这个,还没有一个很好的认识,目前还不知道能写些什么内容。),这里就对表达式做一个简单的总结,也算是说明吧。

表达式的分类方面,可以简单的分为赋值表达式和非赋值表达式,比如以最简单的举例:


1          //这是一个非赋值表达式
var a = 1; //这是一个赋值表达式


赋值表达式是我们很常用的吧,它的重要性是可以完全理解的,如果赋值表达式没用,那么我们上一篇中,全局变量和局部变量也就变得没用任何意义了。非赋值表达式的重要性也可以不说吧,在我们使用各类判断条件时,一般情况下,都是使用的非赋值的表达式,比如if,while等。

赋值表达式

赋值表达式是什么,这里就没有必要多说了吧,所以就说一下,一些常见的问题吧,而这些常见的问题出现的根本原因,就是因为,它们是赋值表达式。

对于赋值表达式,有几个问题我们是必须要知道的:(这里不考虑作用域的问题,就是所有分析都按照全局作用域分析):这里因为是在讨论赋值表达式,所有不考虑说一个变量没有定义却被引用的情况。


alert(A);  //undefined
var A = 1;


1:一个已经使用var定义的变量,如果在赋值之前引用这个变量,那么其值为undefined


var A = 1;
alert(A);  1


2:对于赋值表达式,只有在赋值之后,才能正常的引用,而不会导致不必要的错误

定义函数常用方法

定义函数最常用的方法有两种,一种叫做函数声明,一种叫做函数表达式。


function A(){}       //函数声明
var A = function(){};//函数表达式


对于函数声明的方式,不需要多说吧,在JS语句执行之前已经预编译,可以再任意位置使用。

对于函数表达式,说白了也就是赋值表达式呢,在进行赋值之前,都是存储了一个undefined的值,所以如果这个时候使用的话,就不能获取到理解的结果,只有在赋值语句执行之后,再使用这个时,才能获取到一个想要的结果。


console.log(A);      //undefined
var A = function(){}
console.log(A)       //function (){}


我们可以这样很简单的进行一下证明。当然,这里只是考虑没有其他的语句在这段代码之前对A进行过赋值。

理解到函数表达式,其实质就是一个赋值表达式,那么这两种定义函数的方法的区别,也就可以很容易的想到了吧。

原型链断链的原因–赋值表达式

这里想要偷个懒了,因为之前有写过一篇关于原型链断链原因的文章,里面也有叙述说原型链断链的原因就是因为赋值表达式,所以这里就不再重写文字了。

请参考:原型链断链的原因

原型链中属性值为引用类型导致问题的原因–赋值表达式

原型链有一个很不错的特性就是共享性:即在原型链中的属性和方法,可以被所有实例所共享。所以也导致了一个问题就是,如果原型链中的属性值是一个引用值,那么一个实例修改该引用值,那么所有的实例都会受到影响。


function Person(){
    this.name = "zhang";
}
Person.prototype.friends = [];

var aPerson = new Person();
    bPerson = new Person();
//定义两个实例

//分别给每个实例,添加一个好友
aPerson.friends.push("yun");
bPerson.friends.push("ling");

//看看每个实例中的好友
console.log(aPerson.friends); //["yun", "ling"] 
console.log(bPerson.friends); //["yun", "ling"] 


这里的结果,并不是我们希望看到的,所有一般在构造函数中,如果一个属性值是引用类型的话,那么会定义在构造函数内部,而不是定义到原型链中。


function Person(){
    this.name = "zhang";
    this.friends = [];
}


当然,如果您知道这里为何会出现这个原因,那么也可以人为的去处理这个问题。

这里的原因呢,也就是本文的重点,赋值表达式,当然这里不是说是因为使用了赋值表达式才导致的这个结果,而是因为没有使用赋值表达式,才导致了这个结果。

下面一点点的分析说明。

我们都知道,引用类型都是存放在堆内存中,我们一般都使用一个占位符来表示这个引用类型,比如A = [],这里,我们使用占位符A来指向堆内存中的这个数组。所以,不管我把A这个占位符赋值给多少其他的占位符,在这些占位符被调用时,操作的都是同一个数组,除非是使用赋值表达式,重新定义一个数组,看下面的例子:


var A = [];
A.push("zhang");

var B = A;
B.push("yun");

var C = B;
C.push("ling");


console.log(A);  //["zhang", "yun", "ling"] 
console.log(B);  //["zhang", "yun", "ling"] 
console.log(C);  //["zhang", "yun", "ling"] 

A = []; //赋值语句重新赋值
A.push("123");

console.log(A);  //["123"] 
console.log(B);  //["zhang", "yun", "ling"] 
console.log(C);  //["zhang", "yun", "ling"]  


看到这里,应该也能想明白,为何原型链中的引用类型会影响所有的实例了吧?因为在实例中操作时,只是在修改原型链中引用类型的值,而不是对这个值进行重新赋值。

至于如何解决呢,还是拿上面的这个例子来做个说明:


var A = [];
A.push("zhang");

var B = [],
    i;

for (i in A){
    B.push(A[i]);
}
B.push("yun");

console.log(A);  //["zhang"] 
console.log(B);  //["zhang", "yun"]


B变量重新定义一个数组,然后遍历A数组,把A数组中的所有元素都添加到B数组中去,这样只会,A数组和B数组在分别操作时,就不会对另外一个数组照成影响了,因为他们已经是两个不同的数组了。

相同的办法,也可以在原型链中使用哦,不过呢,这样的写法,即麻烦,也不好看,还是不如直接在构造函数内部定义引用类型的好,这里之所以说这些,只是为了说明这个问题的原因而已,所以,这些知道即可~~~

关于对象的创建,继承,原型链等相关问题,可以查看:自定义创建对象的几种方法对象继承的方法小结原型链断链的原因

备注

能力有限,经验有限,暂时就能想到以上的这几个问题,是受赋值表达式的思想影响的,是否有其他的问题出现的根本原因也是因为赋值表达式,暂时不知道。

如果您能知道,恳请指点,如果发现文中有描述或者理论错误,请指导,谢谢。

本文地址:http://www.zhangyunling.com/?p=140

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>