一: 语法结构
1.1 字符集
JavaScript程序是Unicode字符集编写的,Unicode是ASCII和Latin-1的超集。
1.2 区分大小写
js是区分大小写的,HTML不区分大小写。
1.3 标识符
标识符就是一个名字,用来对变量和函数进行命名,或用做JavaScript代码中某些循环语句中的跳转位置的标记。
js的标识符必须以 字母、下划线(_)或美元符($)开始,后续的可以是字母、数字、下划线或美元符。
1.4 关键字&保留字
- 关键字 :js把一些标识符拿出来用作自己的关键字,因此,就不能再在程序中把这些关键字用作标识符了。
关键字 |
关键字 |
关键字 |
关键字 |
关键字 |
break |
delete |
function |
return |
typeof |
case |
do |
if |
switch |
var |
catch |
else |
in |
this |
void |
continue |
false |
instanceof |
throw |
while |
debugger |
finally |
new |
true |
with |
default |
for |
null |
try |
|
- 保留字:关键字在当前版本没有用到但是在未来版本可能会用到。
class | const | enum | export | extends
import | super | implements | let | private
public | yield | interface | package | protected
static |
- 严格模式同样对下面的标识符的使用做了限制,它们不能用作变量名、函数名或参数名
- js预定义了很多全局变量和函数,应当避免把它们的名字用作变量名和函数名
name |
name |
name |
name |
name |
arguments |
encodeURI |
Infinity |
Number |
RegEXP |
Array |
encodeURIComponent |
isfinite |
Object |
String |
Boolean |
Error |
isNaN |
parseFloat |
SyntaxError |
Date |
eval |
JSON |
parseInt |
TypeError |
decodeURI |
EvalError |
Math |
RangeError |
undefined |
decodeURIComponent |
Function |
NaN |
ReferenceError |
URIError |
二:类型、值和变量
js数据类型分为两类:原始类型(primitive type)和对象类型(object type)
- 原始类型
包括 数字、字符串、布尔值、null、undefined 。
变量是直接按值存放的,存放在栈内存中的简单数据段,可以直接访问。
- 对象(引用)类型
包括数组、函数、对象。
存放在堆内存中的对象,变量保存的是一个指针,这个指针指向另一个位置。当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。
怎么解释呢?
^-^ 小例子瞬间理解
- 用钱买东西:其实我们的口袋就像是栈,银行就像是堆,要买的物品就变量,要付的钱就是原始类型或引用类型。
- 我们买一包辣条(变量)从口袋(栈)拿出钱(原始类型)付款,钱(原始类型)是在口袋(栈)的,付钱速度是非常快的;
- 如果我们买车(变量)的话也得付钱,这个钱刷银行卡(引用类型),钱在银行(堆),银行卡(引用类型)相当于指针指向你存在银行的大么一大堆钱,不管你在银行存多少钱,你的银行卡大小是固定的。你如果直接带那么多钱(原始类型)放在口袋(栈),会撑爆的~ 所以得带银行卡(引用类型),钱放在银行(堆)。
^-^
为变量赋值时,JavaScript的解释程序必须判断该值是原始类型还是引用类型。解析程序则需尝试判断该值是否为JavaScript的原始类型之一。由于这些原始类型占据的空间是固定的,所以可将它们存储在较小的内存区域栈中。这样存储便于迅速查找变量的值。
如果一个值是引用类型,那么它的存储空间会从堆中分配,由于引用值的大小会改变,所以不能将它放在栈中,否则会降低变量查询速度。相反,放在变量的栈空间中的值是该对象存储在堆中的地址,地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。
js数据类型也可以分为可变类型(mutable)和不可变类型(immutable)
- 可变类型(引用类型内容)
包括数组、函数、对象。
- 不可变类型(原始类型内容)
数字、字符串、布尔值、null、undefined 。
在许多语言中,字符串都被看作引用类型,而非原始类型,因为字符串的长度是可变的。JavaScript 打破了这一传统。
堆(heap)和栈(stack)区别:
堆和栈都是内存中的一部分,有着不同的作用,而且一个程序需要在这片区域上分配内存。
区别 |
堆(heap) |
栈(stack) |
空间分配 |
大小需要自己申请,并指明大小 |
系统自动分配释放 |
存储区 |
队列优先,先进先出(FIFO—first in first out) |
先进后出(FILO—First-In/Last-Out) |
缓存方式 |
二级缓存,生命周期由虚拟机的垃圾回收算法来决定 |
一级缓存,被调用时处于存储空间中,调用完毕立即释放。 |
申请效率 |
由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便. |
速度较快。但程序员是无法控制的。 |
那么问题来了,引用类型怎么比较是否相等呢?
比如两个单独的对象或数组必须比较它们的属性或元素:
function equalArrays(a,b){
if(a.length != b.length) return false; //两个长度不同的数组不相等
for(var i = 0; i<a.length; i++){
if(a[i] != b[i]) return false;
}
return true;
}
趁热理解浅拷贝和深拷贝
对于原始类型如字符串,浅拷贝是对值的复制,对于引用类型如对象来说,浅拷贝是对对象地址的复制。
如前面的买东西的例子:
- 浅拷贝: 对于买辣条付的钱(原始类型)的复制,是真的钱;而买车是对银行卡(引用类型)的复制,两个卡都指向同一个账户,银行里的同一堆钱,而不是新开了一个账户(堆)。一个卡里面钱用了一点另外一个卡钱有会少。
- 深拷贝: 买辣条付的钱(原始类型)的复制,是真的钱;而买车是对银行那堆钱(堆)的复制,变成了另外一堆钱,重新开一个账户办个卡(引用类型)对应着它,即使你把新卡钱花完了也不会影响之前的卡。
2.1 数字
在js真实运行环境中, 0.3-0.2 = 0.099 999 999 999 999 98
2.2 转义字符
转义字符 |
含义 |
\n |
换行符 |
\f |
换页符 |
\r |
回车符 |
\" |
双引号 |
\' |
单引号 |
\\ |
反斜线 |
2.3 null 和 undefined
- 对null执行typeof预算,结果返回字符串
- "object",它表示数字、字符串、对象是“无值的”。
undefined表示变量没有被初始化,这个属性或元素不存在。它的值是‘未定义’
它们都表示值得空缺,运算符‘==’ 会认为它们是相等的, '==='可以判断出来。
2.4 全局对象
全局对象属性是全局定义的符号,js程序可以直接使用。
- 全局属性 : 比如 undefined、Infinity 和 NaN
- 全局函数 : 比如 isNaN()、parseInt() 和 eval()
- 构造函数 : 比如 Data()、RegExp()、String()、Object()和 Array()
- 全局对象 : 比如 Math 和 JSON
2.5 包装对象
存取字符串、数字或布尔值属性时创建的临时对象称为包装对象。
js对象是一种复合值:它是属性或已命名值的集合,通过‘.’符号来引用属性值,当属性值是一个函数的时候,称其为方法。
var str = 'hello world';
var newstr = str.substring(str.indexof(" ")+1, str.length); // 使用字符串的属性
只要引用了字符串str的属性,JavaScript就会将字符串值通过调用 new String(str) 的方式转换成对象,这个对象继承了字符串的方法,并被用来处理属性的引用,一旦属性引用结束,这个新创建的对象就会被销毁。
数字和字符串可以通过 Number() 和 Boolean()构造函数创建一个临时对象,它们的属性都是可读的,不能给它们定义新的属性。
2.6 不可变的原始值和可变的对象引用
js中的原始值(undefined、null、布尔值、数字和字符串)与对象(数组和函数)有着本质区别:
- 原始值的比较是值的比较
原始值是不可更改的,任何方法都无法更改一个原始值。原始值的比较只有当它们的值相等时它们才相等。
var str = 'hello'; //定义一个文本
s.toUpperCase(); // 返回 'HELLO',但是没有改变s的值,它返回的是一个新的字符串的值
console.loh("s的值",s) // 打印出'hello' ,s的值并没被改变
它们的值是可修改的。
var o = { x : 1 }; // 定义一个对象
o.x = 2 ; // 通过修改对象的属性值来更改对象
对象通常称为引用类型(reference type),对象值都是引用,对象的比较均是引用的比较, 当且仅当它们引用同一个基对象时,它们才相等。
var a = [1,2,3]; // 定义一个变量a,引用数组 [1,2,3]
var b = a; // 变量b引用同一个数组
b[0] = 4; // 通过变量b来修改引用的数组
a[0]; // => 4, 变量a也会修改,因为他们引用的是同一个数组
a === b // true, a和b引用的同一个数组,所以他们相等。
将对象(数组)赋值给变量a,仅仅是赋值的引用值,对象(数组)本身并没有复制一次。
var a = [1,2,3]; // 待复制的数组
var b = []; // 目标数组
for(var i = 0; i<a.length; i++){ // 遍历 a引用的数组的每个元素
b[i] = a[i]; // 将元素复制到b中
}
function equalArrays(a,b){
if(a.length != b.length) return false; //两个长度不同的数组不相等
for(var i = 0; i<a.length; i++){
if(a[i] != b[i]) return false;
}
return true;
}
2.7 类型转换
JavaScript中的取值非常灵活,它将根据需要自行转换类型。
值 --> 转换 |
字符串 |
数字 |
布尔值 |
对象 |
undefined |
"undefined" |
NaN |
false |
throws TypeError |
null |
"null" |
0 |
false |
throws TypeError |
true |
"true" |
1 |
|
new Boolean(true) |
false |
"false" |
0 |
|
new Boolean(false) |
""(空字符串) |
|
0 |
false |
new String("") |
"1.2"(非空数字) |
|
1.2 |
true |
new String("1.2") |
"one"(非空、非数字) |
|
NaN |
true |
new String("one") |
0 |
"0" |
|
false |
new Number(0) |
[] |
"" |
0 |
true |
|
- parseInt(string, radix)
- radix 为10 ,10进制
parseInt("10",10) --> 1*10^1 + 0*10^0 = 10
- radix 为2, 2进制
parseInt("10",2) --> 1*2^1 + 0*2^0 = 2
- 参数
string : 要被解析的值。如果参数不是一个字符串,则将其转换为字符串(使用 ToString 抽象操作)。字符串开头的空白符将会被忽略。
radix : 一个介于2和36之间的整数(数学系统的基础),表示上述字符串的基数。比如参数"10"表示使用我们通常使用的十进制数值系统。通常将值默认为10。
- 返回值
返回解析后的整数值。如果被解析参数的第一个字符无法被转化成数值类型,则返回 NaN。
- 描述
把第一个参数转换为字符串并解析它,并返回一个整数或NaN,如果不是NaN,返回的值将是作为指定基数中的数字的第一个参数的整数。
- parseFloat(string)
exp : parseFloat("3.14") -- > 3.14
- 参数
string: 需要被解析成为浮点数的字符串.
- 描述
parseFloat是个全局函数,不属于任何对象.将它的字符串参数解析成为浮点数并返回.如果在解析过程中遇到了正负号(+或-),数字(0-9),小数点,或者科学记数法中的指数(e或E)以外的字符,则它会忽略该字符以及之后的所有字符,返回当前已经解析到的浮点数.同时参数字符串首位的空白符会被忽略.
如果参数字符串的第一个字符不能被解析成为数字,则parseFloat返回NaN.
你可以通过调用isNaN函数来判断parseFloat的返回结果是否是NaN.如果让NaN作为了任意数学运算的操作数,则运算结果必定也是NaN.
parseFloat 也可转换和返回Infinity值. 可以使用isFinite 函数来判断结果是否是一个有限的数值 (非Infinity, -Infinity, 或 NaN).
三: 表达式和运算符
3.1 递增 ‘++’、递减‘--’
- 当运算符(++)在操作数之前, ++i,对操作数进行增量计算,并返回计算后的值,称为前增量。
- 当运算符(++)在操作数之后,i++,它对操作数进行增量运算,但返回的是未做增量计算的值。
var i = 1, j = ++i; // i = 2, j = 2; 数之前,返回加后的
var i = 1, j = i++; // i = 2, j = 1; 数之后,返回没加的
‘--’同理。
四:对象
对象是JavaScript基本数据类型。对象是一种复合值,它将很多值复合在一起,可通过名字访问这些值,对象也可以看做是属性的无序集合。属性名是字符串,我们可以把对象那个看成是从字符串到值的映射。它还可以一个称为原型的对象继承属性。对象的方法通常是继承的属性。这种‘原型式继承’是js 的核心特征。
4.1 JavaScript三类对象和两类属性
- 内置对象(native object)
是 ECMAScript规范定义的对象或类,例如数组、函数、日期、正则表达式。
- 宿主对象(host object)
是有JavaScript解释器所嵌入的宿主环境(比如web浏览器)定义的。客户端JavaScript中表示网页结构的HTMLElement对象均是宿主对象。
- 自定义对象(user-defined object)
由运行在JavaScript代码创建的对象。
- 自有属性(own property)
直接在对象中定义的属性
- 继承属性(inherited property)
在对象的原型对象中定义的属性。
4.2 创建对象
4.2.1 对象直接量
4.2.2 通过new创建对象
new 运算符创建并初始化一个新对象。其后跟随一个函数调用,这个函数称为构造函数。
var empty = new Object();
4.2.3 原型
每一个JavaScript对象(null除外),都和原型相关联,每一个对象都从原型继承属性。
所有通过对象直接量创建的对象都具有同一个原型对象,并可以通过JavaScript代码 Object.prototype获得对原型对象的引用。通过关键词new和构造函数调用创建的对象的原型就是构造函数prototype属性的值。因此同使用{}创建对象一样,通过new Object()创建的对象也继承自 Object.prototype。同样,通过new Array()创建的对象的原型就是Array.prototype,通过new Date() 创建的对象的原型式Date.prototype。
没有原型的对象为数不多,Object.prototype就是其中之一,它不继承任何属性,其他原型对象都是普通对象,普通对象都具有原型。所有的内置构造函数(以及大部分自定义的构造函数)都具有一个继承自 Object.prototype的原型。例如 Date.prototype的属性继承自 Object.prototype,因此由new Date()创建的对象的属性同时继承 Date.prototype和 Object.prototype,这一系列链接的原型对象就是所谓的‘原型链’(prototype chain)。
4.2.4 Object.creat()
var o = Object.creat({x:1, y:2});
4.3 属性的查询与设置
可以通过点(.)和方括号([])获取属性的值。
// 获取信息
var Name = book.name;
var page = book[total page];
// 设置信息
book.name = '书名';
book[total page] = '200页';
4.4 继承
JavaScript对象具有‘自有属性’(own property),也有一些属性是从原型对象继承而来的。
4.5 删除属性
delete运算符可以删除对象的属性,它的操作数应当是一个属性访问表达式。delete只是断开属性与宿主的联系,而不会去属性中的属性。
delete book.name // book不再有属性name
delete运算符只能删除自有属性,不能删除继承属性(要删除继承属性必须从定义这个属性的原型对象上删除它)。
4.6 枚举属性
通常使用 for/in循环遍历对象中所有可枚举的属性(包括自有属性和继承属性),把属性名称赋值给循环变量。对象继承的内置方法不可枚举的。但在代码中给对象添加的属性都是可枚举的。
还有两个用以枚举属性的函数:
- Object.keys()
它返回一个数组,这个数组由对象中可枚举的自有属性的名称组成。
- Object.getOwnPropertyNames()
它返回对象的所有自有属性的名称,而不仅仅是可枚举的属性。
4.7 属性 getter 和 setter
对象属性是由名字、值和一组特性构成,属性可以用一个或两个方法替代: getter/setter。由它们定义的属性称作‘存取器属性’(accessor property),它不同于数据属性(data property),数据属性只有一个简单的值。
和数据属性不同,存取器属性不具有可写性(writable attribute)。如果属性同时具有getter、setter方法,那么它是一个读/写属性。如果它只有getter方法,那么它是一个只读属性;如果只有setter,那么它是一个只写属性,读取只写属性总是返回undefined。
var o = {
// 普通属性
data_prop : value,
// 存取器属性都是成对定义的函数
get accessor_prop() { },
set accessor_prop(value) {}
}
例子:
var p = {
// x和y是普通可读写的数据属性
x : 1,
y :2,
// r 是可读写的存储器属性,它有 getter 和 setter
get r() { return this.x + this.y },
set r(newValue) {
var oldValue = this.x + this.y;
var ratio = newValue / oldValue;
this.x *= ratio;
this.y *= ratio;
}
// onlyRead,它只有getter方法
get onlyRead() { return this.x*this.y; }
}
和数据类型一样,存取器属性是可以继承的,因此可将p当做另一个‘点’的原型。可以给新对象定义它的 x 和 y 属性,但是 r 和 theta 属性是继承来的:
var q = inherit(p); // 创建一个继承getter和setter方法的对象
q.x = 1, q.y = 1; // 给q添加两个属性
console.log(q.r); // 可以使用继承的存取器属性
console,log(q.onlyRead)