最近在复习之前学过的JavaScript,现在把笔记整理一下
数据类型
整数和浮点数
不区分整形和浮点数,统一使用Number表示
123
0.456
1.2345e3
-99
NaN
Infinity
字符串
使用单引号或者双引号括起来的文本,比如'abc',"abc"都是字符串
使用转义字符''可以帮我们使用一些功能字符,如下
'I'm "OK"!';//打印结果是I'm "OK"!
ASCII表示:\x##
Unicode表示:\u####
多行字符串(下面这种写法只支持ES6标准)
`多行
字符串
测试`
模板字符串
为了方便进行字符串的拼接
var name = 'Jack';
var age = 20
var message = `hello, ${name},are you ${age} year old!`;
alert(message);
获取字符串长度
var s = 'hello';
s.length
布尔值
true;
false;
2 > 1;//true
2 >= 3 ;//false
比较(注意)
==,自动转换数据类型再进行比较
===,不会转换类型,在数据类型不一致的情况下会返回false\
ps:坚持使用===
NaN === NaN;//false,NaN和任何值都不想等,包括自己
可以通过isNaN()方法来对其进行判断
isNaN(NaN);//true
浮点数的比较不能进行直接比较,而要将结果与一个精确值进行比较
Math.abs(1 / 3 - (1 - 2 / 3)) < 0.0000001;//true
null表示空,和0或者''都不一样,前者表示数值0,后者表示长度为0的字符串,而null表示空
数组
可以是任意数据的集合
var arr = [1,2,3.14,'hello',true];
创建数组var arr1 = new Array(1,2,3);
获取数组长度
arr.length;
通过索引获取数组的索引位置值
arr[2];//3.14
超出访问会扩充数组
arr1[5] = 2;//数组变为[1,2,3,undefined,undefined,2]
常用的数组函数如下
indexOf:根据值求索引
slice:取子数组
push:把元素添加到末尾
pop:返回并移除末尾元素
unshift:把元素添加到头部
shift:删掉头部元素
sort:把数组进行排序
reverse:翻转数组
splice(start,num,arg1...argn):从start开始删除num个元素,并在后面添加n个元素
concat:拼接数组
join:将数组的每个元素都使用join中的元素进行连接
对象
一组由键-值组成的无序集合
var person = {
name:'Bob',
age:20,
city:'ShangHai',
hasCar:true,
zipcode:null
}
可以使用对象名.属性名
的方式来获取对象的属性
在属性名包含特殊字符,比如单引号时,可以使用对象名[属性名]
来访问对象的属性
访问不存在的属性返回undefined
可以使用如下方式进行删除和新增属性,以及判断属性是不是在对象中
person.wife = 'Linda';//增加wife属性
delete person.zipcode;//删除zipcode属性
person.son;//返回undefined,因为没有son属性
hasCar in person;//true,person包含hasCar属性
属性可能是继承得到的,比如toString
属性,于是我们使用hasOwnProperty来判断对象自身是不是包含该属性
变量
使用var进行定义,比如var a = 12;
1、a可以是任意类型
2、a的初始类型为int型,但是可以转化为字符串型
strict模式
变量假如没有进行声明即使用,那么变量就会变成全局变量,这样将会对程序的运行进行严重的影响,我们使用strict模式来强制进行变量声明,使用的方式如下
'use strict';
//code体
...
...
...
判断语句
if(some){
}
some是布尔类型,则根据布尔值进行判断
some不是布尔类型,null,undefined,0,NaN和''表示false,其它一律视为true
循环
for ... in//对对象或者数组中的元素进行遍历
for(var key in o){
}
集合
Map
Map是一个键值对的结构,查询速度很快
创建一个Map对象方式如下
var m = new Map([['Michael',95],['Bob',75],['Tracy',85]]);//创建一个Map
m.get('Michael');//通过键值获取值
m.set('Jack',89);//设置一个键值对
Set
没有值重复的一个无序集合,创建如下
var s = new Set([1,2,3,4,3]);
s;//{1,2,3,4}
s.add(5);//添加值
s.delete(4);//删除值
iterable
Map、Array、Set都属于iterable类型,可以使用for ... of来进行遍历
var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (var x of a) { // 遍历Array
alert(x);
}
for (var x of s) { // 遍历Set
alert(x);
}
for (var x of m) { // 遍历Map
alert(x[0] + '=' + x[1]);
}
for ... in和for ... of的区别,前者得到的是索引,后者得到的是值
函数
一系列动作的抽象
定义
方法1
function 函数名(){
函数体
}
方法2
var 函数名 = function(){
函数体
}
举例如下
function abs(x){
if(x >= 0)
return x;
else
return -x;
}
调用
abs(10);
abs(-9);
调用函数时,即使调用参数与定义的不一样也没关系,可以多出参数数量,也可以少于参数数量,可以调用,但是结果可能不同,总之,JavaScript允许接收任意个参数
函数参数
arguments
我们调用函数时,函数中会有一个默认的关键字,代表了当前函数的调用参数的集合,举个例子
function foo(x){
alert(x);
for(var i = 0;i < arguments.length;i++)
alert(arguments[i]);
}
foo(10,20,30);
rest参数
表示函数参数列表的剩余项,并且只能放在函数参数项的最后,如下所示
function foo(a,b, ...rest){
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
foo(1,2,3,4,5);
foo(1);
以上,即使参数没有达到rest所需的条件,rest也不会是undefined,而是会返回一个空的数组
return
要注意JavaScript引擎有个问题,就是你把一句话写成两行的时候,此时该引擎就会在行的末尾自动添加分号,因此我们在return语句中千万注意将一句话写在一行
函数的作用域
1、如果不同的函数各自声明了同一个变量,那么该变量只在各自的函数体中起作用
2、内部函数可以访问外部变量
3、内部函数中的变量假如和外部函数中的变量重名,那么先访问内部函数的变量
全局作用域
不在函数中定义的变量具有全局作用域,实际上,JavaScript将该变量绑定到window上面去了,如下
var course = 'Learn JavaScript';
alert(course);
alert(window.course);//
上面的运行结果是一样的
let
我们使用var定义变量是不能定义具有局部作用域的变量的
'use strict';
function foo() {
for (var i=0; i<100; i++) {
//
}
i += 100; // 仍然可以引用变量i
}
为了解决这个问题,我们可以使用let进行定义块级的作用域,如下
'use strict';
function foo() {
var sum = 0;
for (let i=0; i<100; i++) {
sum += i;
}
i += 1; // SyntaxError
}
const
const是来定义常量的,并且和let一样具有块级作用域
方法
对象中绑定的函数称为对象的方法,如下
var person = {
name:'Jack',
birth:1992,
age:function(){
var y = new Date().getFullYear();
return y - this.birth;
}
}
person.age;//25
person.age();//25
age();//NaN
//这两种方式都可以调用方法
上面的返回结果与this关键字有关,假如我们通过对象调用age方法,那么this指向对象自身,假如我们直接调用age方法,那么this指向window
apply
apply是一个回调的自带函数,我们看一个例子
function getAge(){
var y = new Date().getFullYear();
return y - this.birth;
}
var person = {
name:'Jack',
birth:1992,
age:getAge
};
person.age();//25
getAge.apply(person,[]);//25
高阶函数
JavaScript的函数其实都指向一个变量,所以一个函数可以接收另一个函数作为参数,这称为高阶函数,如下
function add(x,y,f){
return f(x) + f(y);
}
map/reduce
map作为高阶函数,它抽象了运算规则,把运算的方法当成map函数的参数进行传递,然后对计算的结果进行返回,举例如下
function pow(x){
return x * x;
}
var arr = [1,2,3,4,5,6,7];
arr.map(pow);//返回结果[1,4,9,16,25,36,49]
//arr计算的结果不会覆盖arr原先的值,因此需要进行定义一个变量来接受计算的值
reduce的用法则是,reduce必须接收两个参数,并且将这两个参数计算的结果和下一个元素进行累积运算,如下
[a,b,c,d].reduce(f) = f(f(f(a,b),c),d)
举个例子,将一个数组变成一个整数,如下
var arr = [1,2,3,4];
arr.reduce(function(x,y){
return x * 10 + y;
});//结果为1234
filter
用于把数组的某些元素过滤掉,然后返回剩下的元素,如下
var arr = [1,2,3,4,5,6,9,10,15];
var r = arr.filter(function(x){
return x % 2 !=== 0
});
alert(r);//[1,3,5,9,15]
sort
对数组进行排序,但是默认情况下是将数组元素转化为字符串再进行比较,但是我们依然可以对比较的函数进行定义,如下
var arr = [10,20,1,2];
arr.sort(function(x,y){
if(x < y)
return -1;
else
return 1;
});
闭包
function lazy_sum(arr){
var sum = function(){
return arr.reduce(function(x,y){
return x + y;
});
}
}
var f = lazy_sum([1,2,3,4,5]);
f();
像上面这样,将相关参数和变量都保存在返回的函数中,这种结构被称为闭包
创建一个匿名函数并立即执行的语法
(function (x){
return x * x;
})(3);//9
闭包可以进行私有变量的封装,如下所示
function create_counter(initial){
var x = initial || 0;
return {
inc:function(){
x += 1;
return x;
}
}
}
var c1 = create_counter();
c1.inc();
c1.inc();
c1.inc()
箭头函数
箭头函数与匿名函数的意思是差不多的,而且简化了函数定义,箭头函数有两种形式,一种是只包含一个表达式,另一种可以包含多条语句,这时候就不能省略{...}和return了,如下
形式1
var fn = x => x * x;
形式2
var fn = x => {
if(x > 0)
return x * x;
else
return - x * x;
}
this
箭头函数和匿名函数虽然很像,但是它们的this的作用域是不同的,匿名函数的this指向window,而箭头函数的this指向词法作用域
generator
generator和函数的不同之处在于其可以返回多次,它的写法如下所示
function* foo(x){
yield x + 1;
yield x + 2;
return x + 3;
}
标准对象
JavaScript的一切都是对象
Date对象:用来表示日期和时间,可以获取系统的时间,也可以自行设置
var now = new Date();//获取系统的时间
var d = new Date(2015,5,14,20,15,30,123);//设置指定的时间
RegExp对象:正则表达式对象,用于对字符串进行特定规则的匹配
正则匹配(基本)
\d:表示匹配一个数字
\w:表示匹配一个字符
\s:表示匹配一个空格
.:表示匹配任意一个字符
*:表示匹配任意个字符
+:表示匹配至少1个字符
?:表示匹配0或1个字符
{n}:表示匹配n和字符
{n,m}:表示匹配n-m个字符
正则匹配(进阶)
精确匹配可以使用`[]`来表示范围
[0-9a-zA-Z\_]:表示匹配一个数字、字母或下划线
[0-9a-zA-Z\_]+:表示匹配至少一个数字、字母或下划线
[a-zA-Z\_\$][0-9a-zA-Z\_\$]*:匹配由字母、下划线或$开头,后面跟上任意个数字、字母、下划线或者$的字符串
[a-zA-Z\_\$][0-9a-zA-Z\_\$]{0, 19}:对上面的后面字符串的个数进行了限定,不得超出20个
A|B:表示匹配A或B
^表示行的开头,^\d表示必须以数字开头
$表示行的结束,\d$表示必须以数字结束
有两种方式创建一个正则表达式
var re1 = /ABC\-001/;//第一种方法
var re2 = new RegExp('ABC\\-001');//第二种方法
创建完RegExp对象之后,使用test(参数)方法来进行测试
var re = new RegExp('^\d{3}-\d{3,8}');
re.test('010-12345');//true
re.test('010-1234x');//false
re.test('010 12345');//false
切分字符串
str.split('正则表达式'),如下
'a b c'.split(' ');//['a','b','c']
'a b c'.split('\s+');//['a','b','c']
分组
对字符串进行提取,提取的规则是正则表达式,它的作用是这样的,使用()对正则表达式进行分组,匹配成功之后,返回的结果是一个数组,包括:1、匹配成功的字符串,2、根据()对匹配字符串进行分组的每一个组元素
var re = new RegExp('^(\d{3})-(\d{3,8})$');
re.exec('010-12345');//返回['010-12345','010','12345']
贪婪匹配
匹配尽可能多的字符,正则表达式默认的是贪婪匹配,如下
var re = /^(\d+)(0*)$/
re.exec('102300');//结果为['102300','102300','']
如何采用非贪婪匹配呢,也就是采用尽可能少的匹配呢,加个?就可以了,如下
var re = /^(\d+?)(0*)$/
re.exec('102300');//结果为['102300','1023','00']
全局搜索
全局匹配可以多次执行exec()方法来搜索一个匹配的字符串,如下
var re = /[a-zA-Z]+Script/g;
var s = 'JavaScript,VBScript, JScript and ECMAScript';
re.exec(s);//['JavaScript']
re.lastIndex;//10
re.exec(s);//['VBScript']
re.lastIndex;//20
JSON
一种用来进行传递数据的格式,它规定了字符集必须是UTF-8,规定了字符串和Object的键都必须使用双引号""
使用stringify可以将对象进行序列化,如下
var person = {
name: '小明',
age: 14,
gender: true,
height: 1.65,
grade: null,
'middle-school': '\"W3C\" Middle School',
skills: ['JavaScript', 'Java', 'Python', 'Lisp']
};
JSON.stringify(person);//'{"name":"小明","age":14,"gender":true,"height":1.65,"grade":null,"middle-school":"\"W3C\" Middle School","skills":["JavaScript","Java","Python","Lisp"]}'
//也可以使用参数让结果更加美观
JSON.stringify(person,null,' ');//{
"name": "小明",
"age": 14,
"gender": true,
"height": 1.65,
"grade": null,
"middle-school": "\"W3C\" Middle School",
"skills": [
"JavaScript",
"Java",
"Python",
"Lisp"
]
}
反序列化就是讲JSON格式解析成JavaScript对象,使用的方法是parse,如下
JSON.parse('[1,2,3,true]'); // [1, 2, 3, true]
JSON.parse('{"name":"小明","age":14}'); // Object {name: '小明', age: 14}
JSON.parse('true'); // true
JSON.parse('123.45'); // 123.45
面向对象编程
JavaScript语言没有类和实例的概念,而是通过原型来进行面向对象编程,看下面
var robot = {
name:'Robot',
height:1.6,
run:function(){
console.log(this.name + ' is running...');
}
};
var xiaoming = {
name:'xiaoming'
};
xiaoming.__proto__ = robot;
xiaoming.name;
xiaoming.run();
JavaScript和Java的区别就在于JavaScript没有‘class’概念,而是通过原型链的方式来进行继承,而这种继承,只是将一个对象的原型指向另一个对象而已
JavaScript进行获取对象属性的操作时,会现在当前的对象中找,没有找到就到其原型对象中去找,没有找到就一直追溯到Object.prototype,如果还没找到就返回undefined
构造函数
function Student(name){
this.name = name;
this.hello = function(){
alert('Hello, ' + this.name + '!');
}
}
var Jack = new Student('Jack');
Jack.name;
Jack.hello();
//上面的函数,如果就是普通调用,就会返回一个undefined,如果使用new关键字,那么就是一个构造函数,它绑定的this指向新建的对象,
如果不同的对象可以共用一部分方法,那么就可以大大减少内存的使用,比如下面
function Student(name){
this.name = name;
}
Student.prototype.hello = function(){
alert('Hello, ' + this.name + '!');
};
上面用到了原型的概念,当我们在函数的原型上定义方法时,那么假如定义了该函数的对象,那么不同的对象就可以使用相同的原型方法,这样做可以增加内存的使用效率
原型继承
由于JavaScript没有类的概念,那么JavaScript也不能像类那样进行继承,但是假如我想创建一个继承自原先实例的类,那么我们可以使用原型继承的方式进行继承
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 xiaoming = new PrimaryStudent({
name:'小明',
grade:2
});
xiaoming.name;//小明
xiaoming.grade;//2
浏览器
浏览器对象
window:表示当前浏览器窗口
navigator:获取浏览器的信息
screen:表示屏幕的信息
location:表示当前页面的URL信息
document:表示当前页面,由于HTML在浏览器中以DOM形式表示为树结构,因此document表示DOM数的根节点
document.getElementById();//由于ID是唯一的,因此可以通过id唯一定位一个元素
document.getElementsByTagName();//返回一组节点,这组节点包含参数而不是和参数一致
document.getElementsByClassName();//返回一组节点,这组节点包含参数而不是和参数一致
history:保存了浏览器的历史记录(不应该继续使用这个对象了)
操作DOM
更新:更新DOM节点表示的HTML内容
有两种形式可以用来更新HTML内容
1、直接修改innerHTML
//获取<p id="p-id">...</p>
var p = document.getElementById('p-id');
p.innerHTML = 'ABC';//结果为<p id="p-id">ABC</p>
p.innerHTML = 'ABC<span style="clolr:red">RED</span>XYZ'
2、通过修改innerText,这样只会修改元素的内容
var p = document.getElementById('p-id');
p.innerText = '<script>alert</script>';//只是会设置p的内容,而不会修改节点的属性
遍历:遍历DOM节点下的子节点
使用节点的children属性可以对节点的子节点进行遍历
var parent = document.getElementById('parent');
var i = 0;
var arr = [];
for(i = 0;i < parent.children.length;i++)
arr.push(parent.child[i]);
添加:在该DOM节点下新增一个子节点
有两种方法可以添加新的节点
1、使用appendChild
var js = document.getElementById('js'),
list = document.getElementById('list');
list.appendChild(js);//会将原先的节点添加到list的末尾
2、使用insertBefore
把子节点插入到指定的位置
var list = document.getElementById('list'),
ref = document.getElementById('python'),
haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.insertBefore(haskell,ref);
删除:将该节点从HTML中删除
使用removeChild函数可以将节点进行删除,但是此时节点还在内存中
var self = document.getElementById('to-be-removed');//获取被删除的节点
var parent = self.parentElement;
var removed = parent.removeChild(self);
removed = self;//true,说明被删除的节点还存在于内存中
操作表单
获取值,可以通过拿取input节点的value属性来获取用户对应的输入值
var input = document.getElementById('email');
input.value;//
这对于单选或复选只能获取预设值,假如我们需要获取其是否被选上,那么我们可以用checked来进行获取
设置值
对表单节点的value或者checked进行值的设置
提交表单
有两种方式提交表单
1、使用form中的button的onclick进行提交
<!-- HTML -->
<form id="test-form">
<input type="text" name="test">
<button type="button" onclick="doSubmitForm()">Submit</button>
</form>
<script>
function doSubmitForm() {
var form = document.getElementById('test-form');
// 可以在此修改form的input...
// 提交form:
form.submit();
}
</script>
2、使用表单自身的onsubmit事件
<!-- HTML -->
<form id="test-form" onsubmit="return checkForm()">
<input type="text" name="test">
<button type="submit">Submit</button>
</form>
<script>
function checkForm() {
var form = document.getElementById('test-form');
// 可以在此修改form的input...
// 继续下一步:
return true;//只有return true才会提交,return false将不会提交
}
</script>
第一种方式扰乱了浏览器form的正常提交
操作文件
可以进行上传文件的唯一控件就是<input type="file">
但是浏览器处于安全考虑只允许使用点击的方式来上传文件