概述
完整的javascript实现由三部分构成
ECMAScript 标准
DOM 文档对象模型
BOM 浏览器对象模型
JS 的特点
1.解释型语言
2.类似于C和Java的语法结构
3.动态语言
4.基于原型的 面向对象
三种输出方式
1.控制浏览器弹出一个警告框:alert(“ “);
2.在页面中输出内容:document.write() ——> 向body中输出内容
3.向控制台输出内容:console.log()
1 | <!DOCTYPE html> |
通过调试可以发现, JS代码和其他语言一样,是从上往下顺次执行的。
JS的编写位置
1 | <!DOCTYPE html> |
基本语法
注释
1 | <script type="text/javascript"> |
代码块
程序是由一条一条语句构成的,语句是按照自上向下的顺序一条一条执行的。
在JS中可以使用{}来为语句进行分组:
同一个{}中的语句是一组语句,它们要么都执行,要么都不执行,即一个代码块。
和其他语言不同的是,JS中的代码块仅仅具有分组的作用,代码块内部的内容在外部是完全可见的。
1 | { |
其它
1.JS中严格区分大小写,几乎和所有语言一样
2.JS中的每一条语句以分号结尾(类C)
– 如果不写分号,浏览器会自己添加,但是会影响一些系统资源
– 而且有的时候浏览器会加错分号,导致执行错误
3.JS中会自动忽略多个空格和换行,所以可以利用空格和换行对代码进行格式化操作(类C)
字面量和变量
1.字面量都是一些不可更改的值
比如:1,2,3,4,5等
字面量都可以直接使用,但是一般并不直接使用
2.多用变量,少用字面量
1 | // 使用字面量 |
标识符
在JS中可以由我们自主命名的都可以是标识符
例如:变量名、函数名、属性名等
命名规则(基本和其他语言一致):
1.可以含有数字,字母,_,$
2.标识符不能以数字开头
3.标识符不能是ES标准中的关键字或保留字
4.标识符一般采用驼峰命名法
– helloWorld
– 首字母小写,每个单词开头字母大写,其余字母小写
JS底层保存标识符时采用的是unicode编码,所以理论上所有utf-8中的内容都可以作为标识符(包括汉字)
数据类型
数据类型指的就是字面量的类型
JS中一共只有六种数据类型
– String 字符串
– Number 数值
– Boolean 布尔值
– Null 空值
– Undefined 未定义
– Object 对象
其中前五种属于基本数据类型,最后一种属于引用数据类型
String
用引号引起来(单双引号都行)
– 跟python一样,单双引号没有任何区别,同样的引号不能嵌套
– 在字符串中可以用\作为转义字符,对特殊符号进行转义
1 | var str = "hello"; |
Number
最大、最小与无穷
在JS中所有的数值都是Number类型,包括整数和浮点数。
JS可以表示的数字的最大值为:Number.MAX_VALUE (1.7976931348623157e+308)
如果Number表示的数字超过最大值,则返回Infinity,表示正无穷。对应的,-Infinity表示负无穷。
注:Infinity是一个字面量,类型为number
JS可以表示的最小正数为:Number.MIN_VALUE (5e-324)
1 | console.log(Number.MAX_VALUE); //1.7976931348623157e+308 |
typeof运算符
可以使用运算符 typeof 检查变量类型
语法:typeof 变量
检查字符串时,返回string
检查数值时,返回number
以此类推
1 | var b = 12345 + 54321; |
NaN
NaN 是一个特殊的数字(number类型),表示 Not a number ,同样也是一个字面量。
当进行类似于字符串乘字符这类非法运算时返回 NaN 。
浮点数计算
和其他语言类似,整数运算可以基本保证精确。浮点数无法做到完全精确。
所以不要用JS进行高精度运算计算。
Boolean
Boolean只有两种结果:True 和 False
1 | var bool = true; |
Null
Null只有一个值,即null,用于表示空的对象
它的类型是一个对象(object)
1 | var a = null; |
Undefined
Undefined 只有一个值,即undefined
当声明一个变量但不给它赋值时,其值即undefined
它的类型也是undefined
Object
String, Number, Boolean, Null, Undefined
以上五种属于基本数据类型,除了这五种类型,其它都是对象(Object)。
对象的特点
基本数据类型都是单一的值,值和值之间没有任何联系。
比如说:
1 | //用JS表示一个人的信息(name gender age): |
如果使用基本数据结构,我们创建的变量则都是独立的,不能成为一个整体。
对象属于一种复合的数据类型,在对象中可以保存多个不同数据类型的属性。
对象的分类
1.内建对象
– 由ES标准中定义的对象,在任何的ES的实现中都可以使
– 如:Math String Number …
2.宿主对象
– 由JS的运行环境提供的对象,目前来说主要指由浏览器提供的对象
– 比如BOM DOM
3.自建对象
– 由开发人员自己创建的对象
创建对象
1 | /* |
属性名
向对象中添加属性
属性名:
– 对象的属性名不强制要求遵守标识符的规范,什么名字都可以使用
– 但我们尽可能按照标识符的规范命名
1 | obj.name = "李华"; |
如果要使用特殊的属性名,不能采用.的方式来操作,而需要使用另一种方式:
语法:对象[“属性名”] = 属性值
读取时需要采用这种方式。
使用[]这种形式去操作属性,更加地灵活,在[]中可以直接传递一个变量,读取变量值对应的属性。
1 | obj["123"] = 789; |
属性值
JS对象的属性值,可以是任意的数据类型,也可以是一个对象。
1 | var obj1 = new Object(); |
in 运算符
通过该运算符可以检查一个对象中是否含有指定的属性
如果有则返回true,没有则返回false
语法:”属性名” in 对象
console.log("test2" in obj); //false
增删查改
1 | //向对象中添加属性 |
对象字面量
可以通过对象字面量来创建一个对象:
1 | var obj = {}; |
使用对象字面量,可以在创建对象时,直接指定对象中的属性
语法:{属性名:属性值,属性名:属性值……}
对象字面量的属性名可以加引号也可以不加,建议不加,如果要使用一些特殊的名字,则必须加引号。
属性名和属性值是一组一组的名值对结构,
名和值之间使用 : 连接,多个名值对之间使用 , 隔开
如果一个属性之后没有其它属性,就不必再写 ,
对象的嵌套也同样可以使用,如:
1 | var obj3 = {name:"wakuwaku"} |
枚举对象中的属性
使用for…in 语句:
语法:
for(var 变量 in 对象){
}
for…in语句 对象中有几个属性,循环就会执行几次。
每次执行时,会将对象中一个属性的名字赋值给变量。
1 | var obj = { |
基本数据类型和引用数据类型的区别
基本数据类型
String Number Boolean Null Undefined
引用数据类型
Object
JS 中的变量保存在栈内存中,基本数据类型的值直接在栈内存中存储,值与值之间是独立存在的,修改一个变量不会影响其它变量。
1 | var a = 123; |
对象保存在堆内存中,每创建一个新的对象,就会在堆内存中开辟出一个新的空间。
而变量保存的是对象的内存地址(对象的引用),如果两个变量保存的是同一个对象引用,当通过一个变量修改属性时,另一个也会受到影响。
1 | var obj = new Object(); |
当比较两个基本数据类型时,就是比较值。
而比较两个引用数据类型时,它比较的是对象的内存地址。即便两个对象一模一样,只要地址不同则不同。
1 | var obj3 = new Object(); |
强制类型转换
强制类型转换
– 将一个数据类型强制转换为其它数据类型
– 类型转换主要指将其他数据转换为
String Number Boolean
转换为String
方式一:
– 调用被转换数据类型的toString()方法
– 该方法不会影响到原变量,它会将转换的结果返回
– 注意:null 和 undefined 两个值无法使用 toString() 方法
1 | var a = true; |
方式二:
– 调用String()函数,并将被转换的数据作为参数传递给函数。
– 使用String()函数做强制类型转换时,从底层上来讲
对于Number和Boolean实际上就是调用的 toString() 方法
对 null 和 undefined 会执行直接转换 –> “null” 和 “undefined”
1 | // 方法二:使用String() |
转换为Number
方法一:
使用Number()函数将数据转换为Number类型
– 字符串 –> 数值
1.如果是纯数字的字符串,则将其直接转换成数字
2.如果字符串中有非数字内容,则转换为 NaN
3.如果字符串是一个空串(或全是空格),则转换为0
– 布尔值 –> true 转化为数字 1,false 转换为数字 0
– Null –> 数字 0
– undefined –> NaN
1 | var a = "123"; |
方法二:
– 此方法专门针对字符串使用
– parseInt() 把一个字符串转换为一个整数
– parseFloat() 把一个字符串转换为一个浮点数
1 | /* |
转换为Boolean
将其它的数据类型转换为Boolean
– 使用Boolean()函数
– 数字 –> 除了0 和 NaN,其余都是true
– 字符串 –> 除了空串,其余的都是true
– null 和 undefined–> 都是false
– 对象 –> true
1 | //测试数字 |
其它进制数表示和编码方式
进制表示
0x 开头表示16进制,表示数字的字母不区分大小写
0 开头的是8进制
0b 开头表示2进制(不是所有的浏览器都支持)
1 | a = 0xAB; |
unicode编码
1 | <!DOCTYPE html> |
运算符
运算符也叫操作符
运算符可以对一个或多个值进行运算,并获取运算结果。
比如:typeof就是运算符,可以来获得一个值的类型。它会将该值的类型以字符串的形式返回。
算数运算符
当对非Number类型的值进行运算时,会先将这些值转换为Number然后再运算。
任何值和NaN做运算,最后的结果都是NaN。
+
1.对两个值进行加法运算,并将结果返回
2.可以对两个字符串进行连接
任何的值和字符串做加法运算,都会先转换字符串,然后再进行拼接。
– 可以利用这一特点,来将一个任意的数据类型转换为String。即隐式类型转换,由浏览器自动完成,从底层来看依旧是调用了String()。
1 | //隐式string类型转换 |
3.也可以作为格式化输出的一种方式
1 | //格式化输出 |
-
1.对两个值进行减法运算,并将结果返回
2.没有隐式转换作用
*
对两个值进行乘法运算
/
除法运算
任何做 - * / 运算时都会自动转换为Number,属于第二种隐式类型转换
**可以通过对一个值 -0 *1 /1 来将其转换为Number。**原理和Number()一样,但是使用更简便。
%
取模运算
一元运算符
正负号
一元运算符只需要一个操作数
+
正号
– 正号对数字不会产生任何影响
-
负号
– 负号对数字进行符号取反
对于非Number类型的值,会先转换为number再进行运算
1 | var a = true; |
可以对一个其他数据类型使用 + ,来将其转换为Number
1 | //第三种隐式类型转换 |
自增和自减
自增
– 使变量在自身的基础上增加1
– 对一个变量自增后,原变量的值会立即自增1
– 自增分为两种:后++(a++) 和 前++(++a),都会立即使原变量的值自增1
但是二者本身的值不同。后++的值等于自增以前的值(原),前++的值等于自增之后的值(新)
自减
– 使变量在自身的基础上减1
– 一切属性类似自增
1 | var a = 1; |
逻辑运算符
JS提供三种逻辑运算符
! 非
– 用来对一个值进行非运算
– 对一个布尔值进行取反操作
– 两次取反结果不变
– 对非布尔值进行非运算,会先将数据转换为布尔类型再进行取反运算
所以可以利用该特点,来将一个其它数据类型转换为布尔值
隐式类型转换:
可以为一个任意数据类型取反两次,将其转换为布尔值
原理同Boolean()
1 | var a = true; |
&& 与
– 对符号两侧的值进行与运算并返回结果
– 运算规则:
– 两个值中只要有一个值为false就返回false
两个值均为true才返回true
– JS中的 与 是短路的 与,如果第一个值为false,就不再判断第二个值
1 | var result = true && true; |
|| 或
– 对符号两侧的值进行与运算并返回结果
– 运算规则:
– 两个值中只要有一个值为true即为true
两个值均为false才返回false
– JS中的 或 是短路的 或,同 与
&& || 对非布尔值情况:
– 对于非布尔值进行与或运算时,会先将其转换为布尔值再运算
– 与运算:
– 如果第一个值为true,直接返回第二个原值
– 如果第一个值为false,直接返回第一个原值
1 | //true && true |
– 或运算:
– 如果第一个值为true,则直接返回第一个原值
– 如果第一个值为false,则直接返回第二个原值
1 | //true || true(false) |
相等运算符
1.== 相等运算符用来比较两个值是否相等,相等返回true,不等返回false
使用==来做相等运算
– 当使用==比较两个值时,如果值的类型不同,会进行自动类型转换,
将其转换为相同类型后再作比较
2.!= 不相等用来判断两个值是否不相等,不相等返回true,相等返回false
使用!=来做不相等运算
– 性质与==类似
1 | console.log("1" == 1); //true |
3.=== 全等,用来判断两个值是否全等,和相等类似,不同点在于它不会进行自动类型转换
4.!== 不全等,用来判断两个值是否不全等,特殊性同上
1 | var a = NaN; |
关系运算符
通过关系运算符可以比较两个值之间的大小关系,关系成立则返回true,否则返回false。
>
>=
<
<=
对于非数值情况
– 对于非数值进行比较时,会先转换为Number再比较。
– 如果符号两侧的值都是字符串时,不会将他们转换成数字再比较,而是直接比较它们的unicode编码。
一位一位进行比较,两位一样则比较下一位,可以借用此特性对英文进行排序。对中文而言不是很有 意义。
1 | console.log(1 > true);//false |
条件运算符
条件运算符也叫三元运算符
语法:条件表达式 ? 语句1 : 语句2 ;
– 执行的流程:
条件运算符在执行时,首先对条件表达式进行求值,
如果值为true,则执行语句1,并返回执行结果
如果值为false,则执行语句2,并返回执行结果
如果条件表达式的结果是一个非布尔值,则会将它转换为布尔值再做判断
1 | true ? alert("语句1") : alert("语句2"); //语句1 |
其它
1 | /* |
JS运算符的优先级:https://github.com/xhlwill/blog/issues/16
不用背,查表即可,越靠上优先级越高,先进行计算。如果优先级一样,则从左向右运算。如果实在不清楚,就多加加括号。
流程控制语句
几乎和C语言相同,不多作记录。
JS中的程序是从上到下一行一行控制执行的,通过流程控制语句可以控制程序执行流程,使程序可以根据一定条件来选择执行。
条件判断语句
1 | //if语句 |
条件分支语句
1 | switch(条件表达式){ |
循环语句
1 | //while循环 |
break和continue
break 关键字 可以用来退出switch或循环语句。
不能在if语句中使用break和continue。
break会立即终止离他最近的那个循环语句。
特别的是:可以为循环语句创建一个label,来标识当前的循环
label:循环语句
使用break语句时,可以在break后跟着一个label
这样break将会结束指定的循环,而不是最近的
1 | outer: |
continue 关键字 可以用来跳过当此循环
它同样默认只对离它最近的循环起作用。
函数
函数也是一个对象。
函数中可以给封装一些功能(代码),在需要时可以执行这些功能(代码)。也可以保存一些代码,在需要的时候调用。
使用typeof检查一个函数对象时,会返回function。
1 | //这种方式在开发过程中很少使用 |
函数的创建
使用函数声明来创建一个函数
常规方法。
1 | /* |
使用函数表达式来创建一个函数
1 | /* |
函数的参数
可以在函数的 () 中指定一个或多个形参(形式参数)
– 多个形参之间用 , 隔开,声明形参就相当于在函数内部声明了对应的变量,但是并不赋值
在调用函数时,可以在()中指定实参(实际参数)
– 实参将会赋值给函数中对应的形参
需要注意的是:
1.调用函数时解析器不会检查实参的类型,要注意是否有可能接收到非法的参数,如果有可能则需要对参数进行类型的检查。
2.调用函数时,解析器也不会检查实参的数量,多余的实参不会产生影响。如果实参的数量少于形参的数量,则没有对应实参的形参将是undefined。
函数的返回值
可以用 return 来设置函数的返回值。
return 后的值将会作为函数的执行结果返回,可以通过定义一个变量来接受结果。
在函数中,return 后的语句都不会执行。
如果 return 语句之后不跟任何值,就相当于返回undefined,不写return也是如此。
return 后可以跟任意类型的值。
返回值可以是任意的数据类型,包括对象和函数。
1 | function fun1(){ |
立即执行函数
立即执行函数在定义完后立即被调用,往往只会执行一次。
1 | //无参数 |
对象与方法
1 | var obj = new Object(); |
函数也可以成为对象的属性,如果一个函数作为一个对象的属性保存,那么我们称这个函数为这个对象的方法。
调用这个函数即调用对象的方法(method),和调用单独定义的函数没有本质区别。
this
解析器在调用函数时,每次都会向函数内部传递一个隐含的参数,这个隐含的参数即this,this指向的是一个对象,即函数执行的上下文对象。
根据函数调用方式的不同,this会指向不同的对象:
1.以函数的形式调用时,this指向window
2.以方法的形式调用时,this指向调用方法的对象
3.当以构造函数的形式调用时,this就是新创建的那个对象
4.使用call和apply调用时,this是指定的那个对象
1 | function fun(a,b){ |

1 | //案例 |
call和apply
call()和apply()
– 是函数对象的方法,需要通过函数对象来调用
– 当对函数调用 call() 和 apply() 都会调用函数执行
– call() 和 apply() 可以将一个对象指定为第一个参数,此时这个对象将会成为函数执行时的this
1 | function fun1(){ |
– call() 方法可以将实参在对象之后依次传递。
– apply() 方法需要将实参封装到数组中统一传递。
1 | function fun2(a,b){ |
arguments
在调用函数时,浏览器每次都会传递进两个隐含的参数:
1.函数的上下文对象this
2.封装实参的对象 arguments
– arguments是一个类数组对象,它可以通过索引来操作数据,也可以获取长度
– 在调用函数时,我们所传递的实参都会在arguments中保存
– arguments.length可以用来获取实参的长度
– 我们即使不定义形参,也可以通过arguments来使用实参
– 它里面有一个callee属性,对应当前正在执行的函数的对象
1 | function fun(){ |
作用域
作用域指一个变量作用的范围
在JS仅有两种作用域:
1.全局作用域
– 直接编写在script标签中的代码,都在全局作用域中
– 全局作用域在页面打开时创建,在页面关闭时销毁
– 全局作用域有一个全局对象window(由浏览器创建),我们可以直接使用
– 在全局作用域中:
创建的变量都会作为window对象的属性保存
创建的函数都会作为window对象的方法保存
– 全局作用域中的变量都是全局变量,在页面的任意部分都可以访问得到
2.函数作用域
– 调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁
– 每调用一次函数就会创建一个新的函数作用域,他们之间是互相独立的
– 函数作用域中可以访问到全局作用域的变量,但在全局作用域中无法访问到函数作用域中的变量
– 在函数作用域中操作一个变量时,它会先在自身作用域中寻找,如果有就直接用
如果没有就在上一级作用域中寻找,直到到达全局作用域,如果依旧找不到,会直接报错ReferenceError
声明提前
变量的声明提前
– 使用var关键字声明的变量,会在所有的代码执行之前被声明(但是不会被赋值)
但是如果声明变量时不使用var关键字,则变量不会被声明提前
1 | console.log(a); |
函数的声明提前
– 使用函数声明形式创建的函数 function 函数(){}
它会在所有代码执行之前就被创建,所以我们可以在函数声明前调用函数
– 使用函数表达式创建的函数,不会被声明提前,所以不能在声明前调用
1 | /* |
使用工厂方法创建对象
通过该方法可以大批量创建对象。
1 | function creatPerson(name, age){ |
使用工厂方法创建的对象,使用的构造函数都是Object,所以创建的对象都是Object这个类型。
导致我们无法区分出多种不同类型的对象。
构造函数
创建一个构造函数,专门用来创建person对象
构造函数就是一个普通的函数,创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写(还有加不加new的区别)
构造函数和普通函数的区别就是调用方式的不同
普通函数是直接调用,而构造函数需要使用new关键词来调用
1 | function Person(name, age){ |
构造函数的执行流程:
1.立刻创建一个新的对象
2.将新建的对象设置为函数中的this,在构造函数中可以使用this来引用新建的对象
3.逐行执行函数中的代码
4.将新建的对象作为返回值返回
使用一个构造函数创建的对象,我们称为一类对象,也将一个构造函数成为一个类,我们将通过一个构造函数创建的对象,成为该类的实例
使用 instanceof 可以检查一个对象是否是一个类的实例。
语法:
对象 instanceof 构造函数
返回值:是则返回true,否则返回false
1 | console.log(per instanceof Person); //true |
局限性
创建一个Person构造函数:
1 | function Person(name, age, gender){ |
在Person构造函数中,为每一个对象都添加了一个sayName方法
目前我们的方法是在构造函数内部创建的,即构造函数每执行一次都会创建一个新的Sayname方法
即所有实例的sayName都是唯一的。这样就导致了构造函数每执行一次就会创建一个新的方法。
1 | //创建一个Person实例 |
但是这是完全没必要的,我们可以使所有对象共享同一个方法。
一个方法是:将sayName方法在全局作用域中定义。
1 | function Person(name, age, gender){ |
将函数定义在全局作用域中,污染了全局作用域的命名空间。定义在全局作用域中也很不安全,因此有很大的局限性。
原型对象
我们所创建的每一个函数,解析器都会向函数中添加一个属性 prototype ,这个属性对应着一个对象,这个对象就是原型对象。
如果函数作为普通函数调用,prototype没有任何作用。
当函数以构造函数的形式调用时,它所创建的对象中,都会有一个隐含的属性,指向该构造函数的原型对象,我们可以通过 __proto__
来访问该属性。
原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中所有的内容,统一设置到原型对象中。
当我们访问对象的一个属性或方法时,会优先在对象自身中寻找,如果有则直接用,如果没有则会去原型对象中寻找,如果找到则直接使用。
1 | function Person(){ |
创建构造函数时,可以将对象共有的属性统一添加到构造函数的原型对象中,这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法。
1 | console.log(Person.prototype == MyClass.prototype); //false |
in,hasOwnProperty方法和原型链
1 | /* |
原型对象也是对象,所以它也有原型,当我们使用一个对象的属性或方法时,会现在自身中寻找,自身如果有则直接使用。
如果没有就去原型里找,还是找不到就去原型的原型里找,以此类推,直到找到Object对象的原型,Object对象的原型没有原型,如果在Object中依然找不到,则返回unfined。
1 | console.log(mc.__proto__.hasOwnProperty("hasOwnProperty")); //false |
toString
当我们直接在页面中打印一个对象时,实际上是输出对象的 toString 方法的返回值。
如果我们希望在输出对象时不输出[object Object],可以为对象添加一个 toString 方法。
例如 Person[name= ...,age=...,gender=...]
1 | function Person(name, age, gender){ |
测试 toString 的位置:
1 | console.log(per.hasOwnProperty("toString")); //flase |
垃圾回收(GC)
当一个对象没有任何变量或属性对它进行引用,此时我们将无法操作该对象。(一种内存泄漏)
这种对象过多会占用大量的内存空间,导致程序运行变慢。
JS拥有自动的垃圾回收机制,会自动将垃圾对象从内存中销毁。因此我们不需要也不能进行垃圾回收的操作。
我们只需要将不再使用的对象设置为null即可。
1 | var obj = new Object(); |
数组(Array)
– 数组是一个内建对象
– 它和普通的对象功能类似,用于存储一些值
– 不同的是,普通对象使用字符串作为属性名,而数组使用数字作为索引来操作元素。
– 索引从0开始
– 存储性能比普通对象好
基本操作
1 | //创建数组对象 |
数组字面量
1 | //使用字面量创建数组 |
遍历数组
使用for循环
1 | var arr = [1,2,3,4,5,6,7,8]; |
使用forEach方法
该方法只支持IE8以上浏览器。
forEach方法需要一个函数作为参数,这种函数由我们创建但不由我们调用,称为回调函数。
数组中有几个元素,函数就执行几次,每次执行时,浏览器会将遍历到的元素以实参的形式传递。我们可以定义形参来读取这些内容。
浏览器会在回调函数中传递三个参数:
第一个参数,是当前正在遍历的元素
第二个参数,是当前正在遍历元素的索引
第三个参数,是正在遍历的数组
1 | arr.forEach(function(){ |
输出:
数组常用方法
1 | //创建一个数组 |