写给Android/Java开发者的JavaScript精解(1)

Martha2264 7年前
   <p>作为一个多年Javaer,我学习JavaScript的方式就是比较着学,努力辨识清楚Java与JavaScript的同与异,在比较中加深理解认识,最后达到学会JavaScript的目的。</p>    <p>许多程序语言都有自己的口号,Java的口号是:“write once,run everywhere!”同样,JavaScript也有自己的口号,那就是“everything is object!”我的学习也是从这句话开始的,为此我需要搞清楚以下问题:</p>    <ol>     <li>在JavaScript中,对象(object)到底是什么?</li>     <li>在JavaScript中,基本数据类型是对象吗?</li>     <li>在JavaScript中,数组是对象吗?</li>     <li>在JavaScript中,函数是对象吗?</li>     <li>在JavaScript中,对象能继承吗?</li>     <li>在JavaScript中,创建对象的方法有多少?</li>     <li>在JavaScript中,如何使用函数这种特殊的对象(this,属性使用,构建对象使用,闭包使用,柯里化,默认值,rest和spread)?<br> 搞清楚上面的问题后,我又探究了如下问题:</li>     <li>在JavaScript中,闭包是什么?</li>     <li>在JavaScript中,变量有作用域吗?</li>     <li>在JavaScript中,有哪些奇技淫巧是Java所没有的(模板字符串,数组解构赋值,getter,setter,Generator ,For of,modules)?</li>    </ol>    <p>弄清这些问题后,再去学习React Native,就不会有语言上的困难了,真的好开心!终于能够与React Native愉快地玩耍了!</p>    <h2><strong>一、在JavaScript中,对象(object)到底是什么?</strong></h2>    <p>一言以蔽之,在JavaScript中,对象就是一个Map,里面只有键值对,除此之外,啥也没有!</p>    <h3><strong>1、创建对象</strong></h3>    <p>来,让我们创建一个对象:<br> var person = new Object();<br> 熟悉的new出现了,很亲切。var 是个新东西,来看看它是什么。</p>    <p>在Java中我们要声明一个变量,必须指明它的类型,如下所示:<br> Person person;<br> Dog dog;<br> Cat cat;</p>    <p>之所以必须指明变量的类型,是因为Java是一个静态类型的语言,如果不声明,编译器将无法理解我们声明的变量。与Java正好相反,JavaScript是一个动态类型的语言,声明变量的时候不需要指明变量的类型,只需一个var 关键字就可以了,变量可以是任意类型,只有在你使用变量的时候,JavaScript才会动态确认变量的类型。</p>    <h3><strong>2、添加属性</strong></h3>    <p>在对象包含的键值对中,键被称为对象的属性,值被称为相应的属性值。所有的键都是字符串,而值可以是任意的类型。</p>    <p>让我们为上面的对象添加两个属性:<br> person['name'] = 'milter';<br> person['age'] = 31;</p>    <p>这里又有一个新东西,在JavaScript中,单引号和双引号都可以用来创建字符串,二者是一样的。<br> 下面我们就可以访问这些属性值了:<br> var name = person['name'] ; // milter<br> var age = person['age'] ; // 31</p>    <h3><strong>3、简便方法</strong></h3>    <p>以上创建对象、添加属性、访问属性的方式非常笨拙:</p>    <ul>     <li>添加属性和访问属性的语法呆板(需频繁输入[] 和' ')</li>     <li>一次只能添加一个属性</li>    </ul>    <p>为了解决创建对象和添加属性的笨拙,JavaScript提供了创建对象的大括号({ })语法:</p>    <p>var person ={'name' : 'milter', 'age': 31 };</p>    <p>这样,可在创建对象的同时为其赋予多个属性。</p>    <p>问题还没有解决,上面赋予属性时还需要写单引号,访问属性时还要写中括号和单引号。<br> 为此,JavaScript进一步规定:如果代表属性的字符串符合JavaScript的标识符命名规范,那么就可以省去字符串外面的单引号,同时可以使用object.prop语法访问属性。</p>    <p>JavaScript的标识符命名规范如下:</p>    <ol>     <li>区分大小写,Myname与myname是两个不同的标识符。</li>     <li>标识符首字符可以是以下划线(_)、美元符($)或者字母开始,不能是数字。</li>     <li>标识符中其它字符可以是下划线(_)、美元符($)、字母或数字组成的。</li>     <li>关键字和保留字不能作为标识符。</li>    </ol>    <p>显然,我们的name和age属性符合这个规范,所以现在我们可以这样写了:<br> var person ={ name : 'milter', age: 31 };<br> var name = person.name ; // milter<br> var age = person.age ; // 31</p>    <p>更酷的是,我们可以在对象创建出来后,继续为它添加或删除属性,如下:</p>    <p>person.sex = 'man' ;<br> 以上为person对象添加了叫sex的属性,其值为字符串'man'。</p>    <p>delete person.sex ;<br> 以上删除了对象person中的sex属性。</p>    <p>你可能很想问,对象中的方法难道也是属性,也是键值对?答案是<strong>YES</strong>,后面讲到函数时会细说,暂且按下。</p>    <h2><strong>二、 在JavaScript中,基本数据类型是对象吗?</strong></h2>    <p>JavaScript中的基本数据类型有6种:</p>    <ul>     <li>String</li>     <li>Number</li>     <li>Boolean</li>     <li>Symbol</li>     <li>undefined</li>     <li>null</li>    </ul>    <p>与Java相比,String 和Boolean含义基本没变。<br> 获取一个String值的方式在上文中已经提到,就是用单引号或者双引号括起一段字符即可。</p>    <p>Boolean只有两个值 true和false,不用自己创建,直接拿来用就可以。</p>    <p>Number只能是双精度浮点数,就是Java中的double。</p>    <p>Symbol是个新类型,在ES6中引进。它的每个值都是<strong>独一无二</strong>的,获取一个Symbol值的语法是 : Symbol(String)。<br> 例如:<br> var symb1 = Symbol('one');<br> var symb2 = Symbol('one');</p>    <p>var symb3 = Symbol();<br> var symb4 = Symbol();</p>    <p>由上可见,String是可选的,不管有没有String,都能用这种语法获取一个Symbol值。String只是为这个Symbol值增加了一个描述而已。Symbol的值都是<strong>独一无二</strong>的,是指你<strong>不能两次获得一样的Symbol值</strong>(正如人不可能两次踏入同一条河流,请叫我哲学家)。比如Number这种数据类型,我可以反复获取1这个值,并把它赋给不同的变量:</p>    <p>var number1 = 1 ;<br> var number2 = 1 ;</p>    <p>number1和number2是相等的,String和Boolean也可以这样做。<br> 但是Symbol不是这样的!。上文中, <strong>symb1 != symb2,symb3 != symb4</strong>。</p>    <p>从Java转过来的人,感到这种写法很别扭,总想在Symbol前加上一个new,千万不要这样,JavaScript是不允许的。<br> 请牢记,这里没有创建任何对象,仅仅是创建了一个独一无二的值。</p>    <p><strong>Symbol很像Java中的UUID,Symbol()就相当于UUID.randomUUID()。</strong></p>    <p>undefined和null这两种数据类型都只有一个值,就是它们的类型名。undefined用于表示变量未初始化,null用于表示对象为空。</p>    <p>回到本节的问题,基本数据类型是对象吗?答案是 <strong>NO!</strong><br> 说它们不是对象,原因有两点:</p>    <ol>     <li>它们是不可变的。上文中,创建出person对象后,还能继续为它增加和删除属性,也可以改变原有属性的值。基本数据类型只有一个值,它们没有什么属性可修改或添删。而且一旦获得一个值,这个值就不可能改变。这一点,很像Java中String的不可变性。</li>     <li>它们的比较和传递都是基于值的,对象的比较和传递是基于引用的。例如:<br> var a = 'one' ;<br> var b = 'one';<br> var c = {one:1};<br> var d = {one:1};<br> var e = c ;<br> 上面,a和b是相等的,因为两个变量有着同样的值,c和d是不相等的,因为两个变量指向两个不同的对象,但是e和c是相等的,因为它们都指向同一个对象。如果你这样做:<br> e.one = 2 ;<br> 那么你会发现 c.one也会变成2。</li>    </ol>    <p>正如Java中有基本数据类型的包装类(Integer和int ,Double和double等)并可以自动装拆箱一样,JavaScript也有类似的机制,而且实现的更为彻底。</p>    <p>回头看看六种基本数据类型,发现前四种的首字母是大写的,那是因为JavaScript为它们创建了包装对象,最后两个undefined和null首字母是小写的,因为JavaScript没有为它们提供包装对象。以String为例,代码<br> var str = new String('one');<br> 会用字符串'one'创建一个字符串对象str,String对象有许多操作字符串的方法可供你调用。Number和Boolean也与之类似,你可以<br> var num = new Number(3.1415);<br> var bool = new Boolean(true) ;<br> 可以使用 typeof语法来验证它们是不是对象。<br> typeof bool //object<br> typeof num //object<br> typeof str //object</p>    <p>但是Symbol是个例外,你不能使用new语法创建一个Symbol值的包装对象,JavaScript是非常不鼓励将Symbol值包装成对象的,在实际使用中,这样的需求<strong>非常非常少!</strong>如果你特别想将一个Symbol值包装成对象,只能这样<br> var sym = Symbol();<br> var wrapSymbol = new Object(sym);</p>    <p>同样,与Java有自动装拆箱一样,JavaScript也有类似的机制,且更灵活。简单讲,当你把基本数据当作对象来使用时,它就会自动变成一个对象(undefined和null除外,原因前面已说明)。<br> 举例如下:<br> var hello ='hello';<br> var str = hello.slice(1); //str 的值是'ello'</p>    <p>这点比Java要方便!</p>    <p><strong>其原理是:</strong>JavaScript 发现我们对基本数据类型hello进行了slice方法调用,它会用hello的值创建一个临时的包装对象,即new String(hello),然后在这个包装对象上调用slice(1),返回基本数据类型字符串'ello'。随后,这个临时的包装对象就会被销毁。</p>    <h2><strong>三、在JavaScript中,数组是对象吗?</strong></h2>    <p>基本数据类型不是对象,JavaScript的口号已经自己打脸。现在继续来看数组。先说答案:<strong>数组是对象!</strong><br> JavaScript中,可以这样定义一个数组。<br> var arr = [ 'one', 'two', 'three' ];</p>    <p>乍一看,似乎没有键值对,但是,JavaScript会把上面的代码转化成下面这样:<br> var arr = { '0':'one', '1':'two', '2':'three' } ;<br> 由此我们知道,数组属于对象确定无疑!!<br> 显然按照第一种写法更加简洁直观,你可以认为它是定义数组的语法糖。<br> 现在,我们来访问数组中的元素。按照前面学习的对象知识,似乎应该这样访问数组元素:<br> arr.0 , arr.1, arr.2</p>    <p>实际情况是,这是不允许的!为什么呢?</p>    <p>前面我们讲过,只有属性字符串符合JavaScript的标识符命名规范时,才能用圆点语法(object.prop)访问对象的属性。而在上面的数组中,属性字符串是'0','1','2',它们都是以数字开头的字符串,不符合标识符命名规范,所以我们不能用圆点语法访问数组中的元素。</p>    <p>那怎么访问?用基本的访问对象属性的方法,如下:</p>    <p>arr['0'], arr['1'], arr['2']</p>    <p>鉴于数组使用的频率实在太高,让用户每次访问数组元素都要写单引号,太繁琐,JavaScript对数组对象做了专门的优化,可以让你省去单引号,直接这样写:<br> arr[0], arr[1], arr[2]</p>    <p>优化的原理非常简单,在背后,JavaScript会自动将中括号中的东西两侧加上单引号。<br> 但是这样就会带来一个非常微妙的问题,假如我们这样写:<br> arr[01]<br> 那么,JavaScript就会把它转化成这样:<br> arr['01']<br> 很显然,对象 arr 中没有'01'这个属性,因为字符串'1'和'01'是不相等的,为避免这样的错误,JavaScript就禁止了在访问数组时输入开头为0的数字,它会报出 Invalid Number错误,提示你修改。</p>    <p>另外,JavaScript数组对象有一个length属性,非常迷惑人,它并不是数组中元素的数量,请看下面的代码:<br> var arr = ['one', ,'three'];<br> 在这个数组中,只有两个元素,但是arr.length的值仍然是3。JavaScript会把上面的代码转化成这样:<br> var arr = {'0':'one','1':undefined,'2':'three'};<br> 如果测试 arr[1] == undefined,你会得到结果true。<br> 严格来说,arr.length的值等于最后一个元素的index再加上1。</p>    <p>四、在JavaScript中,函数是对象吗?</p>    <p>在JavaScript中,函数首先具有Java中方法的性质,就是接收输入,进行操作,根据需要产生输出。<br> 同样,先说答案:<strong>函数是对象!</strong></p>    <h3><strong>1、函数的定义</strong></h3>    <p>JavaScript中,定义一个函数有两种方式:声明的方式和表达式的方式。</p>    <ul>     <li>声明的方式:<br> <code>function foo1 (a,b) { return a+b ; };</code></li>     <li>表达式的方式:<br> <code>var foo2 = function (a,b) { return a + b; };</code><br> 上面第一种方式定义了一个名为foo1的函数,第二种方式定义了一个名为foo2的函数,二者有什么区别?</li>    </ul>    <p>最主要的区别是<strong>可用时机</strong>不一样。假设foo1和foo2存在于同一个js文件中,js引擎加载并执行该js文件的具体过程是:<br> 首先扫描整个文件,找出并加载foo1;<br> 然后逐条语句解释执行,文件中任何地方出现的foo1调用(不管是在foo1定义之前还是之后)都可以被正确处理,因为js引擎已经提前加载了该函数。<br> 当执行到定义foo2函数的行时,才会加载这个函数,在之后的语句中才能调用foo2函数,如果在定义foo2函数的行之前使用foo2,程序将会报错。</p>    <p>简单讲,声明方式定义的函数会在程序执行之前加载,程序文件的任何地方都可以使用声明方式定义的函数。表达式的方式定义的函数只有在执行到定义函数所在的行时,才会加载,才能在之后的语句中使用该函数。</p>    <p>问题来了,看看下面这种定义函数的方式:</p>    <pre>  <code>var foo2 = function foo3(a,b) {       return a + b;  };</code></pre>    <p>咦!这是什么鬼?它属于哪种函数定义方式?这个函数的名字到底是foo2呢还是foo3呢?为什么要搞这么诡异的函数定义方式?</p>    <p>简言之,它属于表达式的方式定义的函数,函数的名字是foo2,foo3存在的<strong>唯一目的</strong>就是为了在该函数内部使用该函数(实现递归),在程序的其他地方foo3都是没有意义的,在这一点上,可以认为foo3的作用域在该函数的内部。</p>    <p>现在可以来解释对象的方法问题了。对象的方法也是一个属性,此时,属性的名字就是函数的名字,属性的值就是一个函数。举例说明如下:</p>    <pre>  <code>var  obj = {      add: function(a,b){            return a+b;       }  };</code></pre>    <p>上面的代码中,对象obj有一个属性add,它的值是一个函数,函数的名字就是属性的名字add,所以,你可以这样使用obj:<br> var result = obj.add(4,9); // result == 13</p>    <p>上述定义函数的方式属于表达式方式,也就是说,只有对象被创建后,add函数才会被加载。对象也可以使用声明方式定义的函数作为属性值,如下所示:</p>    <pre>  <code>var obj = { };  obj.add1 = add ;    function add(a,b){      return a+b;  };</code></pre>    <p>上面的代码中,我们把以声明方式定义的函数add赋值给obj的一个名为add1的属性,此时你就可以这样使用了:<br> var result = obj.add1(4,9); //result == 13</p>    <p>那么问题来了,此时的add1和add是什么关系?仅仅是一个函数的两个名字吗?答案是: <strong>NO!</strong>这涉及到函数的另一个概念scope,后面会讲,暂且按下。</p>    <p>理解了对象的方法也是键值对后,我们可以更彻底地理解文章开头的那句话:<br> <strong>在JavaScript中,对象就是一个Map,里面只有键值对,除此之外,啥也没有!</strong></p>    <p>搞清楚了函数的定义方式,理解了对象的方法也是键值对,我们来看本节主题:<strong>函数也是对象!</strong></p>    <p>我们以上面定义的函数foo1(声明方式定义)和foo2(表达式方式定义)为例来说明。<br> 函数是对象,最明显的证据就是我们可以为它增加、删除、修改属性。如下:</p>    <ul>     <li>增加属性:<br> foo1.prop1 = 'first prop';<br> foo1.prop2 = 'second prop';<br> foo2.prop1 = 'first prop';<br> foo2.prop2 = 'second prop';</li>     <li>修改属性:<br> foo1.prop1 = 'changed prop';<br> foo2.prop1 = 'changed prop';</li>     <li>删除属性:<br> delete foo1.prop1;<br> delete foo2.prop1;</li>    </ul>    <p>事实上,我们可以把函数当作一个<strong>特殊的对象</strong>,这个对象是可以被调用的(a object which can be called!)。除了这点特殊性,函数就与一般对象没有什么明显的区别了。</p>    <p><strong>小结:</strong>现在,我们已经解决文章开头提出的10个问题的前4个,这让我们非常彻底地理解了JavaScript中的那句口号:“Everything is object”。我们获得了如下知识:</p>    <ul>     <li>对象就是键值对的集合,除此之外,它啥也没有。</li>     <li>JavaScript有6种基本数据类型,它们<strong>不是对象!</strong></li>     <li>数组是对象,虽然乍一看并不像。</li>     <li>函数也是对象,只是有点特殊,是一种可以被调用的对象。</li>    </ul>    <p>动手写这个系列的初衷,是想给广大的Android开发者学习React Native提供一个有针对性、精炼的JavaScript快速入门教程,默认大家对Java都比较熟悉。所以,对JavaScript和Java比较相似的地方都没有涉及,比如 for 、while、if语句等,而把重点放在JavaScript明显区别于Java的内容上,同时尽量从Java和JavaScript比较的角度来写,目的是方便大家的理解。</p>    <p>另外,由于是为React Native学习做准备,所以对JavaScript中与网页处理相关的内容也尽量避开了。</p>    <p> </p>    <p><br>  </p>    <p><br> 来自:http://www.jianshu.com/p/1b1b1110708d<br>  </p>