forEach和for of
)
由包含的代码就是JavaScript代码,它将直接被浏览器执行。
//第一种写法,直接写
//第二种写法
有些时候你会看到标签还设置了一个
type
属性,但这是没有必要的,因为默认的type
就是JavaScript,所以不必显式地把type
指定为JavaScript。
JavaScript的语法和Java语言类似,每个语句以
;
结束,语句块用{...}
。但是,JavaScript并不强制要求在每个语句的结尾加;
,浏览器中负责执行JavaScript代码的引擎会自动在每个语句的结尾补上;
。注释也和java一样请注意,JavaScript严格区分大小写,如果弄错了大小写,程序将报错或者运行不正常。
下面的一行代码就是一个完整的赋值语句:
var x = 1;
数据类型:
Number(不分整型和浮点型),字符串,布尔值,null和undefined,数组(包含多个元素),对象(由键值组成的无序的集合)
===
比较,它不会自动转换数据类型,如果数据类型不一致,返回false
,如果一致,再比较。推荐用这个变量
var a; // 申明了变量a,此时a的值为undefined var $b = 1; // 申明了变量$b,同时给$b赋值,此时$b的值为1 var s_007 = '007'; // s_007是一个字符串 var Answer = true; // Answer是一个布尔值true var t = null; // t的值是null
//JavaScript var a = 123; // a的值是整数123 a = 'ABC'; // a变为字符串 //Java int a = 123; // a是整数类型变量,类型用int申明 a = "ABC"; // 错误:不能把字符串赋给整型变量
这种变量本身类型不固定的语言称之为动态语言,与之对应的是静态语言。例如Java是静态语言。
字符串操作
多行字符串用反引号``
模板字符串用${name}在字符串中引用变量
字符串常见的操作如下:
var s = 'Hello, world!'; s.length; // 13
要获取字符串某个指定位置的字符,使用类似Array的下标操作,索引号从0开始:
var s = 'Hello, world!'; s[0]; // 'H' s[6]; // ' ' s[7]; // 'w' s[12]; // '!' s[13]; // undefined 超出范围的索引不会报错,但一律返回undefined
需要特别注意的是,字符串是不可变的,如果对字符串的某个索引赋值,不会有任何错误,但是,也没有任何效果:
var s = 'Test'; s[0] = 'X'; alert(s); // s仍然为'Test'
JavaScript为字符串提供了一些常用方法,注意,调用这些方法本身不会改变原有字符串的内容,而是返回一个新字符串:
常用方法:转大小写,搜索指定字符串的索引,截取指定索引之间的字符串
数组操作
JavaScript的
Array
可以包含任意数据类型,并通过索引来访问每个元素。要取得
Array
的长度,直接访问length
属性:var arr = [1, 2, 3.14, 'Hello', null, true]; arr.length; // 6
请注意,直接给
Array
的length
赋一个新的值会导致Array
大小的变化:var arr = [1, 2, 3]; arr.length; // 3 arr.length = 6; arr; // arr变为[1, 2, 3, undefined, undefined, undefined] arr.length = 2; arr; // arr变为[1, 2]
Array
可以通过索引把对应的元素修改为新的值,因此,对Array
的索引进行赋值会直接修改这个Array
:var arr = ['A', 'B', 'C']; arr[1] = 99; arr; // arr现在变为['A', 99, 'C']
请注意,如果通过索引赋值时,索引超过了范围,同样会引起
Array
大小的变化:var arr = [1, 2, 3]; arr[5] = 'x'; arr; // arr变为[1, 2, 3, undefined, undefined, 'x']
大多数其他编程语言不允许直接改变数组的大小,越界访问索引会报错。然而,JavaScript的
Array
却不会有任何错误。在编写代码时,不建议直接修改Array
的大小,访问索引时要确保索引不会越界。常用方法:查找指定元素索引,截取部分索引元素,队尾添加删除元素,队首添加删除元素,排序(无设置按默认),反转,指定索引位置添加删除元素,两个数组连接,数组转字符串(可以设置中间符号)
多维数组就是数组的元素中也是数组
var arr = [[1, 2, 3], [400, 500, 600], '-'];
对象操作
var xiaoming = { name: '小明', birth: 1990, school: 'No.1 Middle School', height: 1.70, weight: 65, score: null };
通过变量
xiaoming
来获取小明的属性了:xiaoming.name; // '小明' xiaoming.birth; // 1990
也可以用
xiaohong['name']
来访问xiaohong
的name
属性,不过xiaohong.name
的写法更简洁。如果访问一个不存在的属性会返回什么呢?JavaScript规定,访问不存在的属性不报错,而是返回
undefined
:
由于JavaScript的对象是动态类型,你可以自由地给一个对象添加或删除属性:
var xiaoming = { name: '小明' }; xiaoming.age; // undefined xiaoming.age = 18; // 新增一个age属性 xiaoming.age; // 18 delete xiaoming.age; // 删除age属性 xiaoming.age; // undefined delete xiaoming['name']; // 删除name属性 xiaoming.name; // undefined delete xiaoming.school; // 删除一个不存在的school属性也不会报错
如果我们要检测
xiaoming
是否拥有某一属性,可以用in
操作符:var xiaoming = { name: '小明', birth: 1990, school: 'No.1 Middle School', height: 1.70, weight: 65, score: null }; 'name' in xiaoming; // true 'grade' in xiaoming; // false
不过要小心,如果
in
判断一个属性存在,这个属性不一定是xiaoming
的,它可能是xiaoming
继承得到的:'toString' in xiaoming; // true
因为
toString
定义在object
对象中,而所有对象最终都会在原型链上指向object
,所以xiaoming
也拥有toString
属性。要判断一个属性是否是
xiaoming
自身拥有的,而不是继承得到的,可以用hasOwnProperty()
方法:var xiaoming = { name: '小明' }; xiaoming.hasOwnProperty('name'); // true xiaoming.hasOwnProperty('toString'); // false
控制台打印消息
console.log('输出内容');
控制台打印消息
判断语句
JavaScript的条件判断和Java一样,if,else if,else,这里不做介绍
var s = '123'; if (s.length) { // 条件计算结果为3 // }
JavaScript把
null
、undefined
、0
、NaN
和空字符串''
视为false
,其他值一概视为true
,因此上述代码条件判断的结果是true
。循环语句
JavaScript的循环也和java一样,就是for,while,do-while,就多了一个for...in,类似java中的foreach循环
for ... in
for
循环的一个变体是for ... in
循环,它可以把一个对象的所有属性依次循环出来:var o = { name: 'Jack', age: 20, city: 'Beijing' }; for (var key in o) { console.log(key); // 'name', 'age', 'city' }
要过滤掉对象继承的属性,用
hasOwnProperty()
来实现:var o = { name: 'Jack', age: 20, city: 'Beijing' }; for (var key in o) { if (o.hasOwnProperty(key)) { console.log(key); // 'name', 'age', 'city' } }
由于
Array
也是对象,而它的每个元素的索引被视为对象的属性,因此,for ... in
循环可以直接循环出Array
的索引:var a = ['A', 'B', 'C']; for (var i in a) { console.log(i); // '0', '1', '2' console.log(a[i]); // 'A', 'B', 'C' }
请注意,
for ... in
对Array
的循环得到的是String
而不是Number
。
Map和Set
JavaScript的默认对象表示方式
{}
可以视为其他语言中的Map
或Dictionary
的数据结构,即一组键值对。但是JavaScript的对象有个小问题,就是键必须是字符串。但实际上Number或者其他数据类型作为键也是非常合理的。
为了解决这个问题,最新的ES6规范引入了新的数据类型
Map
。要测试你的浏览器是否支持ES6规范,请执行以下代码,如果浏览器报ReferenceError错误,那么你需要换一个支持ES6的浏览器:
Map
Map
是一组键值对的结构,具有极快的查找速度。举个例子,假设要根据同学的名字查找对应的成绩,如果用
Array
实现,需要两个Array
:var names = ['Michael', 'Bob', 'Tracy']; var scores = [95, 75, 85];
给定一个名字,要查找对应的成绩,就先要在names中找到对应的位置,再从scores取出对应的成绩,Array越长,耗时越长。
如果用Map实现,只需要一个“名字”-“成绩”的对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢。用JavaScript写一个Map如下:
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]); m.get('Michael'); // 95
初始化
Map
需要一个二维数组,或者直接初始化一个空Map
。Map
具有以下方法:var m = new Map(); // 空Map m.set('Adam', 67); // 添加新的key-value m.set('Bob', 59); m.has('Adam'); // 是否存在key 'Adam': true m.get('Adam'); // 67 m.delete('Adam'); // 删除key 'Adam' m.get('Adam'); // undefined
由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:
var m = new Map(); m.set('Adam', 67); m.set('Adam', 88); m.get('Adam'); // 88
Set
Set
和Map
类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set
中,没有重复的key。要创建一个
Set
,需要提供一个Array
作为输入,或者直接创建一个空Set
:var s1 = new Set(); // 空Set var s2 = new Set([1, 2, 3]); // 含1, 2, 3
重复元素在
Set
中自动被过滤:var s = new Set([1, 2, 3, 3, '3']); s; // Set {1, 2, 3, "3"}
注意数字
3
和字符串'3'
是不同的元素。通过
add(key)
方法可以添加元素到Set
中,可以重复添加,但不会有效果:s.add(4); s; // Set {1, 2, 3, 4} s.add(4); s; // 仍然是 Set {1, 2, 3, 4}
通过
delete(key)
方法可以删除元素:var s = new Set([1, 2, 3]); s; // Set {1, 2, 3} s.delete(3); s; // Set {1, 2}
Map
和Set
是ES6标准新增的数据类型,请根据浏览器的支持情况决定是否要使用。
数据遍历
遍历
Array
可以采用下标循环,遍历Map
和Set
就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable
类型,Array
、Map
和Set
都属于iterable
类型。具有
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 console.log(x); } for (var x of s) { // 遍历Set console.log(x); } for (var x of m) { // 遍历Map console.log(x[0] + '=' + x[1]); }
你可能会有疑问,
for ... of
循环和for ... in
循环有何区别?
for ... in
循环由于历史遗留问题,它遍历的实际上是对象的属性名称。一个Array
数组实际上也是一个对象,它的每个元素的索引被视为一个属性。当我们手动给
Array
对象添加了额外的属性后,for ... in
循环将带来意想不到的意外效果:var a = ['A', 'B', 'C']; a.name = 'Hello'; for (var x in a) { console.log(x); // '0', '1', '2', 'name' }
for ... in
循环将把name
包括在内,但Array
的length
属性却不包括在内。
for ... of
循环则完全修复了这些问题,它只循环集合本身的元素:var a = ['A', 'B', 'C']; a.name = 'Hello'; for (var x of a) { console.log(x); // 'A', 'B', 'C' }
这就是为什么要引入新的
for ... of
循环。然而,更好的方式是直接使用
iterable
内置的forEach
方法,它接收一个函数,每次迭代就自动回调该函数。以Array
为例:var a = ['A', 'B', 'C']; a.forEach(function (element, index, array) { // element: 指向当前元素的值 // index: 指向当前索引 // array: 指向Array对象本身 console.log(element + ', index = ' + index); }); //结果: A, index = 0 B, index = 1 C, index = 2
Set
与Array
类似,但Set
没有索引,因此回调函数的前两个参数都是元素本身:var s = new Set(['A', 'B', 'C']); s.forEach(function (element, sameElement, set) { console.log(element); });
Map
的回调函数参数依次为value
、key
和map
本身:var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]); m.forEach(function (value, key, map) { console.log(value); });
如果对某些参数不感兴趣,由于JavaScript的函数调用不要求参数必须一致,因此可以忽略它们。例如,只需要获得
Array
的element
:var a = ['A', 'B', 'C']; a.forEach(function (element) { console.log(element); });
函数
总结JavaScript的函数和Java的差别
多了function定义,少了返回值类型,作用域修饰符,无返回值则返回undefined,使用时支持任意个参数,arguments关键字获取所有参数,...rest参数支持接收任意个参数
定义函数两种:
function abs(x) { if (x >= 0) { return x; } else { return -x; } }
var abs = function (x) { if (x >= 0) { return x; } else { return -x; } };
调用函数:
abs(10); // 返回10 abs(-9); // 返回9
arguments和...rest参数:
function foo(x) { console.log('x = ' + x); // 10 for (var i=0; i
你可能猜到了,由于函数定义有两种方式,以变量方式
var foo = function () {}
定义的函数实际上也是一个全局变量,因此,顶层函数的定义也被视为一个全局变量,并绑定到window
对象:'use strict'; function foo() { alert('foo'); } foo(); // 直接调用foo() window.foo(); // 通过window.foo()调用
名字空间
全局变量会绑定到
window
上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。例如:
// 唯一的全局变量MYAPP: var MYAPP = {}; // 其他变量: MYAPP.name = 'myapp'; MYAPP.version = 1.0; // 其他函数: MYAPP.foo = function () { return 'foo'; };
把自己的代码全部放入唯一的名字空间
MYAPP
中,会大大减少全局变量冲突的可能。许多著名的JavaScript库都是这么干的:jQuery,YUI,underscore等等。
块级作用域let关键字:
function foo() { for (var i=0; i<100; i++) { // } i += 100; // 仍然可以引用变量i } function foo() { var sum = 0; for (let i=0; i<100; i++) { sum += i; } // SyntaxError: i += 1; }
常量关键字const:
由于var和let申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”:
var PI = 3.14;
ES6标准引入了新的关键字const来定义常量,const与let都具有块级作用域:
'use strict'; const PI = 3.14; PI = 3; // 某些浏览器不报错,但是无效果! PI; // 3.14
解构赋值:多个变量同时赋值:
'use strict'; // 如果浏览器支持解构赋值就不会报错: var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
var person = { name: '小明', age: 20, gender: 'male', passport: 'G-12345678', school: 'No.4 middle school' }; var {name, age, passport} = person;
方法
var xiaoming = { name: '小明', birth: 1990, age: function () { var y = new Date().getFullYear(); return y - this.birth; } }; xiaoming.age; // function xiaoming.age() xiaoming.age(); // 今年调用是25,明年调用就变成26了
this关键字的指向:
方法中的this指向当前对象的属性,函数中的this指向全局变量window,方法中的内部函数的this指向undefined或者window
用
var that = this;
,你就可以放心地在方法内部定义其他函数,而不是把所有语句都堆到一个方法中。 'use strict'; var xiaoming = { name: '小明', birth: 1990, age: function () { var that = this; // 在方法内部一开始就捕获this function getAgeFromBirth() { var y = new Date().getFullYear(); return y - that.birth; // 用that而不是this } return getAgeFromBirth(); } }; xiaoming.age(); // 25
apply和call方法
要指定函数的
this
指向哪个对象,可以用函数本身的apply
方法,它接收两个参数,第一个参数就是需要绑定的this
变量,第二个参数是Array
,表示函数本身的参数。另一个与
apply()
类似的方法是call()
,唯一区别是:
apply()
把参数打包成Array
再传入;
call()
把参数按顺序传入。比如调用
Math.max(3, 5, 4)
,分别用apply()
和call()
实现如下:Math.max.apply(null, [3, 5, 4]); // 5 Math.max.call(null, 3, 5, 4); // 5
对普通函数调用,我们通常把
this
绑定为null
。
高阶函数
JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
一个最简单的高阶函数:
function add(x, y, f) { return f(x) + f(y); }
当我们调用add(-5, 6, Math.abs)时,参数x,y和f分别接收-5,6和函数Math.abs,根据函数定义,我们可以推导计算过程为:x = -5; y = 6; f = Math.abs; f(x) + f(y) ==> Math.abs(-5) + Math.abs(6) ==> 11; return 11;
几个高阶函数:map函数和reduce函数filter函数sort函数
作用分别为:作用在数组每一个元素,循环递归,过滤,排序
https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001435119854495d29b9b3d7028477a96ed74db95032675000#0
闭包:返回函数而不是函数的结果
用于不需要立刻得出结果,而是在后面的代码中,根据需要再计算
匿名函数
“创建一个匿名函数并立刻执行”的语法:
(function (x) { return x * x; })(3); // 9
理论上讲,创建一个匿名函数并立刻执行可以这么写:function (x) { return x * x } (3);
但是由于JavaScript语法解析的问题,会报SyntaxError错误,因此需要用括号把整个函数定义括起来:(function (x) { return x * x }) (3);
箭头函数
ES6标准新增了一种新的函数:Arrow Function(箭头函数)。
为什么叫Arrow Function?因为它的定义用的就是一个箭头:
x => x * x
上面的箭头函数相当于:function (x) { return x * x; }
箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连{ ... }和return都省略掉了。还有一种可以包含多条语句,这时候就不能省略{ ... }和return:x => { if (x > 0) { return x * x; } else { return - x * x; } }
如果参数不是一个,就需要用括号()括起来:// 两个参数:
(x, y) => x * x + y * y
// 无参数:() => 3.14
// 可变参数:(x, y, ...rest) => { var i, sum = x + y; for (i=0; i
如果要返回一个对象,就要注意,如果是单表达式,这么写的话会报错:// SyntaxError: x => { foo: x }
因为和函数体的{ ... }有语法冲突,所以要改为:// ok: x => ({ foo: x })
generator:一个可以多次返回值的函数
跟函数很像,定义如下:
function* foo(x) { yield x + 1; yield x + 2; return x + 3; }
generator和函数不同的是,generator由function*定义(注意多出的*号),并且,除了return语句,还可以用yield返回多次。
调用generator对象有两个方法,一是不断地调用generator对象的next()方法:
var f = fib(5); f.next(); // {value: 0, done: false} f.next(); // {value: 1, done: false} f.next(); // {value: 1, done: false} f.next(); // {value: 2, done: false} f.next(); // {value: 3, done: false} f.next(); // {value: undefined, done: true}
next()方法会执行generator的代码,然后,每次遇到yield x;就返回一个对象{value: x, done: true/false},然后“暂停”。返回的value就是yield的返回值,done表示这个generator是否已经执行结束了。如果done为true,则value就是return的返回值。当执行到done为true时,这个generator对象就已经全部执行完毕,不要再继续调用next()了。
第二个方法是直接用for ... of循环迭代generator对象,这种方式不需要我们自己判断done:
'use strict' function* fib(max) { var t, a = 0, b = 1, n = 0; while (n < max) { yield a; [a, b] = [b, a + b]; n ++; } return; } for (var x of fib(10)) { console.log(x); // 依次输出0, 1, 1, 2, 3, ... }
Typeof为了区分对象的类型,我们用
typeof
操作符获取对象的类型,它总是返回一个字符串:typeof 123; // 'number' typeof NaN; // 'number' typeof 'str'; // 'string' typeof true; // 'boolean' typeof undefined; // 'undefined' typeof Math.abs; // 'function' typeof null; // 'object' typeof []; // 'object' typeof {}; // 'object'
可见,
number
、string
、boolean
、function
和undefined
有别于其他类型。特别注意null
的类型是object
,Array
的类型也是object
,如果我们用typeof
将无法区分出null
、Array
和通常意义上的object——{}
。
包装对象除了这些类型外,JavaScript还提供了包装对象,熟悉Java的小伙伴肯定很清楚
int
和Integer
这种暧昧关系。
number
、boolean
和string
都有包装对象。没错,在JavaScript中,字符串也区分string
类型和它的包装类型。包装对象用new
创建:var n = new Number(123); // 123,生成了新的包装类型 var b = new Boolean(true); // true,生成了新的包装类型 var s = new String('str'); // 'str',生成了新的包装类型
虽然包装对象看上去和原来的值一模一样,显示出来也是一模一样,但他们的类型已经变为
object
了!所以,包装对象和原始值用===
比较会返回false
:typeof new Number(123); // 'object' new Number(123) === 123; // false typeof new Boolean(true); // 'object' new Boolean(true) === true; // false typeof new String('str'); // 'object' new String('str') === 'str'; // false
所以闲的蛋疼也不要使用包装对象!尤其是针对
string
类型!!!如果我们在使用
Number
、Boolean
和String
时,没有写new
会发生什么情况?此时,
Number()
、Boolean
和String()
被当做普通函数,把任何类型的数据转换为number
、boolean
和string
类型(注意不是其包装类型):var n = Number('123'); // 123,相当于parseInt()或parseFloat() typeof n; // 'number' var b = Boolean('true'); // true typeof b; // 'boolean' var b2 = Boolean('false'); // true! 'false'字符串转换结果为true!因为它是非空字符串! var b3 = Boolean(''); // false var s = String(123.45); // '123.45' typeof s; // 'string'
是不是感觉头大了?这就是JavaScript特有的催眠魅力!
总结一下,有这么几条规则需要遵守:
不要使用
new Number()
、new Boolean()
、new String()
创建包装对象;用
parseInt()
或parseFloat()
来转换任意类型到number
;用
String()
来转换任意类型到string
,或者直接调用某个对象的toString()
方法;通常不必把任意类型转换为
boolean
再判断,因为可以直接写if (myVar) {...}
;
typeof
操作符可以判断出number
、boolean
、string
、function
和undefined
;判断
Array
要使用Array.isArray(arr)
;判断
null
请使用myVar === null
;判断某个全局变量是否存在用
typeof window.myVar === 'undefined'
;函数内部判断某个变量是否存在用
typeof myVar === 'undefined'
。最后有细心的同学指出,任何对象都有
toString()
方法吗?null
和undefined
就没有!确实如此,这两个特殊值要除外,虽然null
还伪装成了object
类型。更细心的同学指出,
number
对象调用toString()
报SyntaxError:123.toString(); // SyntaxError
遇到这种情况,要特殊处理一下:
123..toString(); // '123', 注意是两个点! (123).toString(); // '123'
Date对象
var now = new Date(); now; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST) now.getFullYear(); // 2015, 年份 now.getMonth(); // 5, 月份,注意月份范围是0~11,5表示六月 now.getDate(); // 24, 表示24号 now.getDay(); // 3, 表示星期三 now.getHours(); // 19, 24小时制 now.getMinutes(); // 49, 分钟 now.getSeconds(); // 22, 秒 now.getMilliseconds(); // 875, 毫秒数 now.getTime(); // 1435146562875, 以number形式表示的时间戳
如果要创建一个指定日期和时间的
Date
对象,可以用:var d = new Date(2015, 5, 19, 20, 15, 30, 123); d; // Fri Jun 19 2015 20:15:30 GMT+0800 (CST)
RegExp:正则匹配对象
有了准备知识,我们就可以在JavaScript中使用正则表达式了。
JavaScript有两种方式创建一个正则表达式:
第一种方式是直接通过
/正则表达式/
写出来,第二种方式是通过new RegExp('正则表达式')
创建一个RegExp对象。两种写法是一样的:
var re1 = /ABC\-001/; var re2 = new RegExp('ABC\\-001'); re1; // /ABC\-001/ re2; // /ABC\-001/
注意,如果使用第二种写法,因为字符串的转义问题,字符串的两个
\\
实际上是一个\
。先看看如何判断正则表达式是否匹配:
var re = /^\d{3}\-\d{3,8}$/; re.test('010-12345'); // true re.test('010-1234x'); // false re.test('010 12345'); // false
RegExp对象的
test()
方法用于测试给定的字符串是否符合条件。
序列化
JavaScript对象的JSON序列化
第一个参数为对象变量,第二个参数指定属性或者传一个函数,第三个参数为空格缩进
JSON.stringify(xiaoming, ['name', 'skills'], ' ');
反序列化
拿到一个JSON格式的字符串,我们直接用
JSON.parse()
把它变成一个JavaScript对象: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
JSON.parse()
还可以接收一个函数,用来转换解析出的属性var obj = JSON.parse('{"name":"小明","age":14}', function (key, value) { if (key === 'name') { return value + '同学'; } return value; }); console.log(JSON.stringify(obj)); // {name: '小明同学', age: 14}
面对对象编程
JavaScript中的面向对象编程
JavaScript不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。
// 原型对象: var Student = { name: 'Robot', height: 1.2, run: function () { console.log(this.name + ' is running...'); } }; function createStudent(name) { // 基于Student原型创建一个新对象: var s = Object.create(Student); // 初始化新对象: s.name = name; return s; } var xiaoming = createStudent('小明'); xiaoming.run(); // 小明 is running... xiaoming.__proto__ === Student; // true
这是创建原型继承的一种方法,JavaScript还有其他方法来创建对象,我们在后面会一一讲到。
构造函数
除了直接用
{ ... }
创建一个对象外,JavaScript还可以用一种构造函数的方法来创建对象。它的用法是,先定义一个构造函数:function Student(name) { this.name = name; this.hello = function () { alert('Hello, ' + this.name + '!'); } }
你会问,咦,这不是一个普通函数吗?
这确实是一个普通函数,但是在JavaScript中,可以用关键字
new
来调用这个函数,并返回一个对象:var xiaoming = new Student('小明'); xiaoming.name; // '小明' xiaoming.hello(); // Hello, 小明!
注意,如果不写
new
,这就是一个普通函数,它返回undefined
。但是,如果写了new
,它就变成了一个构造函数,它绑定的this
指向新创建的对象,并默认返回this
,也就是说,不需要在最后写return this;
。新创建的
xiaoming
的原型链是:xiaoming ----> Student.prototype ----> Object.prototype ----> null
也就是说,
xiaoming
的原型指向函数Student
的原型。如果你又创建了xiaohong
、xiaojun
,那么这些对象的原型与xiaoming
是一样的:xiaoming ↘ xiaohong -→ Student.prototype ----> Object.prototype ----> null xiaojun ↗
用
new Student()
创建的对象还从原型上获得了一个constructor
属性,它指向函数Student
本身:xiaoming.constructor === Student.prototype.constructor; // true Student.prototype.constructor === Student; // true Object.getPrototypeOf(xiaoming) === Student.prototype; // true xiaoming instanceof Student; // true
class
新的关键字
class
从ES6开始正式被引入到JavaScript中。class
的目的就是让定义类更简单。如果用新的
class
关键字来编写Student
,可以这样写:class Student { constructor(name) { this.name = name; } hello() { alert('Hello, ' + this.name + '!'); } }
比较一下就可以发现,
class
的定义包含了构造函数constructor
和定义在原型对象上的函数hello()
(注意没有function
关键字),这样就避免了Student.prototype.hello = function () {...}
这样分散的代码。最后,创建一个
Student
对象代码和前面章节完全一样:var xiaoming = new Student('小明'); xiaoming.hello();
class继承
用
class
定义对象的另一个巨大的好处是继承更方便了。想一想我们从Student
派生一个PrimaryStudent
需要编写的代码量。现在,原型继承的中间对象,原型对象的构造函数等等都不需要考虑了,直接通过extends
来实现:class PrimaryStudent extends Student { constructor(name, grade) { super(name); // 记得用super调用父类的构造方法! this.grade = grade; } myGrade() { alert('I am at grade ' + this.grade); } }
常见对象
window
window
对象不但充当全局作用域,而且表示浏览器窗口。
window
对象有innerWidth
和innerHeight
属性,可以获取浏览器窗口的内部宽度和高度。内部宽高是指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高。兼容性:IE<=8不支持。
对应的,还有一个
outerWidth
和outerHeight
属性,可以获取浏览器窗口的整个宽高。console.log('window inner size: ' + window.innerWidth + ' x ' + window.innerHeight);
navigator
navigator
对象表示浏览器的信息,最常用的属性包括:
- navigator.appName:浏览器名称;
- navigator.appVersion:浏览器版本;
- navigator.language:浏览器设置的语言;
- navigator.platform:操作系统类型;
- navigator.userAgent:浏览器设定的
User-Agent
字符串。console.log('appName = ' + navigator.appName); console.log('appVersion = ' + navigator.appVersion); console.log('language = ' + navigator.language); console.log('platform = ' + navigator.platform); console.log('userAgent = ' + navigator.userAgent);
请注意,
navigator
的信息可以很容易地被用户修改,所以JavaScript读取的值不一定是正确的。很多初学者为了针对不同浏览器编写不同的代码,喜欢用if
判断浏览器版本,例如:var width; if (getIEVersion(navigator.userAgent) < 9) { width = document.body.clientWidth; } else { width = window.innerWidth; }
但这样既可能判断不准确,也很难维护代码。正确的方法是充分利用JavaScript对不存在属性返回
undefined
的特性,直接用短路运算符||
计算:var width = window.innerWidth || document.body.clientWidth;
screen
screen
对象表示屏幕的信息,常用的属性有:
- screen.width:屏幕宽度,以像素为单位;
- screen.height:屏幕高度,以像素为单位;
- screen.colorDepth:返回颜色位数,如8、16、24。
console.log('Screen size = ' + screen.width + ' x ' + screen.height);
location
location
对象表示当前页面的URL信息。例如,一个完整的URL:http://www.example.com:8080/path/index.html?a=1&b=2#TOP
可以用
location.href
获取。要获得URL各个部分的值,可以这么写:location.protocol; // 'http' location.host; // 'www.example.com' location.port; // '8080' location.pathname; // '/path/index.html' location.search; // '?a=1&b=2' location.hash; // 'TOP'
if (confirm('重新加载当前页' + location.href + '?')) {
location.reload();
} else {
location.assign('/'); // 设置一个新的URL地址
}document
document
对象表示当前页面。由于HTML在浏览器中以DOM形式表示为树形结构,document
对象就是整个DOM树的根节点。
document
的title
属性是从HTML文档中的读取的,但是可以动态改变:
xxx document.title = '努力学习JavaScript!';
要查找DOM树的某个节点,需要从
document
对象开始查找。最常用的查找是根据ID和Tag Name。我们先准备HTML数据:
用
document
对象提供的getElementById()
和getElementsByTagName()
可以按ID获得一个DOM节点和按Tag名称获得一组DOM节点:var i, s, menu, drinks; menu = document.getElementById('drink-menu'); menu.tagName; // 'DL' drinks = document.getElementsByTagName('dt'); s = '提供的饮料有:'; for (i=0; i
document
对象还有一个cookie
属性,可以获取当前页面的Cookie。Cookie是由服务器发送的key-value标示符。因为HTTP协议是无状态的,但是服务器要区分到底是哪个用户发过来的请求,就可以用Cookie来区分。当一个用户成功登录后,服务器发送一个Cookie给浏览器,例如
user=ABC123XYZ(加密的字符串)...
,此后,浏览器访问该网站时,会在请求头附上这个Cookie,服务器根据Cookie即可区分出用户。Cookie还可以存储网站的一些设置,例如,页面显示的语言等等。
JavaScript可以通过
document.cookie
读取到当前页面的Cookie:document.cookie; // 'v=123; remember=true; prefer=zh'
由于JavaScript能读取到页面的Cookie,而用户的登录信息通常也存在Cookie中,这就造成了巨大的安全隐患,这是因为在HTML页面中引入第三方的JavaScript代码是允许的:
...
如果引入的第三方的JavaScript中存在恶意代码,则
www.foo.com
网站将直接获取到www.example.com
网站的用户登录信息。为了解决这个问题,服务器在设置Cookie时可以使用
httpOnly
,设定了httpOnly
的Cookie将不能被JavaScript读取。这个行为由浏览器实现,主流浏览器均支持httpOnly
选项,IE从IE6 SP1开始支持。为了确保安全,服务器端在设置Cookie时,应该始终坚持使用
httpOnly
。history
history
对象保存了浏览器的历史记录,JavaScript可以调用history
对象的back()
或forward ()
,相当于用户点击了浏览器的“后退”或“前进”按钮。这个对象属于历史遗留对象,对于现代Web页面来说,由于大量使用AJAX和页面交互,简单粗暴地调用
history.back()
可能会让用户感到非常愤怒。新手开始设计Web页面时喜欢在登录页登录成功时调用
history.back()
,试图回到登录前的页面。这是一种错误的方法。任何情况,你都不应该使用
history
这个对象了。
操作DOM
由于HTML文档被浏览器解析后就是一棵DOM树,要改变HTML的结构,就需要通过JavaScript来操作DOM。
始终记住DOM是一个树形结构。操作一个DOM节点实际上就是这么几个操作:
更新:更新该DOM节点的内容,相当于更新了该DOM节点表示的HTML的内容;
遍历:遍历该DOM节点下的子节点,以便进行进一步操作;
添加:在该DOM节点下新增一个子节点,相当于动态增加了一个HTML节点;
删除:将该节点从HTML中删除,相当于删掉了该DOM节点的内容以及它包含的所有子节点。
在操作一个DOM节点前,我们需要通过各种方式先拿到这个DOM节点。最常用的方法是
document.getElementById()
和document.getElementsByTagName()
,以及CSS选择器document.getElementsByClassName()
。由于ID在HTML文档中是唯一的,所以
document.getElementById()
可以直接定位唯一的一个DOM节点。document.getElementsByTagName()
和document.getElementsByClassName()
总是返回一组DOM节点。要精确地选择DOM,可以先定位父节点,再从父节点开始选择,以缩小范围。// 返回ID为'test'的节点: var test = document.getElementById('test'); // 先定位ID为'test-table'的节点,再返回其内部所有tr节点: var trs = document.getElementById('test-table').getElementsByTagName('tr'); // 先定位ID为'test-div'的节点,再返回其内部所有class包含red的节点: var reds = document.getElementById('test-div').getElementsByClassName('red'); // 获取节点test下的所有直属子节点: var cs = test.children; // 获取节点test下第一个、最后一个子节点: var first = test.firstElementChild; var last = test.lastElementChild;
第二种方法是使用
querySelector()
和querySelectorAll()
,需要了解selector语法,然后使用条件来获取节点,更加方便:// 通过querySelector获取ID为q1的节点: var q1 = document.querySelector('#q1'); // 通过querySelectorAll获取q1节点内的符合条件的所有节点: var ps = q1.querySelectorAll('div.highlighted > p');
注意:低版本的IE<8不支持
querySelector
和querySelectorAll
。IE8仅有限支持。严格地讲,我们这里的DOM节点是指
Element
,但是DOM节点实际上是Node
,在HTML中,Node
包括Element
、Comment
、CDATA_SECTION
等很多种,以及根节点Document
类型,但是,绝大多数时候我们只关心Element
,也就是实际控制页面结构的Node
,其他类型的Node
忽略即可。根节点Document
已经自动绑定为全局变量document
。
示例代码
JavaScript
Java
Python
Ruby
Swift
Scheme
Haskell
// 选择
JavaScript
: var js = document.getElementById("test-p"); // 选择Python
,Ruby
,Swift
: var arr = document.getElementsByClassName("c-red")[1].children; // 选择Haskell
: var haskell = document.getElementsByClassName("c-green")[1].children[1];
修改DOM
一种是修改
innerHTML
属性,这个方式非常强大,不但可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树:// 获取
...
var p = document.getElementById('p-id'); // 设置文本为abc: p.innerHTML = 'ABC'; //ABC
// 设置HTML: p.innerHTML = 'ABC RED XYZ'; //...
的内部结构已修改用
innerHTML
时要注意,是否需要写入HTML。如果写入的字符串是通过网络拿到了,要注意对字符编码来避免XSS攻击。第二种是修改
innerText
或textContent
属性,这样可以自动对字符串进行HTML编码,保证无法设置任何HTML标签:// 获取
...
var p = document.getElementById('p-id'); // 设置文本: p.innerText = ''; // HTML被自动编码,无法设置一个这种方式的缺点是扰乱了浏览器对form的正常提交。浏览器默认点击
注意要
return true
来告诉浏览器继续提交,如果return false
,浏览器将不会继续提交form,这种情况通常对应用户输入有误,提示用户错误信息后终止提交form。在检查和修改
时,要充分利用
来传递数据。
例如,很多登录表单希望用户输入用户名和口令,但是,安全考虑,提交表单时不传输明文口令,而是口令的MD5。普通JavaScript开发人员会直接修改
:
这个做法看上去没啥问题,但用户输入了口令提交时,口令框的显示会突然从几个
*
变成32个*
(因为MD5有32个字符)。要想不改变用户的输入,可以利用
实现:
注意到
id
为md5-password
的标记了
name="password"
,而用户输入的id
为input-password
的没有
name
属性。没有name
属性的的数据不会被提交。
操作文件
在HTML表单中,可以上传文件的唯一控件就是
。
注意:当一个表单包含
时,表单的
enctype
必须指定为multipart/form-data
,method
必须指定为post
,浏览器才能正确编码并以multipart/form-data
格式发送表单的数据。出于安全考虑,浏览器只允许用户点击
来选择本地文件,用JavaScript对
的
value
赋值是没有任何效果的。当用户选择了上传某个文件后,JavaScript也无法获得该文件的真实路径:通常,上传的文件都由后台服务器处理,JavaScript可以在提交表单时对文件扩展名做检查,以便防止用户上传无效格式的文件:
var f = document.getElementById('test-file-upload'); var filename = f.value; // 'C:\fakepath\test.png' if (!filename || !(filename.endsWith('.jpg') || filename.endsWith('.png') || filename.endsWith('.gif'))) { alert('Can only upload image file.'); return false; }
File API
由于JavaScript对用户上传的文件操作非常有限,尤其是无法读取文件内容,使得很多需要操作文件的网页不得不用Flash这样的第三方插件来实现。
随着HTML5的普及,新增的File API允许JavaScript读取文件内容,获得更多的文件信息。
HTML5的File API提供了
File
和FileReader
两个主要对象,可以获得文件信息并读取文件。下面的例子演示了如何读取用户选取的图片文件,并在一个
中预览图像:var fileInput = document.getElementById('test-image-file'), info = document.getElementById('test-file-info'), preview = document.getElementById('test-image-preview'); // 监听change事件: fileInput.addEventListener('change', function () { // 清除背景图片: preview.style.backgroundImage = ''; // 检查文件是否选择: if (!fileInput.value) { info.innerHTML = '没有选择文件'; return; } // 获取File引用: var file = fileInput.files[0]; // 获取File信息: info.innerHTML = '文件: ' + file.name + '
' + '大小: ' + file.size + '
' + '修改: ' + file.lastModifiedDate; if (file.type !== 'image/jpeg' && file.type !== 'image/png' && file.type !== 'image/gif') { alert('不是有效的图片文件!'); return; } // 读取文件: var reader = new FileReader(); reader.onload = function(e) { var data = e.target.result; // 'data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...' preview.style.backgroundImage = 'url(' + data + ')'; }; // 以DataURL的形式读取文件: reader.readAsDataURL(file); });上面的代码演示了如何通过HTML5的File API读取文件内容。以DataURL的形式读取到的文件是一个字符串,类似于
data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...
,常用于设置图像。如果需要服务器端处理,把字符串base64,
后面的字符发送给服务器并用Base64解码就可以得到原始文件的二进制内容。
回调
上面的代码还演示了JavaScript的一个重要的特性就是单线程执行模式。在JavaScript中,浏览器的JavaScript执行引擎在执行JavaScript代码时,总是以单线程模式执行,也就是说,任何时候,JavaScript代码都不可能同时有多于1个线程在执行。
你可能会问,单线程模式执行的JavaScript,如何处理多任务?
在JavaScript中,执行多任务实际上都是异步调用,比如上面的代码:
reader.readAsDataURL(file);
就会发起一个异步操作来读取文件内容。因为是异步操作,所以我们在JavaScript代码中就不知道什么时候操作结束,因此需要先设置一个回调函数:
reader.onload = function(e) { // 当文件读取完成后,自动调用此函数: };
当文件读取完成后,JavaScript引擎将自动调用我们设置的回调函数。执行回调函数时,文件已经读取完毕,所以我们可以在回调函数内部安全地获得文件内容。
AJAX
Asynchronous JavaScript and XML,意思就是用JavaScript执行异步网络请求。
https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434499861493e7c35be5e0864769a2c06afb4754acc6000
Primise
异步执行中使用的一个对象
Canvas
Canvas是HTML5新增的组件,它就像一块幕布,可以用JavaScript在上面绘制各种图表、动画等。
JQuery
jQuery是一个快速、简洁的JavaScript框架,是继Prototype之后又一个优秀的JavaScript代码库(或JavaScript框架)
你可能感兴趣的:(JavaScript笔记整合(1))