HTML+CSS+JS 属于前端三剑客
HTML:基于标签编写网页文档的结构,版本是HTML2,简称H5
CSS:基于选择器和属性美化,布局排版网页,版本是CSS3
JS:嵌入在网页中的编程语言,也称为"脚本语言",负责编写用户与网页的交互操作
变量
流程控制
对象
数组
函数
Java和JavaScript的区别
1、Java是强类型语言,静态类型语言
2、JavaScript是弱类型语言,动态类型语言
JS的继承依靠的是原型链
1、同一个构造方法构造出来的对象拥有一个__proto__指针,指向同一个原型对象
2、构造方法身上有一个prototype指针,与该构造方法构造出来的所有对象的__proto__指向同一个原型对象
3、通过"指针.__proto__.成员"或者"构造方法.prototype.成员" 可以设置一个所有构造方法构造出来的对象都可以继承的成员
4、通过"Object.prototype.成员"可以设置一个任意构造方法构造出来的对象都可以继承的成员
试题1:什么是原型对象,什么是对象原型
他们都是一个东西,构造方法身上有一个prototype指针,与该构造方法构造出来的所有对象的__proto__指向同一个原型对象
试题2:什么是原型链
所有的对象和构造方法身上都有指针,指向同一个原型对象,原型对象身上有指针指向其继承的构造方法的原型对象。最顶层的是Object的原型对象。这种由子构造函数及其父辈构造函数的原型对象组成单向链表称为原型链,在对象身上访问成员的时候,会沿着原型链一直向上追溯,如果追溯到顶层Object的原型对象都找不到,返回undefined。
JavaScript整理
1、基本语法:
与Java基本一致,虽然JavaScript不强制每句语句后面都要加“;”,但是如果不加“;”可能会在运行时导致运行结果与期望值不一致。
2、数据类型和变量:
1、number
JavaScript不区分整数和浮点数,即(byte、short、int、long、float、double),统一用number表示
2、字符串
JavaScript中,字符串以单引号或双引号括起来,和Java的区别是Java中单引号只能表示字符,JavaScript中单引号和双引号都表示字符串
3、布尔型
JavaScript中,Boolean的表示和Java一样,一个布尔值只有true和false两种值
在JavaScript中,&&运算、||运算、!运算与Java一致。
4、但是在比较运算符上,javaScript有“==”和“===”两种运算符,“==”比较时会自动转换数据类型再比较,这样会产生很多奇怪的结果;“===”比较时不会自动转换数据类型,数据不一致就返回false,一致了再比较。由于JavaScript的设计缺陷,我们最好始终使用“===”进行比较。
5、null和undefined
“null”表示一个空值,它与undefined的区别是“null”代表的是空值,一般情况下代表的是有一个变量存在但没有赋值;而undefined代表的是这个变量不存在。
6、数组
JavaScript中创建数组使用Array(),但是更推荐直接使用[]创建数组
7、对象
javaScript对象的键都是字符串类型,值可以是任意数据类型,JavaScript中的对象只需要一个构造方法,就可以创造对象,不需要像Java一样声明变量。
8、变量
变量在JavaScript中就使用一个变量名表示,变量名是大小写英文、数字、$、_的组合,且不能用数字开头,变量名也不能是JavaScript的关键字。在JavaScript中使用“=”给变量赋值,使用“var”可以给一个变量不同类型的值
使用var
申明的变量则不是全局变量,它的范围被限制在该变量被申明的函数体内(函数的概念将稍后讲解),同名变量在不同的函数体内互不冲突。
为了修补JavaScript这一严重设计缺陷,ECMA在后续规范中推出了strict模式,在strict模式下运行的JavaScript代码,强制通过var
申明变量,未使用var
申明变量就使用的,将导致运行错误。
3、字符串
JavaScript的字符串用' '或" "括起来的字符表示;
转义字符\可以转义很多字符,例如:\n表示换行,\t表示制表符;
1、多行字符串
多行字符串用\n写起来比较费劲,最新的ES6标准新增了一种多行字符串的表示方法,用反引号“...”表示
2、模板字符串
要把多个字符串连接起来,可以用“+”连接
3、操作字符串
要获得字符串某个指定位置的字符,使用类似Array的下标操作,索引号从0开始;字符串是不可变的,如果对字符串的某个索引赋值,不会有任何错误,但是也没有任何效果。
toUpperCase:把一个字符串全部变成大写
toLowerCase:把一个字符串全部变成小写
indexOf:会搜索指定字符串出现的位置
subString:返回指定索引区的子串
4、数组
1、要取得Array的长度,直接访问length属性
直接给Array的length赋值会导致Array大小变化;
2、Array可以通过索引把对应的元素修改为新的值,因此,对Array的索引进行赋值会直接修改这个Array
3、slice:slice()对应String的subString()版本,它截取Array的部分元素,然后返回一个新的Array(截取不包括尾部的一个元素);
4、push和pop:push()向Array的末尾添加若干元素,pop()则把Array的最后一个元素删除掉;
5、UNshift和shift:unshift()在Array的头部添加若干元素,shift()则把Array的第一个元素删掉;
6、sort:可以对当前Array进行排序,它会直接修改当前Array的元素位置,直接调用时,按照默认顺序排序;
7、reverse:把整个Array的元素给调反,即反转;
8、splice:修改Array的万能方法
9、concat:把当前的Array和另一个Array连接起来,并返回一个新的Array;
10、join:把当前Array的每个元素都用指定的字符串连接起来,然后返回连接后的字符串;
5、对象
JavaScript的对象是一种无序的集合数据类型,它由若干键值对组成。
JavaScript的对象用于描述现实世界中的某个对象。例如,为了描述“小明”这个淘气的小朋友,我们可以用若干键值对来描述他:
JavaScript用一个{...}
表示一个对象,键值对以xxx: xxx
形式申明,用,
隔开。注意,最后一个键值对不需要在末尾加,
,如果加了,有的浏览器(如低版本的IE)将报错。
上述对象申明了一个name
属性,值是'小明'
,birth
属性,值是1990
,以及其他一些属性。最后,把这个对象赋值给变量xiaoming
后,就可以通过变量xiaoming
来获取小明的属性了:
访问属性是通过.
操作符完成的,但这要求属性名必须是一个有效的变量名。如果属性名包含特殊字符,就必须用''
括起来:
xiaohong
的属性名middle-school
不是一个有效的变量,就需要用''
括起来。访问这个属性也无法使用.
操作符,必须用['xxx']
来访问:
也可以用xiaohong['name']
来访问xiaohong
的name
属性,不过xiaohong.name
的写法更简洁。我们在编写JavaScript代码的时候,属性名尽量使用标准的变量名,这样就可以直接通过object.prop
的形式访问一个属性了。
实际上JavaScript对象的所有属性都是字符串,不过属性对应的值可以是任意数据类型。
如果访问一个不存在的属性会返回什么呢?JavaScript规定,访问不存在的属性不报错,而是返回undefined
:
'use strict'; var xiaoming = { name: '小明' };
Run
由于JavaScript的对象是动态类型,你可以自由地给一个对象添加或删除属性:
如果我们要检测xiaoming
是否拥有某一属性,可以用in
操作符:
不过要小心,如果in
判断一个属性存在,这个属性不一定是xiaoming
的,它可能是xiaoming
继承得到的:
因为toString
定义在object
对象中,而所有对象最终都会在原型链上指向object
,所以xiaoming
也拥有toString
属性。
要判断一个属性是否是xiaoming
自身拥有的,而不是继承得到的,可以用hasOwnProperty()
方法:
JS的继承依靠的是原型链
1、同一个构造方法构造出来的对象拥有一个__proto__指针,指向同一个原型对象
2、构造方法身上有一个prototype指针,与该构造方法构造出来的所有对象的__proto__指向同一个原型对象
3、通过"指针.__proto__.成员"或者"构造方法.prototype.成员" 可以设置一个所有构造方法构造出来的对象都可以继承的成员
4、通过"Object.prototype.成员"可以设置一个任意构造方法构造出来的对象都可以继承的成员
试题1:什么是原型对象,什么是对象原型
他们都是一个东西,构造方法身上有一个prototype指针,与该构造方法构造出来的所有对象的__proto__指向同一个原型对象
试题2:什么是原型链
所有的对象和构造方法身上都有指针,指向同一个原型对象,原型对象身上有指针指向其继承的构造方法的原型对象。最顶层的是Object的原型对象。这种由子构造函数及其父辈构造函数的原型对象组成单向链表称为原型链,在对象身上访问成员的时候,会沿着原型链一直向上追溯,如果追溯到顶层Object的原型对象都找不到,返回undefined。
6、条件判断
JavaScript使用if () { ... } else { ... }
来进行条件判断。例如,根据年龄显示不同内容,可以用if
语句实现如下:
其中else
语句是可选的。如果语句块只包含一条语句,那么可以省略{}
:
多行条件判断
如果还要更细致地判断条件,可以使用多个if...else...
的组合:
上述多个if...else...
的组合实际上相当于两层if...else...
:
但是我们通常把else if
连写在一起,来增加可读性。这里的else
略掉了{}
是没有问题的,因为它只包含一个if
语句。注意最后一个单独的else
不要略掉{}
。
7、循环
循环
for ... in
for
循环的一个变体是for ... in
循环,它可以把一个对象的所有属性依次循环出来:
要过滤掉对象继承的属性,用hasOwnProperty()
来实现:
由于Array
也是对象,而它的每个元素的索引被视为对象的属性,因此,for ... in
循环可以直接循环出Array
的索引:
while
for
循环在已知循环的初始和结束条件时非常有用。而上述忽略了条件的for
循环容易让人看不清循环的逻辑,此时用while
循环更佳。
while
循环只有一个判断条件,条件满足,就不断循环,条件不满足时则退出循环。比如我们要计算100以内所有奇数之和,可以用while循环实现:
do ... while
最后一种循环是do { ... } while()
循环,它和while
循环的唯一区别在于,不是在每次循环开始的时候判断条件,而是在每次循环完成的时候判断条件:
用do { ... } while()
循环要小心,循环体会至少执行1次,而for
和while
循环则可能一次都不执行。
8、函数定义和调用
定义函数
在JavaScript中,定义函数的方式如下:
上述abs()
函数的定义如下:
function
指出这是一个函数定义;abs
是函数的名称;(x)
括号内列出函数的参数,多个参数以,
分隔;{ ... }
之间的代码是函数体,可以包含若干语句,甚至可以没有任何语句。
调用函数
调用函数时,按顺序传入参数即可:
由于JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数:
传入的参数比定义的少也没有问题:
此时abs(x)
函数的参数x
将收到undefined
,计算结果为NaN
。
要避免收到undefined
,可以对参数进行检查:
rest参数
由于JavaScript函数允许接收任意个参数,于是我们就不得不用arguments
来获取所有参数:
为了获取除了已定义参数a
、b
之外的参数,我们不得不用arguments
,并且循环要从索引2
开始以便排除前两个参数,这种写法很别扭,只是为了获得额外的rest
参数,有没有更好的方法?
ES6标准引入了rest参数,上面的函数可以改写为:
rest参数只能写在最后,前面用...
标识,从运行结果可知,传入的参数先绑定a
、b
,多余的参数以数组形式交给变量rest
,所以,不再需要arguments
我们就获取了全部参数。
如果传入的参数连正常定义的参数都没填满,也不要紧,rest参数会接收一个空数组(注意不是undefined
)。
9、方法
在一个对象中绑定函数,称为这个对象的方法。
在JavaScript中,对象的定义是这样的:
但是,如果我们给xiaoming
绑定一个函数,就可以做更多的事情。比如,写个age()
方法,返回xiaoming
的年龄:
绑定到对象上的函数称为方法,和普通函数也没啥区别,但是它在内部使用了一个this
关键字,这个东东是什么?
在一个方法内部,this
是一个特殊变量,它始终指向当前对象,也就是xiaoming
这个变量。所以,this.birth
可以拿到xiaoming
的birth
属性。
apply
虽然在一个独立的函数调用中,根据是否是strict模式,this
指向undefined
或window
,不过,我们还是可以控制this
的指向的!
要指定函数的this
指向哪个对象,可以用函数本身的apply
方法,它接收两个参数,第一个参数就是需要绑定的this
变量,第二个参数是Array
,表示函数本身的参数。
装饰器
利用apply()
,我们还可以动态改变函数的行为。
JavaScript的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。
现在假定我们想统计一下代码一共调用了多少次parseInt()
,可以把所有的调用都找出来,然后手动加上count += 1
,不过这样做太傻了。最佳方案是用我们自己的函数替换掉默认的parseInt()
:
10、Date
在JavaScript中,Date
对象用来表示日期和时间。
要获取系统当前时间,用:
注意,当前时间是浏览器从本机操作系统获取的时间,所以不一定准确,因为用户可以把当前时间设定为任何值。
如果要创建一个指定日期和时间的Date
对象,可以用:
你可能观察到了一个非常非常坑爹的地方,就是JavaScript的月份范围用整数表示是0~11,0
表示一月,1
表示二月……,所以要表示6月,我们传入的是5
!这绝对是JavaScript的设计者当时脑抽了一下,但是现在要修复已经不可能了。
JavaScript的Date对象月份值从0开始,牢记0=1月,1=2月,2=3月,……,11=12月。
第二种创建一个指定日期和时间的方法是解析一个符合ISO 8601格式的字符串:
但它返回的不是Date
对象,而是一个时间戳。不过有时间戳就可以很容易地把它转换为一个Date
:
使用Date.parse()时传入的字符串使用实际月份01~12,转换为Date对象后getMonth()获取的月份值为0~11。
时区
Date
对象表示的时间总是按浏览器所在时区显示的,不过我们既可以显示本地时间,也可以显示调整后的UTC时间:
那么在JavaScript中如何进行时区转换呢?实际上,只要我们传递的是一个number
类型的时间戳,我们就不用关心时区转换。任何浏览器都可以把一个时间戳正确转换为本地时间。
时间戳是个什么东西?时间戳是一个自增的整数,它表示从1970年1月1日零时整的GMT时区开始的那一刻,到现在的毫秒数。假设浏览器所在电脑的时间是准确的,那么世界上无论哪个时区的电脑,它们此刻产生的时间戳数字都是一样的,所以,时间戳可以精确地表示一个时刻,并且与时区无关。
所以,我们只需要传递时间戳,或者把时间戳从数据库里读出来,再让JavaScript自动转换为当地时间就可以了。
11、regexp
所以我们判断一个字符串是否是合法的Email的方法是:
-
创建一个匹配Email的正则表达式;
-
用该正则表达式去匹配用户的输入来判断是否合法。
因为正则表达式也是用字符串表示的,所以,我们要首先了解如何用字符来描述字符。
在正则表达式中,如果直接给出字符,就是精确匹配。用\d
可以匹配一个数字,\w
可以匹配一个字母或数字,所以:
-
'00\d'
可以匹配'007'
,但无法匹配'00A'
; -
'\d\d\d'
可以匹配'010'
; -
'\w\w'
可以匹配'js'
;
.
可以匹配任意字符,所以:
'js.'
可以匹配'jsp'
、'jss'
、'js!'
等等。
RegExp
有了准备知识,我们就可以在JavaScript中使用正则表达式了。
JavaScript有两种方式创建一个正则表达式:
第一种方式是直接通过/正则表达式/
写出来,第二种方式是通过new RegExp('正则表达式')
创建一个RegExp对象。
两种写法是一样的:
注意,如果使用第二种写法,因为字符串的转义问题,字符串的两个\\
实际上是一个\
。
先看看如何判断正则表达式是否匹配:
RegExp对象的test()
方法用于测试给定的字符串是否符合条件。
切分字符串
用正则表达式切分字符串比用固定的字符更灵活,请看正常的切分代码:
嗯,无法识别连续的空格,用正则表达式试试:
无论多少个空格都可以正常分割。加入,
试试:
再加入;
试试:
如果用户输入了一组标签,下次记得用正则表达式来把不规范的输入转化成正确的数组。
分组
除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()
表示的就是要提取的分组(Group)。比如:
^(\d{3})-(\d{3,8})$
分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码:
如果正则表达式中定义了组,就可以在RegExp
对象上用exec()
方法提取出子串来。
exec()
方法在匹配成功后,会返回一个Array
,第一个元素是正则表达式匹配到的整个字符串,后面的字符串表示匹配成功的子串。
exec()
方法在匹配失败时返回null
。
提取子串非常有用。来看一个更凶残的例子:
这个正则表达式可以直接识别合法的时间。但是有些时候,用正则表达式也无法做到完全验证,比如识别日期:
对于'2-30'
,'4-31'
这样的非法日期,用正则还是识别不了,或者说写出来非常困难,这时就需要程序配合识别了。
贪婪匹配
需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的0
:
由于\d+
采用贪婪匹配,直接把后面的0
全部匹配了,结果0*
只能匹配空字符串了。
必须让\d+
采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0
匹配出来,加个?
就可以让\d+
采用非贪婪匹配:
全局搜索
JavaScript的正则表达式还有几个特殊的标志,最常用的是g
,表示全局匹配:
全局匹配可以多次执行exec()
方法来搜索一个匹配的字符串。当我们指定g
标志后,每次运行exec()
,正则表达式本身会更新lastIndex
属性,表示上次匹配到的最后索引:
全局匹配类似搜索,因此不能使用/^...$/
,那样只会最多匹配一次。
正则表达式还可以指定i
标志,表示忽略大小写,m
标志,表示执行多行匹配。
12、JSON
序列化
让我们先把小明这个对象序列化成JSON格式的字符串:
'use strict'; var xiaoming = { name: '小明', age: 14, gender: true, height: 1.65, grade: null, 'middle-school': '\"W3C\" Middle School', skills: ['JavaScript', 'Java', 'Python', 'Lisp'] };
反序列化
拿到一个JSON格式的字符串,我们直接用JSON.parse()
把它变成一个JavaScript对象:
JSON.parse()
还可以接收一个函数,用来转换解析出的属性:
'use strict';
13、创建对象
当我们用obj.xxx
访问一个对象的属性时,JavaScript引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到Object.prototype
对象,最后,如果还没有找到,就只能返回undefined
。
构造函数
除了直接用{ ... }
创建一个对象外,JavaScript还可以用一种构造函数的方法来创建对象。它的用法是,先定义一个构造函数:
忘记写new怎么办
如果一个函数被定义为用于创建对象的构造函数,但是调用时忘记了写new
怎么办?
在strict模式下,this.name = name
将报错,因为this
绑定为undefined
,在非strict模式下,this.name = name
不报错,因为this
绑定为window
,于是无意间创建了全局变量name
,并且返回undefined
,这个结果更糟糕。
所以,调用构造函数千万不要忘记写new
。为了区分普通函数和构造函数,按照约定,构造函数首字母应当大写,而普通函数首字母应当小写,这样,一些语法检查工具如jslint将可以帮你检测到漏写的new
。