标准对象
在JavaScript的世界里,一切皆是对象。但某些对象与其他对象不太一样。为了区分对象的类型,通常用typeof
操作符获取对象的类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 console .log (typeof 123 );console .log (typeof NaN );console .log (typeof 'str' );console .log (typeof true );console .log (typeof undefined );console .log (typeof Math .abs );console .log (typeof null );console .log (typeof []);console .log (typeof {});
注意,null、Array的类型都是object,若用typeof
将无法区分出null、Array和object。
包装对象
除了这些类型外,JavaScript还提供了包装对象。Number
、boolean
和string
都有包装对象。包装对象用new创建:
1 2 3 4 5 6 7 8 9 10 11 var n=new Number (123 );var b=new Boolean (true );var s=new String ('str' );console .log (n);console .log (b);console .log (s);
虽然包装对象看上去和原来的值一样,但类型已经变为object了,用===
与原始值比较会返回false
:
1 2 3 4 5 6 7 8 9 10 11 var n=new Number (123 );var b=new Boolean (true );var s=new String ('str' );console .log (n===123 );console .log (b===true );console .log (s==='str' );
因此,要注意以下几点:
不要使用new Number()
、new Boolean()
、new String()
创建包装对象
用parseInt()
或parseFloat()
来转换任意类型的number
用Sting()
来转换任意类型到string
,或直接调用某个对象的toString()
方法,只有null
和undedined
没有toString()
方法,number对象调用toString()
要写成:123..toString();
或(123).toStirng();
通常不用把任意类型转换为boolean
再判断
用typeof
可以判断出number
、boolean
、string
、function
和undefined
判断Array要用Array.isArray(arr)
判断null要用myvar===null
判断某个全局变量是否存在用typeof window.myvar==='undefined'
函数内部判断某个变量是否存在用typeof myvar==='undefined'
Date
在JavaScript里,Date对象用来表示日期和时间。
用Date获取系统时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 var now=new Date ();console .log (now);console .log (now.getFullYear ());console .log (now.getMonth ());console .log (now.getDate ());console .log (now.getDay ());console .log (now.getHours ());console .log (now.getMinutes ());console .log (now.getSeconds ());console .log (now.getMilliseconds ());console .log (now.getTime ());
当前时间是浏览器从本机操作系统获取的时间所以不一定准确。
若要创建一个指定日期和时间的Date对象,可以用:
1 2 3 4 5 var d=new Date (2015 ,5 ,19 ,20 ,15 ,30 ,123 );console .log (d);
第二种创建一个指定日期和时间的方法是解析一个符号ISO 8601格式的字符串:
1 2 3 4 5 6 7 8 var d=Date .parse ('2015-06-24T19:49:22.875+08:00' );console .log (d);var dd=new Date (d)console .log (dd);
时区
Date对象表示的时间总是按浏览器所在时区显示的,不过既可以显示本地时间,也可以显示调整后的UTC时间:
1 2 3 4 5 6 7 8 9 10 var d=Date .parse ('2015-06-24T19:49:22.875+08:00' );console .log (d);var dd=new Date (d)console .log (dd.toLocaleString ());console .log (dd.toUTCString ());
获取当前时间戳:
1 2 3 4 5 6 7 8 if (Date .now ){ console .log (Date .now ()); }else { console .log (new Date ().getTime ()); }
练习题
小明为了和女友庆祝情人节,特意制作了网页,并提前预定了法式餐厅。小明打算用JavaScript给女友一个惊喜留言,结果女友并未出现。小明非常郁闷,请你帮忙分析他的JavaScript代码有何问题:
1 2 3 4 var today = new Date ();if (today.getMonth () === 2 && today.getDate () === 14 ) { alert ('亲爱的,我预定了晚餐,晚上6点在餐厅见!' ); }
修改后的代码如下:
1 2 3 4 5 var today = new Date ();if (today.getMonth () === 1 && today.getDate () === 14 ) { alert ('亲爱的,我预定了晚餐,晚上6点在餐厅见!' ); }
RegExp
JavaScript有两种方式创建一个正则表达式:
第一种是直接通过/正则表达式/
写出来,第二种是通过new RegExp(正则表达式)
创建一个RegExp对象。
1 2 3 4 5 6 7 8 var re1=/ABC\-001/ ;var re2=new RegExp ('ABC\\-001' );console .log (re1);console .log (re2);
判断正则表达式是否匹配:
1 2 3 4 5 6 7 8 9 var re=/^\d{3}\-\d{3,8}$/ ;console .log (re.test ('010-12345' ));console .log (re.test ('010-1234x' ));console .log (re.test ('010 12345' ));
切分字符串
用正则表达式切分字符串:
1 2 3 4 console .log ('a b c , ; ; d' .split (/[\s\,\;]+/ ));
分组
正则表达式用()
表示要提取的分组,可以提取子串。若正则表达式种定义了组,就可以在RegExp
对象上用exec()
方法提取出子串来:
1 2 3 4 5 6 7 var re=/^(\d{3})-(\d{3,8})$/ ;console .log (re.exec ('010-12345' ));console .log (re.exec ('010 12345' ));
exec()
方法在匹配成功后会返回一个Array,第一个元素是正则表达式匹配到的整个字符串,后面的字符串表示匹配成功的子串。在匹配失败是返回null
。
正则表达式可以直接识别合法的世界,但无法做到完全识别日期:
1 2 3 4 5 6 7 8 var re1=/^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-0]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-0]|3[0-9]|4[0-9]|5[0-9]|[0-9])$/ ;console .log (re1.exec ('11:43:59' ));var re2=/^(0[1-9]|1[0-2]|[0-9])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]|[0-9])$/ ;console .log (re2.exec ('2-30' ));
贪婪匹配
正则匹配默认是贪婪匹配。在需要培贪婪匹配的语句后加问号?
,可以让该语句采用非贪婪匹配。
全局搜索
JavaScript的正则表达式有几个特殊的标志,最常用的是全局匹配g
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 var s='JavaScript,VBScript,JScript and ECMAScript' ;var re=/[a-zA-Z]+Script/g ;console .log (re.exec (s));console .log (re.lastIndex );console .log (re.exec (s));console .log (re.lastIndex );console .log (re.exec (s));console .log (re.lastIndex );console .log (re.exec (s));console .log (re.lastIndex );console .log (re.exec (s));
JavaScript的正则表达式还可以指定i
标志,表示忽略大小写;m
标志表示执行多行匹配。
练习题
请尝试写一个验证Email地址的正则表达式。版本一应该可以验证出类似的Email:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 var re = /^([a-z\.0-9])+@+([a-z0-9]+\.+[a-z]{3})$/ ;var i, success = true , should_pass = ['someone@gmail.com' , 'bill.gates@microsoft.com' , 'tom@voyager.org' , 'bob2015@163.com' ], should_fail = ['test#gmail.com' , 'bill@microsoft' , 'bill%gates@ms.com' , '@voyager.org' ]; for (i = 0 ; i < should_pass.length ; i++) { if (!re.test (should_pass[i])) { console .log ('测试失败: ' + should_pass[i]); success = false ; break ; } } for (i = 0 ; i < should_fail.length ; i++) { if (re.test (should_fail[i])) { console .log ('测试失败: ' + should_fail[i]); success = false ; break ; } } if (success) { console .log ('测试通过!' ); }
版本二可以验证并提取出带名字的Email地址:
1 2 3 4 5 6 7 8 9 10 11 12 13 var re = /^<?([a-zA-Z]+\s?[a-zA-Z]+)>?\s?([a-zA-Z]*@[a-zA-Z]+\.[a-zA-Z]+)$/ ;var r = re.exec ('<Tom Paris> tom@voyager.org' );if (r === null || r.toString () !== ['<Tom Paris> tom@voyager.org' , 'Tom Paris' , 'tom@voyager.org' ].toString ()) { console .log ('测试失败!' ); } else { console .log ('测试成功!' ); }
JSON
JSON是一种数据交换格式,字符集必须是UTF-8。为了统一解析,JSON的字符串规定必须用双引号""
,Object的键也必须用双引号""
。
JavaScript内置了JSON的解析,把任何JavaScript对象序列化成一个JSON格式的字符串,才能通过网络传递给其他计算机。
序列化
用JSON.stringify()
把JavaScript对象序列化成JSON格式的字符串:
1 2 3 4 5 6 7 8 9 10 11 12 13 var person={ name :'zhangsan' , age :18 , gender :true , height :1.65 , grade :null , 'middel-school' :'No.1 Middle School' , skills :['JavaScript' ,'Java' ,'Python' ,'Lisp' ] }; var s=JSON .stringify (person);console .log (s);
JSON.stringify()
还可以通过参数来控制按缩进输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var s=JSON .stringify (person,null ,' ' );console .log (s);
其第二个参数用于控制如何筛选对象的键值,如:
1 2 3 4 5 6 7 8 9 10 11 12 var s=JSON .stringify (person,['name' ,'skills' ],' ' );console .log (s);
还可以往第二个参数里传入一个函数,使得对象的每个键值对都会被函数先处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function convert (key,value ){ if (typeof value==='string' ){ return value.toUpperCase (); } return value; } var s=JSON .stringify (person,['name' ,'skills' ],' ' );console .log (s);
除此之外,还可以给person对象定义一个toJSON()
的方法,直接返回序列化数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var person={ name :'zhangsan' , age :18 , gender :true , height :1.65 , grade :null , 'middel-school' :'No.1 Middle School' , skills :['JavaScript' ,'Java' ,'Python' ,'Lisp' ], toJSON :function ( ){ return { 'Name' :this .name , 'Age' :this .age }; } }; var s=JSON .stringify (person);console .log (s);
反序列化
把一个JSON格式的字符串,用JSON.parse()
把它变成一个JavaScript对象:
1 2 3 4 5 6 7 8 9 console .log (JSON .parse ('[1,2,3,true]' ));console .log (JSON .parse ('{"name":"zhangsan","age":14}' ));console .log (JSON .parse ('true' ));console .log (JSON .parse ('123.45' ));
JSON.parse()
还可以接收一个函数,用来转换解析出的属性:
1 2 3 4 5 6 7 8 9 var obj=JSON .parse ('{"name":"zhangsan","age":14}' ,function (key,value ){ if (key==='name' ){ return value+' classmate' ; } return value; }); console .log (JSON .stringify (obj));
面向对象编程
在JavaScript种不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。所谓的继承关系是把对象的原型指向另一个对象。
以下是创建原型继承的一种方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var Student ={ name :'Robot' , heighe :1.2 , run :function ( ){ console .log (this .name +' is running...' ); } }; function createStudent (name ){ var s=Object .create (Student ); s.name =name; return s; } var zhangsan=createStudent ('ZhangSan' );zhangsan.run (); console .log (zhangsan.__proto__ ===Student );
创建对象
除了直接用{...}
创建一个对象外,JavaScript还可以用构造函数的方法来创建对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function Student (name ){ this .name =name; this .hello =function ( ){ console .log ('Hello, ' +this .name +'!' ); } } var zhangsan=new Student ('ZhangSan' );console .log (zhangsan.name );zhangsan.hello ();
注意:若不写new
,student
就是一个普通函数,它返回undefined
,若写了new
,student
就是一个构造函数,它绑定的this指向新创建的对象,并默认返回this
。
用new Student()
创建的对象还从原型上获得了一个constructor
属性,它指向函数Student
本身:
1 2 3 4 5 6 7 8 console .log (zhangsan.constructor ===Student .prototype .constructor );console .log (Student .prototype .constructor ===Student );console .log (Object .getPrototypeOf (zhangsan)===Student .prototype );
上述代码主要描述以下关系:
若要让通过构造函数创建的对象共享一个hello
函数,将函数放到这些对象共同的原型Student.prototype
上即可:
1 2 3 4 5 6 7 8 9 10 11 function Student (name ){ this .name =name; } Student .prototype .hello =function ( ){ console .log ('Hello, ' +this .name +'!' ); }; var zhangsan=new Student ('ZhangSan' );zhangsan.hello ();
构造函数的首字母应当大写,普通函数首字母应当小写。
可以编写一个函数,在内部封装所有的new
操作:
1 2 3 function createStudent (props ){ return new Student (props||{}) }
练习题
请利用构造函数定义Cat
,并让所有的Cat对象有一个name
属性,并共享一个方法say()
,返回字符串'Hello, xxx!'
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function Cat (name ) { this .name =name; } Cat .prototype .say =function ( ){ return 'Hello, ' +this .name +'!' ; } var kitty = new Cat ('Kitty' );var doraemon = new Cat ('哆啦A梦' );if (kitty && kitty.name === 'Kitty' && kitty.say && typeof kitty.say === 'function' && kitty.say () === 'Hello, Kitty!' && kitty.say === doraemon.say ) { console .log ('测试通过!' ); } else { console .log ('测试失败!' ); }
原型继承
JavaScript的原型继承实现方式为:
定义新的构造函数,并在内部用call()
调用希望“继承”的构造函数,并绑定this
;
借助中间函数F
实现原型链继承,最好通过封装的inherits
函数完成;
继续在新的构造函数原型上定义新方法。
现有一构造函数Student
:
1 2 3 4 5 6 7 function Student (props ){ this .name =props.name ||'Unnamed' ; } Student .prototype .hello =function ( ){ console .log ('Hello, ' +this .name +'!' ); }
Student
的原型链如下:
现在要基于Student
扩展出PrimaryStudent
。因此先定义新的构造函数PrimaryStudent
:
1 2 3 4 5 function PrimaryStudent (props ){ Student .call (this .props ); this .grade =props.grade ||1 ; }
利用空函数F
来修改原型链,使得PrimaryStudent
指向Student.prototype
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 function Student (props ){ this .name =props.name ||'Unnamed' ; } Student .prototype .hello =function ( ){ console .log ('Hello, ' +this .name +'!' ); } function PrimaryStudent (props ){ Student .call (this ,props); this .grade =props.grade ||1 ; } function F ( ){} F.prototype =Student .prototype ; PrimaryStudent .prototype =new F ();PrimaryStudent .prototype .constructor =PrimaryStudent ;PrimaryStudent .prototype .getGrade =function ( ){ return this .grade ; }; var lisi=new PrimaryStudent ({ name :'lisi' , grade :3 }); console .log (lisi.name );console .log (lisi.grade );console .log (lisi.__proto__ ===PrimaryStudent .prototype );console .log (lisi.__proto__ .__proto__ ===Student .prototype );console .log (lisi instanceof PrimaryStudent );console .log (lisi instanceof Student );
此时的新型原型链如下:
如果把整个继承动作用一个inherits()
函数封装起来,还可以隐藏空函数F
的定义,inherits()
函数可以复用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 function Student (props ){ this .name =props.name ||'Unnamed' ; } Student .prototype .hello =function ( ){ console .log ('Hello, ' +this .name +'!' ); } function PrimaryStudent (props ){ Student .call (this ,props); this .grade =props.grade ||1 ; } function inherits (Child,Parent ){ var F=function ( ){}; F.prototype =Parent .prototype ; Child .prototype =new F (); Child .prototype .constructor =Child ; } inherits (PrimaryStudent ,Student );PrimaryStudent .prototype .getGrade =function ( ){ return this .grade ; }; var lisi=new PrimaryStudent ({ name :'lisi' , grade :3 }); console .log (lisi.name );console .log (lisi.grade );
class继承
class定义
ES6中引入了新的关键字class
,使类的定义更简单。
用class
定义Student
类并创建一个对象如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 class Student { constructor (name ){ this .name =name; } hello ( ){ console .log ('Hello, ' +this .name +'!' ); } } var lisi=new Student ('Lisi' );lisi.hello ();
原型继承
可以通过extends
实现原型继承:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Student { constructor (name ){ this .name =name; } hello ( ){ console .log ('Hello, ' +this .name +'!' ); } } class PrimaryStudent extends Student { constructor (name,grade ){ super (name); this .grade =grade; } myGrade ( ){ console .log ('I am at grade ' +this .grade ); } } var lisi=new PrimaryStudent ('Lisi' ,3 );lisi.hello (); lisi.myGrade ();
不是所有的浏览器都支持ES6的class,当浏览器不支持时,需要Babel 这个工具把class
代码转换为传统的prototype
代码。
练习题
请利用class
重新定义Cat
,并让它从已有的Animal
继承,然后新增一个方法say()
,返回字符串'Hello, xxx!'
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Animal { constructor (name ) { this .name = name; } } class Cat extends Animal { constructor (name ){ super (name); } say ( ){ return 'Hello, ' +this .name +'!' ; } } var kitty = new Cat ('Kitty' );var doraemon = new Cat ('哆啦A梦' );if ((new Cat ('x' ) instanceof Animal ) && kitty && kitty.name === 'Kitty' && kitty.say && typeof kitty.say === 'function' && kitty.say () === 'Hello, Kitty!' && kitty.say === doraemon.say ) { console .log ('测试通过!' ); } else { console .log ('测试失败!' ); }
浏览器
目前主流的浏览器分为以下几种:
IE6-11:国内用的最多的IE浏览器,从IE10开始支持ES6标准
Chrome:Google出品的基于Webkit内核浏览器,由于Chrome一经安装就时刻保持自升级,因此不用管它的版本,最新版肯定支持ES6
Safari:Apple的Mac系统自带的基于Webkit内核浏览器,从OSX 10.7 Lion自带的6.1版本开始支持ES6
Firefox:Mozilla自研的Gecko内核,也是时刻保持自升级。
移动设备上目前IOS和Android两大阵营分别主要使用Apple的Safari和Google的Chrome,两种都是Webkit核心,最新版本均支持ES6
浏览器对象
JavaScript可以获取浏览器提供的很对对象,并进行操作。
window
window对象不但充当全局作用域,而且表示浏览器窗口。
window对象有innerWidth
和innerHeight
属性,可以获取浏览器窗口的内部宽度和高度。内部宽高是指出去菜单栏、工具栏、边框等占位元素后,用于显示页面的净宽高。IE<=8版本的浏览器不支持这一特性:
1 2 3 4 console .log ('window inner size: ' +window .innerWidth +'x' +window .innerHeight );
与之对应的,还有一个outerWidth
和outerHeight
属性,可以获取浏览器窗口的整个宽高。
navigator
navigator
对象表示浏览器的信息,最常用的属性包括:
navigator.appName:浏览器名称
navigator.appVersion:浏览器版本
navigator.language:浏览器设置的语言
navigator.platform:操作系统类型
navigator.userAgent:浏览器设定的user-Agent
字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 console .log ('appName=' +navigator.appName );console .log ('appVersion=' +navigator.appVersion );console .log ('language=' +navigator.language );console .log ('platform=' +navigator.platform );console .log ('userAgent=' +navigator.userAgrnt );
注意,由于navigator的信息很容易被用户修改,所以常用JavaScript对不存在属性返回unfinded的特性,计算浏览器的信息是否又被改动:
1 var width=window .innerWith ||document .body .clientWidth ;
screen
screen对象表示屏幕的信息,常用的属性有:
screen.width:屏幕宽度,以像素为单位
screen.height:屏幕高度,以像素为单位
sreen.colorDepth:返回颜色位数,如8、16、24
1 2 3 4 console .log ('Screen size=' +screen.width +'x' +screen.height );
location
location对象表示当前页面的URL信息。一个完整的URL可以用location.href
获取。要获取URL各部分的值,可以这么写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 console .log (location.protocol );console .log (location.host );console .log (location.post );console .log (location.pathname );console .log (location.search );console .log (location.hash );
要加载一个新页面,可以调用location.assign()
方法,要重新加载当前页面,可以调用location.reload()
方法:
1 2 3 4 5 if (confirm ('要重新加载当前页' +location.href +'?' )){ location.reload (); }else { location.assign ('/' ); }
document
document对象表示当前页面。由于HTML在浏览器中以DOM形式表示为树形结构,document对象就是整个DOM树的根节点。
document的title属性是从HTML文档中的<title>...</title>
读取的,但其也可以动态改变:
1 document .title ='helloJavaScript!' ;
若要查找DOM树的某个节点,需要从document对象开始查找。最常用的方法是根据ID和Tag Name。
有一HTML数据如下:
1 2 3 4 5 6 7 8 <dl id ="drink-menu" style ="border:solid 1px #ccc;padding:6px;" > <dt > 摩卡</dt > <dd > 热摩卡咖啡</dd > <dt > 酸奶</dt > <dd > 北京老酸奶</dd > <dt > 果汁</dt > <dd > 鲜榨苹果汁</dd > </dl >
用document对象提供的getElementById()
和getElementsByTagName()
可以按ID获得一个DOM节点和按Tag名称获得一组DOM节点:
1 2 3 4 5 6 7 8 9 10 11 var menu=document .getElementById ('drink-menu' );var drinks=document .getElementsByTagName ('dt' );var i,s;s='提供的饮料有:' ; for (i=0 ;i<drinks.length ;i++){ s=s+drinks[i].innerHTNL +',' ; } console .log (s);
document对象还有一个cookie
属性,可以获取当前页面的Cookie:
1 console .log (document .cookie );
由于JavaScript能读到页面的Cookie,而用户的登录信息通常也存在Cookie中,且HTML页面中允许引入第三方的JavaScript代码。为了防止第三方的JavaScript直接能获取网站的用户登录信息,服务器在设置Cookie时可以使用httpOnly
,以防止被JavaScript读取。 IE浏览器从IE6 SP1开始支持。
history
history对象保持了浏览器的历史记录,JavaScript可以调用history对象的back()
或forward()
,相当于用户点击了浏览器的后退或前进按钮。在任何情况都不应该使用history这个对象。