语法
每个语句以;
结束,语句块用{...}
。但是,JavaScript并不强制要求在每个语句的结尾加;
第一种是==
比较,它会自动转换数据类型再比较,很多时候,会得到非常诡异的结果;
第二种是===
比较,它不会自动转换数据类型,如果数据类型不一致,返回false
,如果一致,再比较。
由于JavaScript这个设计缺陷,不要使用==
比较,始终坚持使用===
比较。
唯一能判断NaN
的方法是通过isNaN()
函数:
null
表示一个空的值,而undefined
表示值未定义。事实证明,这并没有什么卵用,区分两者的意义不大。大多数情况下,我们都应该用null
。undefined
仅仅在判断函数参数是否传递的情况下有用。
数组
[1, 2, 3.14, 'Hello', null, true];
new Array(1, 2, 3); // 创建了数组[1, 2, 3]
如果通过索引赋值时,索引超过了范围,会引起Array
大小的变化
slice 截取部分元素
push 向结尾添加元素
pop 弹出元素,空数组返回undefined
unshift 向头部添加元素
shift 从头部移除元素
sort 按照默认顺序排序
reverse 翻转元素的位置
plice 方法是修改Array的“万能方法”,它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素
concat 连接两个Array
join()方法是一个非常实用的方法,它把当前Array的每个元素都用指定的字符串连接起来,然后返回连接后的字符串
对象
var person = {
name: 'Bob',
age: 20,
tags: ['js', 'web', 'mobile'],
city: 'Beijing',
hasCar: true,
zipcode: null
};
person['name']; // '小红'
person.name; // '小红'
动态语言特性
可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量,但是要注意只能用var
申明一次
如果一个变量没有通过
var
申明就被使用,那么该变量就自动被申明为全局变量
可以自由地给一个对象添加或删除属性
delete person.age; // 删除age属性
in判断一个属性是否存在,可以是通过继承得到的
hasOwnProperty() 判断某个属性是否是自身拥有的
字符串
JavaScript的字符串就是用''
或""
括起来的字符表示。
字符串模板
var name = '小明';
var age = 20;
var message = `你好, ${name}, 你今年${age}岁了!`;
alert(message);
for...in...循环
var o = {
name: 'Jack',
age: 20,
city: 'Beijing'
};
for (var key in o) {
console.log(key); // 'name', 'age', 'city'
}
Map
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
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
Set
var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}
s.add(4);
s.delete(3);
iterable
ES6标准引入了新的iterable类型,Array、Map和Set都属于iterable类型
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]);
}
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
console.log(value);
});
函数
function abs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
var abs = function (x) {
if (x >= 0) {
return x;
} else {
return -x;
}
};
允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数
关键字arguments
,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments
类似Array
但它不是一个Array
reset参数
function foo(a, b, ...rest) {
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
foo(1, 2, 3, 4, 5);
// 结果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]
foo(1);
// 结果:
// a = 1
// b = undefined
// Array []
用var
申明的变量实际上是有作用域的
变量提升
JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部:
'use strict';
function foo() {
var x = 'Hello, ' + y;
console.log(x);
var y = 'Bob';
}
foo();
//实际上和下面是等价的
function foo() {
var y; // 提升变量y的申明,此时y为undefined
var x = 'Hello, ' + y;
console.log(x);
y = 'Bob';
}
请严格遵守“在函数内部首先申明所有变量”这一规则
function foo() {
var
x = 1, // x初始化为1
y = x + 1, // y初始化为2
z, i; // z和i为undefined
// 其他语句:
for (i=0; i<100; i++) {
...
}
}
全局作用域
不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象window
,全局作用域的变量实际上被绑定到window
的一个属性
'use strict';
var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'
名字空间
全局变量会绑定到window
上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。
减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
return 'foo';
};
let 关键字
ES6引入了新的关键字let
,用let
替代var
可以申明一个块级作用域的变量
'use strict';
function foo() {
var sum = 0;
for (let i=0; i<100; i++) {
sum += i;
}
// SyntaxError:
i += 1;
}
const
来定义常量,const
与let
都具有块级作用域
解构赋值
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素,只对z赋值第三个元素
z; // 'ES6'
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school'
};
var {name, age, passport} = person;//取出若干个属性
如果需要从一个对象中取出若干属性,也可以使用解构赋值
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school',
address: {
city: 'Beijing',
street: 'No.1 Road',
zipcode: '100001'
}
};
var {name, address: {city, zip}} = person;
name; // '小明'
city; // 'Beijing'
zip; // undefined, 因为属性名是zipcode而不是zip
// 注意: address不是变量,而是为了让city和zip获得嵌套的address对象的属性:
address; // Uncaught ReferenceError: address is not defined
//使用场景,解构赋值的函数参数,这样可以直接传一个Data对象了
function buildDate({year, month, day, hour=0, minute=0, second=0}) {
return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
}
方法
在一个对象中绑定函数,称为这个对象的方法。
在一个方法内部,this
是一个特殊变量,它始终指向当前对象
要保证this
指向正确,必须用obj.xxx()
的形式调用
apply方法 call方法
控制this
的指向的,函数本身的apply
方法,它接收两个参数,第一个参数就是需要绑定的this
变量,第二个参数是Array
,表示函数本身的参数
与apply()
类似的方法是call()
,唯一区别是:
apply()
把参数打包成Array
再传入;call()
把参数按顺序传入。
重要的应用是动态改变原函数
高阶函数
一个函数可以接收另一个函数作为参数,这种函数就称之为高阶函数
function add(x, y, f) {
return f(x) + f(y);
}
var x = add(-5, 6, Math.abs); // 11
map/reduce/filter/sort
函数作为返回值
function lazy_sum(arr) {
var sum = function () {
return arr.reduce(function (x, y) {
return x + y;
});
}
return sum;
}
闭包
函数lazy_sum
中又定义了函数sum
,并且,内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
注意到返回的函数在其定义内部引用了局部变量arr
,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
立即执行的匿名函数
(function (x) {
return x * x;
})(3);
在没有class
机制,只有函数的语言里,借助闭包,可以封装一个私有变量!!
箭头函数
(x, y) => x * x + y * y
// 可变参数:
(x, y, ...rest) => {
var i, sum = x + y;
for (i=0; i { foo: x }
// ok:
x => ({ foo: x })
箭头函数和匿名函数有个明显的区别:箭头函数内部的this
是词法作用域,由上下文确定
generator
generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}
generator和函数不同的是,generator由function*
定义(注意多出的*
号),并且,除了return
语句,还可以用yield
返回多次。
调用generator对象有两个方法,一是不断地调用generator对象的next()
方法;
第二个方法是直接用for ... of
循环迭代generator对象,这种方式不需要我们自己判断done
。
generator和普通函数相比,有什么用?
generator就可以实现需要用面向对象才能实现的功能;
把异步回调代码变成“同步”代码;
标准对象
在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'
。
Date、RegExp、JSON
avaScript有两种方式创建一个正则表达式:
第一种方式是直接通过/正则表达式/
写出来,第二种方式是通过new RegExp('正则表达式')
创建一个RegExp对象。
var re1 = /ABC\-001/;
var re2 = new RegExp('ABC\\-001');
var s = JSON.stringify(xiaoming);//序列化
JSON.parse('{"name":"小明","age":14}');//反序列化
面向对象编程
JavaScript不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。
原型链最末端的我们称为对象,其指向的我们称为原型(对象)
Object.create 创建对象
// 原型对象:
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
构造函数
//这个就是构造函数
function Student(name) {
this.name = name;
this.hello = function () {
alert('Hello, ' + this.name + '!');
}
}
//创建对象
var xiaoming = new Student('小明');//写了new,Student方法就变成了一个构造函数,它绑定的this指向新创建的对象,并默认返回this,也就是说,不需要在最后写return this;
xiaoming.name; // '小明'
xiaoming.hello(); // Hello, 小明!
//原型链:xiaoming ----> Student.prototype ----> Object.prototype ----> null
xiaoming.constructor === Student.prototype.constructor; // true
Student.prototype.constructor === Student; // true
Object.getPrototypeOf(xiaoming) === Student.prototype; // true
xiaoming instanceof Student; // true
用new Student()
创建的对象还从原型上获得了一个constructor
属性,它指向函数Student
本身
红色箭头是原型链。注意,Student.prototype
指向的对象就是xiaoming
、xiaohong
的原型对象,这个原型对象自己还有个属性constructor
,指向Student
函数本身。
另外,函数Student
恰好有个属性prototype
指向xiaoming
、xiaohong
的原型对象,但是xiaoming
、xiaohong
这些对象可没有prototype
这个属性,不过可以用__proto__
这个非标准用法来查看。
现在我们就认为xiaoming
、xiaohong
这些对象“继承”自Student
。
原型继承道格拉斯(发明JSON的那个)写法
中间对象可以用一个空函数F
来实现
// PrimaryStudent构造函数:
function PrimaryStudent(props) {
Student.call(this, props);
this.grade = props.grade || 1;
}
// 空函数F:
function F() {
}
// 把F的原型指向Student.prototype:
F.prototype = Student.prototype;
// 把PrimaryStudent的原型指向一个新的F对象,F对象的原型正好指向Student.prototype:
PrimaryStudent.prototype = new F();
// 把PrimaryStudent原型的构造函数修复为PrimaryStudent:
PrimaryStudent.prototype.constructor = PrimaryStudent;
// 继续在PrimaryStudent原型(就是new F()对象)上定义方法:
PrimaryStudent.prototype.getGrade = function () {
return this.grade;
};
// 创建xiaoming:
var xiaoming = new PrimaryStudent({
name: '小明',
grade: 2
});
xiaoming.name; // '小明'
xiaoming.grade; // 2
// 验证原型:
xiaoming.__proto__ === PrimaryStudent.prototype; // true
xiaoming.__proto__.__proto__ === Student.prototype; // true
// 验证继承关系:
xiaoming instanceof PrimaryStudent; // true
xiaoming instanceof Student; // true
class 继承
function Student(name) {
this.name = name;
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}
class PrimaryStudent extends Student {
constructor(name, grade) {
super(name); // 记得用super调用父类的构造方法!
this.grade = grade;
}
myGrade() {
alert('I am at grade ' + this.grade);
}
}
浏览器对象
window
window
对象不但充当全局作用域,而且表示浏览器窗口
navigator
navigator
对象表示浏览器的信息
screen
screen
对象表示屏幕的信息
location
location
对象表示当前页面的URL信息
document
document
对象表示当前页面。由于HTML在浏览器中以DOM形式表示为树形结构,document
对象就是整个DOM树的根节点。
history
history
对象保存了浏览器的历史记录,不应该直接使用这个对象
操作DOM、操作表单、操作文件
document.getElementById()
和document.getElementsByTagName()
,以及CSS选择器document.getElementsByClassName()
AJAX
关于异步的理解
在现代浏览器上写AJAX主要依靠XMLHttpRequest
对象
var request = new XMLHttpRequest(); // 新建XMLHttpRequest对象
request.onreadystatechange = function () { // 状态发生变化时,函数被回调
if (request.readyState === 4) { // 成功完成
// 判断响应结果:
if (request.status === 200) {
// 成功,通过responseText拿到响应的文本:
return success(request.responseText);
} else {
// 失败,根据响应码判断失败原因:
return fail(request.status);
}
} else {
// HTTP请求还在继续...
}
}
// 发送请求:
request.open('GET', '/api/categories');
request.send();
alert('请求已发送,请等待响应...');
安全限制/跨域策略
一是通过Flash插件发送HTTP请求
二是通过在同源域名下架设一个代理服务器来转发,JavaScript负责把请求发送到代理服务器
三是JSONP,它有个限制,只能用GET请求,并且要求返回JavaScript。这种方式跨域实际上是利用了浏览器允许跨域引用JavaScript资源
如果浏览器支持HTML5,那么就可以一劳永逸地使用新的跨域策略:CORS(Cross-Origin Resource Sharing)
Promise
在JavaScript的世界中,所有代码都是单线程执行的。异步执行可以用回调函数实现。
古人云:“君子一诺千金”,“承诺将来会执行”的对象在JavaScript中称为Promise对象。
//一个异步执行的test函数
function test(resolve, reject) {//第一个参数resolve,第二个参数rejuect
var timeOut = Math.random() * 2;
log('set timeout to: ' + timeOut + ' seconds.');
setTimeout(function () {
if (timeOut < 1) {
log('call resolve()...');
resolve('200 OK');//成功时调用resolve()
}
else {
log('call reject()...');
reject('timeout in ' + timeOut + ' seconds.');//失败是调用reject()
}
}, timeOut * 1000);
}
var job1 = new Promise(test);
//then执行后会继续返回一个Promise对象
new Promise(test).then(function (result) {
console.log('成功:' + result);
}).catch(function (reason) {
console.log('失败:' + reason);
});
job1.then(job2).then(job3).catch(handleError);//一连串的异步代码,其中,job1、job2和job3都是Promise对象
并行执行的,用Promise.all()
实现;容错执行的,用Promise.race()
实现。
Canvas
var canvas = document.getElementById('test-text-canvas'),
var ctx = canvas.getContext('2d');
var gl = canvas.getContext("webgl"); //3d
错误处理
JavaScript引擎是一个事件驱动的执行引擎,代码总是以单线程执行,而回调函数的执行需要等到下一个满足条件的事件出现后,才会被执行
try ... catch ... finally
涉及到异步代码,无法在调用时捕获,原因就是在捕获的当时,回调函数并未执行,要在回调函数中捕获
throw new Error('输入错误');
Nodejs
HelloWorld
'use strict';
console.log('Hello, world.');
执行 : node hello.js
如果要基于vscode使用:
新建文件夹nodetest -> 新建文件 hello.js -> 打开调试面板,选择配置或修复"launch.json" -> 选择环境 Node.js -> 点击添加配置,选择node 的启动项目 -> 修改如下
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "run hello.js",
"program": "${workspaceFolder}/hello.js"
}
]
}
模块
一个.js文件就称之为一个模块(module),把很多函数分组,分别放到不同的文件里,大大提高了代码的可维护性和复用性。
使用module.exports = variable;
暴露的变量,用var ref = require('module_name');
引用变量的方式叫做CommonJS规范
var s = 'Hello';
function greet(name) {
console.log(s + ', ' + name + '!');
}
module.exports = greet;//把函数'greet'作为模块的输出暴露出去
使用module
'use strict';
// 引入hello模块:
var greet = require('./hello');//如果不写相对路径,只写hello,Node会依次在内置模块、全局模块和当前模块下查找hello.js
var s = 'Michael';
greet(s); // Hello, Michael!
模块的实现原理,如果避免var变量的全局污染?闭包!
如果我们把一段JavaScript代码用一个函数包装起来,这段代码的所有“全局”变量就变成了函数内部的局部变量。
变量module
是Node在加载js文件前准备的一个变量,并将其传入加载函数
基本模块
global 全局对象
process 代表当前Node.js进程
fs 文件系统模块,负责读写文件
stream Node.js提供的又一个仅在服务区端可用的模块,目的是支持“流”这种数据结构
http 创建一个server 服务器
rypto 提供通用的加密和哈希算法
WebSocket
WebSocket是HTML5新增的协议,它的目的是在浏览器和服务器之间建立一个不受限的双向通信的通道,比如说,服务器可以在任意时刻发送消息给浏览器。
参考
廖雪峰的官方网站