JavaScript学习笔记1

JavaScript 在1995年由网景公司发明,1996年生成 ECMAScript 标准,不同浏览器对该标准的实现不同,如 FireFox 使用 SpiderMonkey 引擎,Chrome 使用 v8 引擎(Nodejs也是)

一、JavaScript组成

1、JS的组成

JavaScript 包含三部分:ECMAScript(标准)+ DOM(文档对象模型)+BOM(浏览器对象模型)

2、JS的特点

(1)解释型语言,无需编译

(2)类似 C 和 Java 的语法结构

(3)动态语言

(4)基于原型的面向对象

注意:JS 中严格区分大小写

3、在网页中使用 JS

(1)可将 JS 代码编写到标签的 onclick 属性中,如 <button onclick="alert("xxxx");">按钮</button>

(2)可将 JS 代码写在超链接的 href 属性中,点击超链接时会执行 JS 代码,如 <a href="javascript:alert("xxxx");">超链接</a>

(3)通过 <script> 标签中写 JS 代码, 如<script type="text/javascript">alert("xxxx");</script>

(4)通过 <script> 标签引入外部 JS 文件,<script type="text/javascript" src="./xxx.js"></script>

注意1:第一、二种方法把 JS 写在标签的书中属于结构与行为耦合,不方便维护,而把 JS 写到外部文件中可在不页面同时引用,也可利用到浏览器的缓存机制

注意2:一个 <script> 标签一旦用于引入外部文件,就不能在其中编写 JS 代码了,即使写了浏览器也会忽略,但若再创建一个 <script> 标签其中的内部 JS 代码可执行

二、语法

1、输出与输入

alert(“xxxxxx”); //控制浏览器弹出警告框

document.write(“xxxx”); //向 body 中输出内容,当要换行时不能用 document.write("xxxx"+"\n"); 而要用 document.write("xxxx"+"<br />");

console.log(“xxx”); //向控制台输出内容

prompt(“提示文字”); //用户可输入字符串,用户输入内容会以 String 类型作为函数返回值返回

通过类型转换 +prompt(“提示文字”); 会把字符串转为 Number,可用于输入数字

2、字面量和变量

(1)字面量

字面量是一些不可改变的值,字面量都是可直接使用的(但一般不会直接使用,而是通过变量)

数据类型

字面量类型(数据类型)六种:
    String     字符串
    Number     数值
    Boolean    布尔值
    Null       空值,专门用来表示一个为空的对象,typeof 检查一个 null 值会返回 object
    Undefined  未定义,typeof 检查一个 null 值会返回 undefined
    Object     对象
    Symbol     ES6 中引入的新类型
其中 String、Number、Boolean、Null、Undefined 是基本数据类型,Object 属于引用数据类型

可利用 typeof 变量名来查看变量的数据类型,它会将变量的类型以字符串形式返回

特殊值

Number.MAX_VALUE  数字的最大值,若数字超过最大值,会返回
Number.MIN_VALUE  最小的正值,大于 0 的最小值
Infinity   表示正无穷
Infinity   正无穷,也属于数字类型
-Infinity  负无穷
NaN        表示 Not A Number,也属于数字类型

在 JS 中整数运算基本可保证精确,但浮点数运算可能得到不精确结果,因此不要用 JS 进行对精确度要求较高的运算

强制类型转换

1. 转换为字符串 String

方式一:调用 变量.toString(),该方法不会影响原变量,会将转换的结果返回,且 null 和 undefined 没有 toString() 方法,若调用会报错

方式二:调用 String(变量) 函数,该方法不会影响原变量,会将转换的结果返回,且该方式对 null 和 undefined 有效

方式三:隐式类型转换,将任意数据类型 + 一个 "",隐式类型转换由浏览器自动完成,实际上它也是调用 String() 函数

2. 转换为数字 Number

方式一:使用 Number(变量) 函数,该方法不会影响原变量,会将转换的结果返回

— 若字符串中有非数字内容则转为 NaN,若字符串为空串或是全为空格的字符串会转为 0

— 对于布尔值,true 转为 1,false 转为 0

— null 会转为 0

— undefined 会转为 NaN

方式二:针对字符串

parseInt(变量,进制)可将字符串中的有效整数内容取出并转为 Number,第二个参数可不写,如 “123px” 会转为 123,“123a567” 会转为 123

parseFloat(变量)可将字符串中的有效小数内容取出并转为 Number,如 “123.456.789px” 会转为 123.456

若对非 String 使用 parseInt()parseFloat()会先将其转为 String 然后再操作

方式三:隐式类型转换,可通过一个值 -0*1/1 来将其转为 Number,隐式类型转换由浏览器自动完成,实际上它也是调用 Number() 函数

方式四:隐式类型转换,将任意数据类型使用 + 来将其转换为 Number,隐式类型转换由浏览器自动完成,实际上它也是调用 Number() 函数

3. 转换为布尔 Boolean

方式一:调用 Boolean(变量) 函数,该方法不会影响原变量,会将转换的结果返回

方式二:隐式类型转换,可通过为任意数据类型取两次反来将其转为 Boolean,隐式类型转换由浏览器自动完成,实际上它也是调用 Boolean() 函数

对于数字 —> 布尔,除了 0 和 NaN,其余都是 true

对于字符串 —> 布尔,除了空串,其余都是 true

null 和 undefined 都会转为 false

对象也会转换为 true

其他进制数字

JS 中十六进制数字需以 0x 开头,八进制数字需以 0 开头,二进制需以 0b 开头(二进制表示方法浏览器兼容性不好)

注意 a=070 表示八进制,而字符串 a="070" 转为数字时有些浏览器会当八进制,有些会当十进制转换,可在 parseInt(变量,进制) 中传递进制参数来解决

(2)变量

变量可用来保存和描述字面量,且变量值可任意改变

3、标识符

所有可自主命名的都是标识符,如变量名、函数名、属性名

规则:(1)可含有字母、数字、_、$

(2)不能以数字开头

(3)不能是 JS 中的关键字或保留字,如下图

关键字和保留字

(4)一般都采用驼峰命名法:首字母小写,每个单词开头字母大写,其余字母小写

除了(4),其他都是强制要求

注意:JS 底层保存标识符时是采用 Unicode 编码,所以理论上所有 utf-8 中含有的内容都可以作为标识符

4、运算符

(1)二元运算符 + - * /

当对非 Number 类型的值进行运算时,会先将这些值转换为 Number 再运算(除了 Number 和字符串做加法以外),Number 和 字符串做减法/乘法等都是转换为 Number

任何值和 NaN 做运算结果都为 NaN

若对两个字符串作加法,则会把两个字符串拼接为一个字符串

任何值和字符串做加法,都会先转换为字符串,然后再和字符串作拼串操作(可用于 String 强制类型转换)

注意
result = 1 + 2 + “3”; //结果是“33”
result = “1” + 2 + 3; //结果是“123”

任何值做 - * / 都会自动转换为 Number(可用于 Number 强制类型转换)

(2)一元运算符

+ 正号不会对数字产生影响,- 负号可以对数字取反

对于非 Number 类型的值,会将其先转换为 Number 再运算(可用于 Number 强制类型转换)

result = 1 + "2" + 3;   //结果是“123”
result = 1 + +"2" + 3;   //结果是6

自增/减分为两种:后++a++)或后--a--)和前++++a)或前----a),两种值不同

a++a--的值等于原变量的值(自增/减前的值)

++a--a的值等于原变量新值(自增/减后的值)

var a = 20;
console.log(a++ + ++a + a);  //结果为 20 + 22 + 22
var b = 20;
b = b++;   //结果为 20,因为 b++ 的值为 20

(3)条件(三元)运算符

语法 条件表达式 ? 语句1 : 语句2;,若条件表达式是非布尔值,则会将其转换为布尔值再比较

(4)逻辑运算符

! 对非布尔值进行运算都会将其转换为布尔值,然后再取反(可用于 Boolean 强制类型转换)

JS 中的 “与&&” 属于短路的 “与”,(即若第一个值为 false 则不会再检查第二个)

JS 中的 “或||” 属于短路的 “或”,(即若第一个值为 true 则不会再检查第二个)

对于非布尔值进行与或运算都会将其转换为布尔值,并返回转换前的值

与运算:若两个值都为 true,则返回后面的值,若两个值中有 false,则返回靠前的 false(即若第一个值为 true 则必然返回第二个值,若第一个值为 false 则直接返回第一个值)

或运算:若两个值都为 false,则返回后面的值,若两个值中有 true,则返回靠前的 true(即若第一个值为 false 则必然返回第二个值,若第一个值为 true 则直接返回第一个值)

(5)关系运算符

对于非数值进行 > >= < <= 比较时,会将其转换为数字然后再比较

若比较符号两侧都是字符串时不会将其转换为数字,而会分别比较字符串中字符的 Unicode 编码,比较字符编码时是一位一位比较,若两位一样则比较下一位,否则直接返回比较结果(可利用该特性对英文进行排序,比较中文没有意义)

在比较两个字符串型的数字时,一定要转型,转型后比较的就是数字大小,否则会一位一位比较字符串编码

任何值和 NaN 做任何比较都是 false

1 > true     //false
1 >= true     //true
10 > null     //true
10 > "hello"  //false 相当于比较 10 > NaN

(6)相等运算符

当使用 ==!= 比较时若值的类型不同,则会自动进行类型转换,将其转换为相同类型后再比较

undefined 衍生自 null,所以这两个值做相等判断时会返回 true

NaN 不和任何值相等,包括它本身,通过 isNaN() 函数判断一个值是否是 NaN

"1" == 1      //true,转为 Number 比较
true == "1"   //true,二者都转为 Number
null == 0     //false 注意
undefined == null  //true
Nan == Nan    //false

=== 全等,和 == 类似,但它们不会做自动类型转换,若两个值类型不同,直接返回 false;!== 不全等,和 != 类似,但它们不会做自动类型转换,若两个值类型不同,直接返回 true

undefined === null  //false

运算符

使用 , 可同时声明多个变量并赋值,如var a=1,b=2,c=3

5、运算符优先级

运算符优先如下图,越靠上优先级越高

运算符优先级

6、代码块

JS 中可使用 {} 为语句分组,同一个 {} 中的语句称为一个代码块,要么都执行,要么都不执行

JS 中的代码块只有分组的作用,没有其他用途,代码块内部的内容在外部是完全可见的

代码块后不用写 ;

7、流程控制语句

语句分类:条件判断语句、条件分支语句、循环语句

switch…case 语句中的条件判断是采用全等

for 循环中 () 内三个部分都可以省略,也可以写在外部,若在 for 循环中不写任何表达式,只写两个 ; 是个死循环,慎用

可为循环语句创建一个 label,来标识当前循环,用法为 标签名:循环语句,使用 break 或 continue 语句时,可在 break 或 continue 后跟着一个 label,此时 break 将会结束指定的循环,或 continue 将会跳过当前的指定循环,而不是最近的循环

outer:
for(var i=0;i<5;i++){
    语句...
    for(var j=0;j<5;j++){
        break outer;  //此时 break 结束的是外层循环
    }
}

8、对象

基本数据类型创建的变量都是独立的,不能成为一个整体

对象不是基本数据类型,而是引用数据类型,是复合数据类型

除了基本数据类型外一切都是对象,网页中看到的所有都是对象

(1)对象的分类

内建对象

由 ES 标准中定义的对象,在任何的 ES 的实现中都可以使用,如 Math、String、Number、Boolean、Function、Object 等

宿主对象

由 JS 的运行环境提供的对象,主要指由浏览器提供的对象,如 BOM、DOM,这是两组对象,由许多对象组成,如 console、document 都是由浏览器创建提供的,是宿主对象

自定义对象

由开发人员自己创建的对象

(2)创建对象

使用 new 关键字调用的函数,是构造函数constructor,构造函数是专门用来创建对象的函数

使用 typeof 检查一个对象时,会返回 object

使用工厂方法创建对象

function createPerson(name,age,gender){
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.gender = gender;
    obj.sayName = function(){
        alert(this.name);
    }
    return obj;
}
var obj1 = createPerson("xxx",18,"男"); 

创建构造函数来创建对象

使用同一个构造函数创建的对象称为一类对象,也将一个构造函数称为一个类,将通过构造函数创建的对象称为该类的实例

使用 instanceof 可检查一个对象是否是一个类的实例,对象 instanceof 构造函数 返回布尔值,所有对象都是 Object 的后代,所以任何对象和 Object 做 instanceof 检查时都会返回 true

构造函数就是一个普通函数,不同的是构造函数习惯上首字母大写。

构造函数和普通函数的区别:普通函数直接调用,而构造函数需要使用 new 关键字调用

构造函数执行流程:

  1. 立刻创建一个新的对象

  2. 将新建的对象设置为函数中的 this,在构造函数中可以使用 this 来引用新建的对象

  3. 逐行执行函数中的代码

  4. 将新建的对象作为返回值返回

    function Person(name,age,gender){

     this.name = name;
     this.age = age;
     this.gender = gender;
     this.sayName = function(){
         alert(this.name);
     }

    }
    var per = new Person(“xxx”,18,”男”);

构造函数的方法定义

在构造函数内部创建的方法,每执行一次构造函数就会创建一个新的相同方法,所有实例的该方法都是唯一的,这没有必要,所以可使所有对象共享同一个方法

方式一:将方法定义到全局作用域中

但是将函数定义到全局作用域中会污染了全局作用域的命名空间,并且也很不安全,容易被另一个程序猿覆盖

方式二:使用原型对象

对象操作

var 对象名 = new Object();var 对象名 = {"属性名1":属性值,属性名n:属性值}; 创建对象,第二种方式的对象也叫对象字面量,对象字面量的属性名可加引号也可不加,但若使用一些特殊名字时必须加引号

对象.属性名 = 属性值; 向对象添加属性,对象的属性名不强制要求遵守标识符的规范,但尽量按照标识符的规范

对象["属性名"] = 属性值; 向对象添加属性,对于使用特殊的属性名时,不能采用 . 的方式操作

对象.属性名; 读取对象中的属性,若读取对象中没有的属性不会报错,而会返回 undefined

对象["属性名"] 读取对象中的属性,这种方式更灵活,因为 [] 中可以传变量,而 . 的方式操作属性会需要准确的属性名。此外,对于使用 对象["属性名"] = 属性值; 赋值的属性只能用这种方式读取属性

对象.属性名 = 新值; 修改对象的属性值

delete 对象.属性名; 删除对象的属性

"属性名" in 对象 检查对象或其原型中是否含有某属性

对象实例.hasOwnProperty("属性名") 检查对象自身是否含有某属性

枚举对象的属性

for(var 变量 in 对象){xxx}
对象中有几个属性循环体就会执行几次,其中每次执行时就把对象中的一个属性名赋值给变量

JS 对象的属性值可以是任意的数据类型,甚至也可以是一个对象(如函数)

若函数作为一个对象的属性,则称该函数是这个对象的方法,调用该函数称为调用对象的方法

9、基本数据类型和引用数据类型

JS 中的变量都是保存到栈内存中,基本数据类型直接在栈内存中存储,值与值之间是独立存在,修改一个变量不会影响其他的变量

对象是保存在堆内存中的,每创建一个新的对象,就会在堆内存中开辟出一个新空间,而变量保存的是对象的内存地址(对象的引用),若两个变量保存的是同一个对象引用,当通过一个变量修改该对象属性时,另一个也会受到影响

基本数据类型和引用数据类型

但是当两个变量表示同一个对象时,把其中一个变量值设为 null 后另一个变量依然指向对象不会受到影响

基本数据类型和引用数据类型1

当比较两个基本数据类型时就是比较它们的值,而比较两个引用数据类型时比较的是内存地址,即使两个对象内容一样,但地址不同依然会返回 false

基本数据类型和引用数据类型2

10、函数

函数也是一个对象,是一个具有功能的对象,使用 typeof 检查一个函数对象时会返回 function

var 变量名 = new Function(); 使用构造函数创建函数对象,可将要封装的代码以字符串的形式传递给构造函数,如 var fun = new Function("console.log('hello world')");,但开发中很少使用构造函数来创建一个函数对象

function 函数名([形参1,形参2,...,形参n]){} 使用函数声明创建一个函数

var 函数名 = function([形参1,形参2,...,形参n]){} 使用函数表达式创建函数,相当于声明一个匿名函数并赋值给变量

函数对象(); 调用函数

(function([形参1,形参n]){xxx})([形参1,形参n]) 匿名函数定义完立即被调用,这种函数称为立即执行函数,立即执行函数往往只会执行一次

函数声明中的形参相当于在函数内部声明了相应变量

调用函数时解析器不会检查实参的类型,所以要注意是否有可能会接收到非法参数,若有可能则需要对参数进行类型检查

调用函数时解析器不会检查实参的数量,多余的参数不会被赋值,若实参的数量少于形参的数量,则没有对应实参的形参将是 undefined

函数中的参数可以是另一个函数,当实参中的函数是 函数名() 表示调用函数,使用的是函数的返回值,当实参中的函数是 函数名 表示函数对象,相当于直接使用函数对象

若函数的 return 可以返回任意类型的值,若 return 语句后不跟任何值或不写 return 语句,相当于返回 undefined

三、作用域

JS 中有两种作用域:全局作用域、函数作用域

1、全局作用域

直接编写在 <script> 标签中的 JS 代码都在全局作用域,全局作用域中的变量都是全局变量,在页面任意部分都能访问到

全局作用域在页面打开时创建,在页面关闭时销毁

全局作用域中有一个全局对象 window,代表一个浏览器的窗口,由浏览器创建可直接使用

在全局作用域中创建的变量都会作为 window 对象的属性保存,创建的函数都会作为 window 对象的方法

变量声明提前:使用 var 关键字声明的变量,会在所有代码执行之前被声明(但不一定会赋值),但若声明变量时不使用 var 关键字,则变量不会被声明提前

console.log(a);
var a = 123;  //该行对变量 a 的声明实际上会在最开头,只是在这一行被赋值

函数声明提前:使用函数声明形式 function 函数(){} 创建的函数会在所有代码执行之前被创建,所以可以在函数声明前调用。但是使用函数表达式 var 变量 = function(){} 创建的函数不会被声明提前,所以不能在声明前调用

2、函数作用域

调用函数时创建函数作用域,函数执行完毕后,函数作用域销毁。每调用一次函数就会创建一个新的函数作用域,它们之间相互独立

当在函数作用域中操作一个变量时会先在自身作用域中寻找,若有就直接使用,若没有则向上一级作用域中寻找,直到找到全局作用域,若全局作用域中依然没有找到,则会报错 ReferenceError

在函数中要访问全局变量可使用 window 对象,window.变量名

在函数作用域中也有声明提前的特性,使用 var 关键字声明的变量会在函数中所有代码执行之前被声明。函数声明也会在函数中所有代码执行之前执行。定义形参相当于在函数作用域中声明了变量

var a = 1;
function func(){
    console.log(c);
    var c = 10;
}
func();  //此时会输出 “undefined”,因为在 func 函数中var c 变量会声明提前,但是执行到 console 语句时函数作用域中的 c 变量还未被赋值

在函数中不使用 var 声明的变量都会成为全局变量

var a = 1;
function func(){
    console.log(c);
    c = 10;
}
func();  //此时会输出 1,因为函数中 c 没有使用 var 关键字所以不会提前声明,所以向上一级中找 c

四、this

解析器在调用函数每次都会向函数内部传递一个隐含的参数 this

this 指向一个对象,这个对象称为函数执行的上下文对象

根据函数调用方式的不同,this 会指向不同的对象

— 以函数的形式(函数名(),也相当于 window.函数名())调用时,this 永远是 window

— 以方法的形式(对象.方法名())调用时,this 就是调用方法所属的那个对象

— 在构造函数中调用时,this 就是新创建的那个对象

— 使用 call 和 apply 调用时,this 是指定的那个对象

— 在事件响应函数中,this 指向响应函数所绑定的对象(普通情况下)

    — addEventListener() 中 this 指向绑定事件的对象

    — attachEvent() 中 this 指向 window

五、原型对象

prototype

创建的每个函数,解析器都会向函数中添加一个属性 prototype,这个属性对应着一个原型对象

__proto__

若函数作为普通函数调用 prototype 没有任何作用,但当以构造函数形式调用时,它所实例化的对象都会有个隐含属性指向该构造函数的原型对象,可通过 __proto__ 来访问该属性

原型对象中的属性、方法

原型对象相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,可将对象中共有的内容(属性、方法)统一设置到原型对象中

当访问对象的属性或方法时会先在对象自身中寻找,若有则直接使用,若没有则去原型对象中寻找

function Person(name,age,gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
}
Person.prototype.sayName = function(){
    alert(this.name);
}
var per = new Person("xxx",18,"男"); 
per.sayName();

原型对象

使用 "属性名" in 对象实例 检查对象中是否含有某属性时,若对象中没有但是原型中有也会返回 true

使用 对象实例.hasOwnProperty("属性名") 检查的是对象自身中是否含有该属性

原型对象也是对象,所以它也有原型,当使用一个对象的属性或方法时,先在自身中寻找,若没有则去原型对象中寻找,若依然没有则去原型的原型中寻找,直到找到 Object 对象的原型

Object 对象的原型没有原型,若在 Object 中依然没有找到,则返回 undefined。Object 的 __proto__ 是 null

原型的原型

修改原型对象中的方法

当在页面中打印对象时实际上是输出对象的 toString() 方法的返回值,若不希望输出 [Object Object],可为当前对象实例添加一个 toString() 方法,或者修改对象原型的 toString()方法

per.toString() = function(){ //但该方法只对当前实例有效
    return "xxx"
}
Person.prototype.toString = function(){  //该方法对该对象的所有实例有效
    return "xxxx"
}

六、垃圾回收(GC)

程序运行过程中会产生垃圾,垃圾积攒过多后会导致程序运行速度过慢

垃圾:当一个对象没有任何的变量或属性对它进行引用,此时将永远无法操作该对象,这是这种对象就是一个垃圾,这种对象过多会占用大量内存空间导致程序运行变慢,须进行清理

JS 中有自动垃圾回收机制,由浏览器、JS 引擎将这些垃圾对象自动从内存中销毁,我们不需要也不能进行垃圾回收操作

我们需要做的只是要将不再使用的对象设置为 null 即可

垃圾回收

七、数组

数组也是个内建对象,和普通对象不同的是普通对象使用字符串作为属性名,而数组使用数字作为索引操作元素

数组的存储性能比普通对象好,开发中常使用数组存储数据

数组中的元素可以是任意数据类型,也可以是对象(函数、数组等)

使用 typeof 检查一个数组时会返回 object

1、数组对象的操作

var 数组名 = new Array(元素1,元素2,...,元素n)var 数组名 = new Array(数组长度) 使用构造函数创建数组对象

var 数组名 = [元素1,元素2,...,元素n] 使用字面量创建数组对象

数组名[索引] = 值 向数组中添加元素

数组名[索引] 读取数组中的元素,若读取不存在的索引不会报错而是返回 undefined

数组名.length 设置或获取数组的最大索引+1,即使数组中不连续;通过 length 设置数组长度时,若修改的长度大于原长度则多出部分会空出来,若小于原长度则多出部分会被移除

数组名[数组名.length] = 值 向数组最后添加元素

2、数组的方法

添加与删除

push(元素1,元素2,...,元素n) 向数组末尾添加一个或多个元素,并返回数组的新长度

pop() 删除数组的最后一个元素,并返回被删除的元素

unshift(第一个元素,第二个元素,...,第n个元素) 向数组开头添加一个或多个元素,并返回数组的新长度,新数组中新增元素的顺序和传参顺序一致

shift() 删除数组的第一个元素,并返回被删除的元素

数组遍历

数组名.forEach(function(value,index,当前数组)) 遍历数组(只支持 IE8 以上的浏览器),需要一个函数作为参数(这种由我们创建不由我们调用的称为回调函数)

数组中有几个元素函数就会执行几次,每次执行时浏览器会将遍历到的元素以实参形式传进来,浏览器会在回调函数中传递三个参数:(当前正遍历的元素, 当前正遍历元素的索引, 正在遍历的数组)

数组片段获取

slice(开始位置,结束位置) 从某个已有的数组返回选定的元素,第二个参数可选,左闭右开,该方法不会影响原数组,会返回一个新数组

splice(开始位置,删除个数,添加元素1,添加元素n) 删除元组并向数组添加新元素,会修改原数组,并返回被删除的元素,添加的元素会自动插入开始位置,并且顺序与参数传入顺序一致

数组连接

数组1.concat(数组2,数组n,元素1,元素n) 连接两个或多个数组,该方法不会影响原数组,会返回一个新数组

数组.join(连接符) 该方法将数组转换为字符串,方法不会影响原数组,默认使用 , 作为连接符

数组排序

数组.reverse() 用来反转数组,该方法直接修改原数组

数组.sort() 对数组中元素排序,该方法直接修改原数组,默认按照 Unicode 编码排序,所以对数组排序会得到错误结果(1开头都会排在前面)

可在 sort() 中添加回调函数指定排序规则,回调函数中需定义两个形参,浏览器根据回调函数返回值决定元素顺序(返回大于0,元素会交换位置,返回小于等于0,元素位置不变)

数组.sort(function(a,b){
    return a-b;  //升序排列
})

八、call 和 apply

call 和 apply 都是函数对象的方法,需要通过函数对象(不带括号的函数名)调用

相同点:

调用函数执行:当对函数调用 call() 和 apply() 都会调用函数执行

修改函数的 this:调用 call() 和 apply() 可将一个对象指定为第一个参数,此时这个对象会成为函数执行时的 this(原先 函数名() 函数调用时的 this 指向 window)

不同点

call() 方法可将实参在对象之后依次传递

apply() 方法需要将实参封装到一个数组中统一传递

function fun(){
    console.log(this)
}
var obj={
    name: "obj",
    sayName:function(){
        alert(this.name);
    }
}
var obj2={name: "obj2"}
fun.call(obj);  //this 指 obj,apply 同理
obj.sayName.call(obj);  //alert 的是 obj,apply 同理
obj.sayName.apply(obj2);  //alert 的是 obj2,call 同理

function fun(a,b){
    console.log(a);
    console.log(b);
}
obj={
    name: "obj",
    sayName:function(){
        alert(this.name);
    }
}
fun.call(obj,2,3);
fun.apply(obj,[2,3]);

九、arguments

在调用函数时,浏览器每次都会传递两个隐含参数:(1)函数的上下文对象 this(2)封装实参的对象 arguments

arguments 是一个类数组对象,可以通过索引来操作数据,也可以获取长度(arguments.length)

调用函数时,传递的实参都会在 arguments 中保存,即使不定义形参也可通过 arguments 来使用实参(如 arguments[0] 表示第一个实参)

arguments 中有个属性叫 callee,该属性对应当前正在指向的函数对象

function fun(a,b){
    console.log(arguments.callee == fun);  //输出 true
}

十、Date 对象

Date 对象也是一个内建对象

var 变量 = new Date(); 使用构造函数创建 Date 对象,会封装为当前代码执行的时间

var 变量 = new Date("月/日/年 时:分:秒"); 创建指定时间的对象

Date对象实例.getDate(); 获取当前日期对象的日

Date对象实例.getDay(); 获取当前日期对象的星期几,会返回 0-6 表示周日-周六

Date对象实例.getMonth(); 获取当前日期对象的月,会返回 0-11 表示1月-12月

Date对象实例.getFullYear(); 获取当前日期对象的年

Date对象实例.getTime(); 获取当前日期对象的时间戳,时间戳指从格林威治标准时间1970年1月1日0时0分0秒到当前日期所花费的毫秒数

var d = new Date("1/1/1970 0:0:0");
console.log(d.getTime());  //输出为 -28800000,因为系统使用的是北京时间,和格林威治标准时间相差八小时

Date.now(); 获取当前时间戳

计算机底层在保存时间时使用的都是时间戳

可使用时间戳测试代码的执行性能

十一、Math

Math 和其他对象(如 Date)不同,它不是一个构造函数,它属于工具类,封装了数学运算相关的属性和方法

Math.PI 圆周率

Math.abs(数字) 计算绝对值

Math.ceil(数字) 向上取整,小数位有就自动进 1

Math.floor(数字) 向下取整

Math.round(数字) 四舍五入取整

Math.random() 生成 0~1 之间(不包括 0 和 1)的随机数

Math.round(Math.random()*x) 生成 0~x 之间(包括 0 和 x)的随机整数

Math.round(Math.random()*(y-x)+x) 生成 x~y 之间(包括 x 和 y)的随机整数

Math.max(数字1,数字2,...,数字n) 取最大值

Math.min(数字1,数字2,...,数字n) 取最小值

Math.pow(x,y) x 的 y 次幂

Math.sqrt(数字) 开方

十二、包装类

JS 中提供了三个包装类将基本数据类型转换为对象:

String() 将基本数据类型字符串转换为 String 对象

Number() 将基本数据类型数字转换为 Number 对象

Boolean() 将基本数据类型布尔值转换为 Boolean 对象

转换成对象后可添加属性

实际应用中不会使用基本数据类型的对象,若使用基本数据类型的对象,在做比较时可能会有不可预料的结果

方法和属性只能添加给对象,不能添加给基本数据类型

但是当对基本数据类型的值调用属性或方法时,浏览器会临时使用包装类将其转换为对象,然后再调用对象的属性和方法,调用后再转换回基本数据类型,如基本数据类型调用 toString() 方法

String

在底层字符串是以字符数组的形式保存的,可使用索引和 length

字符串.charAt(索引) 返回指定位置的字符,不会影响原字符串

字符串.charCodeAt(索引) 返回指定位置字符的 Unicode 编码

String.fromCharCode(十进制编码或 0x十六进制) 根据字符 Unicode 编码获取字符

字符串.concat(字符串1,字符串2,字符串n) 连接两个或多个字符串,不会影响原字符串

字符串.indexOf(字符,开始检索位置) 返回字符在字符串中第一次出现的索引或 -1(未找到时),第二个参数可选

字符串.lastIndexOf(字符,开始检索位置) 返回字符在字符串中最后一次出现的索引或 -1(未找到时),第二个参数可选

字符串.slice(开始位置,结束位置) 截取字符串,左闭右开,不会影响原字符串,若省略第二个参数则默认是字符串末尾,可使用负值参数

字符串.substring(开始位置,结束位置) 截取字符串,和 slice 类似,左闭右开,,不会影响原字符串,但该方法不能接收负值参数,若传递负值默认使用 0,并且会自动调整参数为升序

字符串.substr(开始位置,截取长度) 截取字符串,不会影响原字符串,但 ECMAScript 没有对该方法标准化,所以不建议使用

字符串.split(分割符) 将字符串拆分为数组,若传递一个空串作为参数,则会拆分出字符串中的每个字符,也可传正则表达式

字符串.toUpperCase() 字符串转为大写,不会影响原字符串

字符串.toLowerCase() 字符串转为小写,不会影响原字符串

十三、正则表达式

正则表达式用于定义一些字符串的规则,计算机可根据正则表达式,检查一个字符串是否符合规则,或将字符串中符合规则的内容提取出来

正则表达式是个对象,使用 typeof 检查正则对象,会返回 object

创建正则表达式

var 变量 = new RegExp(正则表达式,匹配模式) 创建正则表达式对象,匹配模式可选值有 "i" 忽略大小写,"g" 全局匹配模式,可设置多个匹配模式,且顺序无要求,这种创建方式更灵活,因为参数中可传变量

var 变量 = /正则表达式/匹配模式 使用字面量创建正则表达式,如 var 变量 = /a/i;,在正则表达式中使用 |[] 表示或,使用 [^xxx] 表示除了 xxx 以外的内容,这种创建方式更简单

正则表达式的方法

正则对象.test(待检查字符串) 检查字符串是否符合正则表达式规则,返回布尔值

var reg = new RegExp("a");  //该正则表达式可检查一个字符串中是否含有 a,默认严格区分大小
console.log(reg.test("abc")); //返回 true,因为含有 a

var reg = new RegExp("ab","i");  //该正则表达式可检查一个字符串中是否含有 ab,忽略大小写
console.log(reg.test("Abc")); //返回 true,因为含有 ab

var reg = /a|b|c/;  //检查字符串中是否有 a 或 b 或 c
var reg = /[abc]/;  //检查字符串中是否有 a 或 b 或 c
var reg = /[a-z]/;  //检查字符串中是否有任意小写字母
var reg = /[A-Z]/;  //检查字符串中是否有任意大写字母
var reg = /[A-z]/;  //检查字符串中是否有任意字母
var reg = /[0-9]/;  //检查字符串中是否有任意数字
var reg = /a[bde]c/;  //检查字符串中是否有 abc 或 adc 或 aec
var reg = /[^ab]/;  //检查字符串中是否有除了 ab 以外的内容
var reg = /[^0-9]/;  //检查字符串中是否有除了数字以外的内容

字符串和正则相关方法

字符串.split(/[A-z]/); 根据任意字母拆分字符串,该方法即使不指定全局匹配也会全都拆分 
字符串.search(/a[bde]c/); 搜索字符串中是否含有指定内容,返回第一次出现索引或 -1,设置全局匹配无效
字符串.match(/[A-z]/gi); 根据正则表达式,从字符串中将符合条件的内容提取出来,默认只找第一个符合要求的内容,可设置为全局匹配 g 模式这样可找符合要求的所有内容,返回数组
字符串.replace(被替换内容,新的内容); 
字符串.replace(/[a-z]/ig,"");  //可删除所有字母

量词

通过量词可设置内容出现的次数

量词只对它前边的一个内容起作用

{n} 正好出现 n 次

{n,m} 出现 n~m 次

{n,} 出现 n 次以上

n+ 至少出现 1 次 n,相当于 {1,}

n* 出现 0 次或多次 n,相当于 {0,}

n? 出现 0 次或 1 次 n,相当于 {0,1}

^n 以 n 开头

n$ 以 n 结尾

^n|n$ 以 n 开头或以 n 结尾

^n$ 字符串只能是 n

var reg = /a{3}/;  字符串中连续出现 3 次 a
var reg = /(ab){3}/;  字符串中连续出现 3 次 ab
var reg = /ab+c/;  字符串中 abc,其中 b 至少有一个
var reg = /^a/;  字符串中是否以 a 开头
var reg = /a$/;  字符串中是否以 a 结尾

元字符

. 表示任意字符,除了换行和行结束符

\w 表示任意字母、数字、_

\W 除了字母、数字、_,相当于 [^A-z0-9_]

\d 任意数字,相当于 [0-9_]

\D 除了数字,相当于 [^0-9_]

\s 表示空格

\S 除了纯空格

\b 表示单词边界,标识是个独立的单词

\B 除了单词边界

正则表达式中使用 \ 作为转义字符

注意:使用构造函数时,由于它的参数是一个字符串,而 \ 是字符串中转义字符,若使用 \ 则需使用 \\ 代替

var reg = /\\/;
reg.test("b.\");  //返回 false,因为 \ 转义的是 "
reg.test("b.\\");  //返回 true
var reg = new RegExp("\\.");  //相当于 var reg = /\./;
var reg = new RegExp("\\\\");  //相当于 var reg = /\\/;

其他

去除字符串中所有空格

字符串.replace(/\s/g,"")

去除字符串开头的所有空格

字符串.replace(/^\s*/,"")

去除字符串末尾的所有空格

字符串.replace(/\s*$/,"")

去除字符串前后的所有空格

字符串.replace(/^\s*|\s*$/g,"")

检查手机号是否合法:(1)以 1 开头(2)第二位 3-9 任意数字(3)第三位以后任意 9 个数字

var reg = /^1[3-9][0-9]{9}$/  

电子邮箱匹配:任意字母数字下划线.任意字母数字下划线(可有可无) @ 任意字母数字下划线.任意字母数字.任意字母(2-5位).任意字母(2-5位)

var reg = /^\w{3,}(\.\w+)*@[A-z0-9]+(\.[A-z]{2,5}){1,2}$/

十四、DOM

1、DOM

DOM(文档对象模型)是宿主对象,JS 中通过 DOM 对 HTML 文档进行操作

— 文档:整个 HTML 网页文档

— 对象:网页中的每个部分都转换为一个对象

— 模型:使用模型表示对象之间的关系,方便获取对象,DOM 树

2、节点

节点:构成网页的最基本的组成部分(最基本单元),网页中每一部分都可称为节点(如:html 标签、属性、文本、注释、整个文档等),但它们具体类型不同,节点类型不同,则其属性和方法也不同。

常用节点分为四类:

— 文档节点:整个 HTML 文档

— 元素节点:HTML 文档中的 HTML 标签

— 属性节点:元素的属性,并非元素节点的子节点,而是元素节点的一部分

— 文本节点:HTML 标签中的文本内容(任意非 HTML 的文本)

节点属性

节点的共有属性:nodeName、nodeType、nodeValue

nodeName nodeType nodeValue
文档节点 #document 9 null
元素节点 标签名 1 null
属性节点 属性名 2 属性值
文本节点 #text 3 文本内容

浏览器已提供文档节点对象,该对象是 window 的属性,可在页面中直接使用,是全局变量。通过document 对象可在整个文档访问内查找节点对象,并可以通过该对象创建各种节点对象

获取节点

获取元素节点,都通过 document 对象调用

document.getElementById("xxx")   通过 id 获取一个元素节点对象
document.getElementsByTagName("xxx")  通过标签名获取一组元素节点对象
document.getElementsByName("xxx")   通过 name 属性获取一组元素节点对象
document.documentElement;  获取 html 根标签
document.body;  获取 body 标签,保存的是 body 的引用
document.all;  代表页面中的所有元素
document.getElementsByTagName("*");  获取页面中的所有元素,相当于 document.all
document.getElementsByClassName("xxx");  根据元素的 class 属性值查询一组元素节点对象,但该方法不支持 IE8 及以下浏览器
document.querySelector("CSS选择器")  可根据一个 CSS 选择器查询一个元素节点对象,但该方法只返回唯一一个元素,若有多个满足条件的元素,则返回第一个,IE8也支持
document.querySelectorAll("CSS选择器")  可根据一个 CSS 选择器查询一个元素节点对象,但该方法返回符合条件的元素数组

获取/修改元素属性

获取元素属性:元素.属性名,注意 class 属性不能采用这种方式,因为 class 是 JS 中的保留字

读取 class 属性时需要使用 元素.className

获取/修改元素内的文本节点

修改获取到的元素节点对象属性:变量.innerHTML = "xxx",注意 innerHTML 对自结束标签没有意义

或者先获取元素的文本子节点,再获取其 nodeValue 就是文本内容,如 var 变量 = 元素对象.firstChild; console.log(变量.nodeValue)

注:innerText 可获取元素内部的文本内容,和 innerHTML 类似,不同的是它会自动将 html 标签去除只留下文本内容

获取元素节点的子节点

通过具体元素节点调用

getElementsByTagName("xxx")  [方法],返回当前节点的指定标签名后代节点,注意调用对象
childNodes  [属性],表示当前节点的所有子节点(包括元素节点、文本节点等),注意 DOM 标签间的空白也会当成一个文本节点(IE8以上和其他浏览器)
children  [属性],表示当前元素的所有子元素(而非节点,因此不会包含空格换行的文本节点)
firstChild  [属性],表示当前节点的第一个子节点(包括空白文本节点)
firstElementChild  [属性],表示当前节点的第一个子元素(不包括空白文本节点),IE8不支持
lastChild  [属性],表示当前节点的最后一个子节点

获取元素节点的父节点和兄弟节点

通过具体的节点调用

parentNode  [属性],表示当前节点的父节点
previousSibling  [属性],表示当前节点的前一个兄弟节点(包括空白文本节点)
previousElementSibling  [属性],表示当前节点的前一个兄弟元素(不包括空白文本节点)
nextSibling  [属性],表示当前节点的后一个兄弟节点(包括空白文本节点)
previousElementSibling  [属性],表示当前节点的后一个兄弟元素(不包括空白文本节点)

创建元素节点

document.createElement("标签名");  可创建一个元素节点对象,并返回创建好的对象
document.createElement("文本内容"); 可创建一个文本节点对象,并返回新节点
父节点.appendChild(子节点);  可向父节点中添加一个新的子节点
父节点.insertBefore(新子节点,指定的旧子节点);  在指定的子节点前面插入新的子节点
父节点.replaceChild(新子节点,指定的旧子节点);  使用新子节点替换已有子节点
父节点.removeChild(子节点);  删除子节点,相当于 子节点.parentNode.removeChild(子节点)
使用 innerHTML 也可以完成 DOM 的增删改相关操作,但这种方法有时候动静太大

3、文档加载

浏览器加载页面时,按照自上向下顺序加载,读取到一行就运行一行

事件 JS 代码编写位置的三种情况:

(1)若将 <script> 标签写到页面上边 <head> 里,在代码执行时页面还没加载,则 DOM 对象也没有加载会导致无法获取 DOM 对象

(2)将 JS 代码编写到页面下部就是为了可以在页面加载完后再执行,这样性能也更好

(3)将事件写在 onload 事件中,onload 事件会在整个页面加载完成后才触发,确保代码执行时所有 DOM 对象已经加载完毕,支持该事件的对象有 image,layer,window,window.onload = function(){xxx}

4、利用 DOM 修改 CSS 样式

操作内联样式

元素.style.样式名 读取样式

元素.style.样式名 = "样式值" 设置样式,每修改一个样式,浏览器就重新渲染一次页面,这样执行的性能比较差,且要修改多个样式时不太方便

通过 style 属性设置和读取的都是内联样式,无法读取样式表中的样式

若 CSS 样式名中含有 -,这种名称在 JS 中是不合法的,如 background-color,需要将这种样式名改为驼峰命名法(去掉 -,将 - 后的字母大写)

因为内联样式有较高优先级,所以通过 JS 修改的样式往往会立即显示,但不会覆盖添加了 !important 的样式

box1.style.width = "200px";
box1.style.backgroundColor = "red";

通过类修改样式

可通过修改元素的 class 属性间接修改样式,元素对象.className = "xxx"元素对象.className += " xxx"(注意新类前面的空格),这样只需修改一次即可同时修改多个样式,浏览器只需重新渲染页面一次,性能较好,并且这种方式可以使表现和行为进一步分离

读取元素样式

(1)元素.currentStyle.样式名"

获取元素当前正在显示的样式(只有 IE 浏览器支持)

该方法不能修改样式

(2)元素.getComputedStyle(要获取样式的元素,伪元素)

获取元素当前正在显示的样式,第二个参数一般都传 null,该方法返回一个封装了当前元素对应样式的对象

该方法不能修改样式

所有浏览器及 IE9 以上支持

(3)元素.getComputedStyle(要获取样式的元素,伪元素).样式名

获取元素当前正在显示的样式的值,若获取的样式没有设置,则会获取其真实值而非默认值,如若没有设置 width,不会获取到 auto,而会获取其真实宽度

该方法不能修改样式

不支持 IE8 及以下浏览器

(4)同时支持 IE8 和其他浏览器

可通过判断是否有 getComputedStyle 方法,若没有就用 currentStyle

if(window.getComputedStyle){  
//注意这里需要加 window. 若不加是表示寻找变量,找不到就会报错,而加上表示寻找 window 对象的属性,找不到也不会报错而是返回 undefined
    return getComputedStyle(obj,null)
}else{
    return obj.currentStyle.样式名
}

其他样式相关属性

元素.clientWidth 获取元素的可见宽度(包含内容区和内边距)

元素.clientHeight 获取元素的可见高度(包含内容区和内边距)

上述两个属性获取到的值都是不带 px 单位的数字,可直接用于计算,并且是只读的,不能通过该属性修改样式

元素.offsetWidth 获取元素的可见宽度(包含内容区、内边距和边框)

元素.offsetHeight 获取元素的可见高度(包含内容区、内边距和边框)

元素.offsetParent 获取当前元素的定位父元素,获取离当前元素最近的开启了定位的祖先元素,若所有祖先元素都没有开启定位,则返回 body

元素.offsetLeft 当前元素相对于其定位父元素的水平偏移量

元素.offsetTop 当前元素相对于其定位父元素的垂直偏移量

元素.scrollHeight 元素整个滚动区域的高度

元素.scrollWidth 元素整个滚动区域的宽度

元素.scrollLeft 元素水平滚动条移动的距离

元素.scrollTop 元素垂直滚动条移动的距离

onsrcoll 该事件会在元素滚动条滚动时触发

当 scrollHeight - scrollTop = clientHeight 时表示垂直滚动条滚动到底了

当 scrollWidth - scrollLeft = clientWidth 时表示水平滚动条滚动到底了

设置鼠标点击元素按住拖动时保持点击的相对位置不变

鼠标的clientX-元素的offsetLeft
鼠标的clientY-元素的offsetTop

十五、事件

事件:就是文档或浏览器窗口中发生的一些特定的交互瞬间(如点击、鼠标移动、按下键盘、关闭窗口等)

JS 与 HTML 间的交互通过事件实现

在事件响应函数中,响应函数给谁绑定的 this 就是谁

事件处理

可在事件对应的属性中设置 JS 代码,当事件被触发时这些代码会执行

绑定事件的方式:

方式一:
<button id="btn" onclick="alert('xxx')">按钮</button>

方式二:
var btn = document.getElementById("元素id")
btn.onclick = function(){xxx}

事件对象

当事件的响应函数被触发时,浏览器每次都会将一个事件对象作为实参传递进响应函数(除 IE8 及以下浏览器以外)

IE8 及以下浏览器中,是将事件对象作为 window 对象的属性保存的

在事件对象中封装了当前事件相关的一切信息,如鼠标坐标、键盘哪个键被按下、鼠标滚轮滚动的方向

event.clientX 鼠标指针在当前可见窗口的水平坐标

window.event.clientX IE8 中鼠标指针在当前可见窗口的水平坐标

event.clientY 鼠标指针在当前可见窗口的垂直坐标

window.event.clientY IE8 中鼠标指针在当前可见窗口的垂直坐标

event.pageX 鼠标指针相对于整个页面的水平坐标,但在 IE8 中不支持

event.pageY 鼠标指针相对于整个页面的垂直坐标,但在 IE8 中不支持

event.target 表示触发事件的对象,而非事件所绑定的对象,区别于在响应函数中的 this 是指事件所绑定的对象

兼容不同浏览器的两种方式:

元素.事件如onmousemove = function(event){
    if(!event){                     //方式一
        event = window.event;
    }
    event = event || window.event;  //方式二,更常用
}

事件冒泡(Bubble)

冒泡指事件的向上传导,当后代元素上的事件被触发时,其祖先元素的相同事件(如都是 onclick )也会被触发

在开发中大部分情况冒泡是有用的,若不希望发生事件冒泡可通过事件对象的 cancelBubble 属性设置为 true 来取消冒泡

元素对象.onclick = function(event){
    event = event || window.event;
    event.cancelBubble = true;
}

事件的委派

事件的委派指将事件统一绑定给元素的共同祖先元素,这样当后代元素上的事件触发时会一直冒泡到祖先元素,从而通过祖先元素的响应函数来处理事件

通过事件委派可以只绑定一次事件即可应用到多个元素上,即使是后添加的元素

事件委派是利用了冒泡,通过委派可以减少事件绑定的次数,提高程序的性能

触发事件的对象是期望的元素则执行,否则不执行,可通过判断 if (event.target.className == 'xxx') 来实现

事件绑定

(1)元素对象.事件 = 函数

当使用 元素对象.事件 = 函数 的形式绑定响应函数时只能同时为一个元素的一个事件绑定一个响应函数,若为同一事件绑定多个响应函数,则后面的会覆盖前面的

(2)addEventListener()

通过该方法也可为元素绑定响应函数,IE8 及以下浏览器不支持,在 IE8 中可使用 attachEvent() 来实现

参数:

— 事件的字符串(不要加on)

— 回调函数(当事件触发时会被调用)

— 是否在捕获阶段触发事件,需要布尔值,一般都传 false

元素对象.addEventListener("click",function(){},false);

使用 addEventListener() 可以为一个元素的相同事件同时绑定多个响应函数,当事件被触发时,响应函数将会按照函数的绑定顺序执行

addEventListener() 中的 this,是绑定事件的对象

(3)attachEvent()

该方法功能和 addEventListener() 类似,也可以同时为一个事件绑定多个处理函数,不同的是该方法在 IE8 中支持,但在其他浏览器中不支持,并且它是后绑定的先执行,执行顺序和 addEventListener() 相反

参数:

— 事件的字符串(要加on)

— 回调函数

— 是否在捕获阶段触发事件,需要布尔值,一般都传 false

由于 attachEvent() 中的回调函数是由浏览器调用,所以 attachEvent() 中的 this 是 window

(4)兼容所有浏览器

function bind(obj,eventStr,callback){ //(要绑定的事件对象,去掉 on 的事件字符串,回调函数)
    if(obj.addEventListener){  //大部分浏览器兼容的方式
        obj.addEventListener(eventStr,callback,false);
    }else{  //IE8及以下浏览器
        obj.attachEvent("on"+eventStr,function(){
            callback.call(obj);
            //因为 this 是谁由调用方式决定,因为加匿名函数前 callback 由浏览器调用,所以外面加个匿名函数,在函数内来指定 callback 由谁调用
        });
    }
}

事件的传播

关于事件传播,网景公司和微软公司由不同的理解和设计

微软认为事件应该是由内向外传播,当事件触发时应先触发当前元素的事件,再向当前元素的祖先元素上传播,即事件应该在冒泡阶段执行

网景公司认为事件应该由外向内传播,当事件触发时,应先触发当前元素的最外层祖先元素的事件,再向内传播给后代元素(即事件捕获)

W3C 综合了两个公司的方案,将事件传播分成了三个阶段

(1)捕获阶段:从最外层的祖先元素(大部分浏览器从 window 开始),向目标元素进行事件的捕获,但是默认此时不会触发事件

(2)目标阶段:事件捕获到目标元素,捕获结束开始在目标元素上触发事件

(3)冒泡阶段:事件从目标元素向它的祖先元素传递,依次触发祖先元素上的事件

若希望在捕获阶段就触发事件,可以将 addEventListener() 的第三个参数设置为 true,一般情况下不会希望在捕获阶段触发事件,所以这个参数一般是 false

在 IE8 及以下浏览器中没有捕获阶段

鼠标事件

设置鼠标点击元素按住拖动时保持点击的相对位置不变

鼠标的clientX-元素的offsetLeft
鼠标的clientY-元素的offsetTop

onmousemove 该事件会在鼠标在元素中移动时被触发

onmousedown 鼠标按下

onmouseup 鼠标松开,可用于取消其他事件,如

元素对象.onmouseup = function(){
    对象.onmousemove = null;
    对象.onmouseup = null;
}

元素对象.setCapture() 设置元素对鼠标按下相关事件进行捕获,该元素会把下一次所有鼠标按下相关的事件捕获到自身上(该方法只有 IE 支持,在火狐中调用不会报错,但 chrome 会报错)

鼠标拖拽元素的例子:

box.onmousedown = function(event){  //box是开启一个绝对定位的 div
    box.setCapture && box.setCapture();  //设置捕获代替解决 IE8 不支持 return false
    event = event || window.event;
    val ol = event.clientX - box.offsetLeft;
    val ot = event.clientY - box.offsetTop;
    document.onmousemove = function(event){
        event = event || window.event;
        val left = event.clientX - ol;
        val top = event.clientY - ot;
        box.style.left = left + "px";
        box.style.top = top + "px";
    }
    document.onmouseup = function(){
        box.onmousemove = null;
        box.onmouseup = null;
        box.releaseCapture && box.releaseCapture();  //鼠标松开时取消对事件的捕获
    }
    return false;  //因为拖拽时浏览器默认会去搜索引擎中搜索内容,这会导致拖拽功能异常,这是浏览器的默认行为,可通过 return false 来取消(但对 IE8 无效)
}

滚轮事件

onmousewheel 在鼠标滚轮滚动时触发(火狐不支持该属性)

火狐中滚轮滚动可使用 DOMMouseScroll 来绑定滚动事件,且该事件需要通过 addEventListener() 函数来绑定

event.wheelDelta 获取滚轮滚动的方向,往上为正,往下为负(火狐不支持该属性)

火狐中通过 event.detail 来获取滚动方向,往上为负,往下为正

当 scrollHeight - scrollTop = clientHeight 时表示垂直滚动条滚动到底了

当 scrollWidth - scrollLeft = clientWidth 时表示水平滚动条滚动到底了

当滚轮滚动时,若浏览器有滚动条,滚动条会随之滚动,这是浏览器默认行为,可通过 return false 取消该默认行为,若使用 addEventListener() 绑定响应函数应使用 event.preventDefault() 取消默认行为(IE8 不支持preventDefault)

随滚轮滚动时元素高度改变的例子:

box.onnmousewheel = function(event){
    event = event || window.event;
    if(event.wheelDelta>0 || event.wheelDelta<0){
        //向上滚
        box.style.height = box.clientHeight - 10 + "px";
    }else{
        //向下滚
        box.style.height = box.clientHeight + 10 + "px";
    }
    event.preventDefault && event.preventDefault();
    return false;
}
box.addEventListener("DOMMouseScroll",box.onnmousewheel);  //兼容火狐

键盘事件

onkeydown 某个键盘按键被按下,若一直按着按键不松手事件会一直被触发。当 onkeydown 连续触发时,第一次和第二次之间会间隔稍微长一点(这是为了防止误操作发生)其他次会很快触发

onkeyup 某个键盘按键被松开

event.keyCode 获取按键的 Unicode 编码从而判断哪个按键被按下,数字 0-9 对应编码 48-57

altKey、ctrlKey、shiftKey 判断 alt、ctrl、shift 是否被按下

注意键盘事件一般都会绑定给可以获取焦点的对象(如 input)或者是 document,一般不给 div 绑定

document.onkeydown = function(event){
    event = event || window.event;
    if(event.keyCode === 89 && event.ctrlKey){
        console.log("ctrl和y都被按下")
    }
    if(event.keyCode >= 48 && event.keyCode <= 57){
        return false;  //不能输入数字
    }
}
document.onkeyup = function(){
}

根据方向键移动 div 的例子:

document.onkeydown = function(event){
    event = event || window.event;
    var speek = 10;
    if(event.ctrlKey){  //按下 ctrl 后速度加快
        speed = 50;
    }
    switch(event.keyCode){
        case 37:  //向左
            box.style.left = box.offsetLeft - speed + "px";
            break;
        case 39:  //向右
            box.style.left = box.offsetLeft + speed + "px";
            break;
        case 38:  //向上
            box.style.top = box.offsetTop - speed+ "px";
            break;
        case 40:  //向下
            box.style.top = box.offsetTop + speed + "px";
            break;
    }
}

十六、BOM

BOM(浏览器对象模型),BOM 可以使我们可以通过 JS 操作浏览器,DOM 是操作网页文档的,在 BOM 中提供了一组对象来完成对浏览器的操作

BOM 对象有:Window、Navigator、Location、History、Screen

(1)Window:代表整个浏览器的窗口,同时 window 也是网页中的全局对象

(2)Navigator:代表当前浏览器的信息,通过该对象可以识别不同的浏览器

由于历史原因 Navigator 对象中的大部分属性都已经不能识别浏览器了,一般只会使用 userAgent 来判断浏览器的信息,userAgent 是个包含用来描述浏览器信息内容的字符串

注意 IE11 中已经将微软和 IE 相关的表示去掉,所以不能通过 userAgent 识别是否是 IE 浏览器

若通过 userAgent 不能判断,还可通过一些浏览器中特有的对象来判断浏览器信息,如 ActiveXObject 是 IE 独有的

var ua = navigator.userAgent;
if(/firefox/i.test(ua)){
    console.log("火狐");
}else if(/chrome/i.test(ua)){
    console.log("chrome");
}else if(/msie/i.test(ua)){
    console.log("IE");
}else if("ActiveXObject" in window){
    console.log("IE11");
}

(3)Location:当前浏览器的地址栏信息,通过 Location 可获取地址栏信息或操作浏览器跳转页面

若直接打印 location 可获取地址栏的信息(当前页面的完整路径)

location = “xxxx” 直接将属性修改为一个完整的路径或相对路径则页面会自动跳转到该路径,并会生成相应的历史记录

location.assign(“路径”) 用来跳转到其他页面,作用和直接修改 location 一样

location.reload() 重新加载当前页面,作用和刷新一样,若传递 true 作为参数则会强制清空缓存刷新页面

location.replace(“路径”) 使用新的页面替换当前页面,调用完毕也会跳转页面,但不会生成历史记录,不能使用回退按钮回退

(4)History:代表浏览器的历史记录,可通过该对象操作浏览器的历史记录,由于保护隐私原因,该对象不能获取到具体的历史记录,只能操作浏览器向前或向后翻页,且该操作只在当次访问时有效

history.length 可获取到当次访问的链接数量

history.back() 可用来回退到上一个页面,和浏览器中的回退按钮一样

history.forward() 可跳转到下一个页面,和浏览器的前进按钮一样

history.go(整数) 可跳转到指定的页面,需要整数作为参数

— 1 表示向前跳转一个页面,相当于 forward()

— 2 表示向前跳转两个页面

— -1 表示向后跳转一个页面

— -2 表示向后跳转两个页面

(5)Screen:代表用户的屏幕信息,通过该对象可获取到用户的显示器的相关信息(在移动端用的多)

这些 BOM 对象在浏览器中都是作为 window 对象的属性保存的,可以通过 window 对象来使用,也可以直接使用

十七、定时器

setInterval(回调函数,调用间隔) 是 window 对象的方法,将回调函数每隔一段时间执行一次,第二个参数的单位是毫秒,会返回 Number 类型的返回值,该返回值作为定时器的唯一标识

注意在开启定时器前一般需要先使用 clearInterval(xx) 将当前元素上的其他定时器关闭

clearInterval(定时器标识) 关闭定时器,可接收任意参数,若参数是一个有效的定时器标识则停止对应定时器,若不是一个有效标识,则什么也不做

var num = 1;
var timer = setInterval(function(){
    num++;
    console.log(num)
    if(num == 11){
        clearInterval(timer);
    }
},1000);

setTimeout(回调函数,延时时间) 演示调用一个函数不马上执行,而是隔一段时间后再执行,而且只会执行一次

clearTimeout(延时器标识) 关闭一个延时调用

延时调用和定时调用实际上可以互相代替的,注意定时调用会执行多次,而延时调用只会执行一次

根据方向键移动 div 并解决第一次按下时延时的例子:

var speek = 10;
var dir = 0;
setInterval(function(){
    switch(event.keyCode){
        case 37:  //向左
            box.style.left = box.offsetLeft - speed + "px";
            break;
        case 39:  //向右
            box.style.left = box.offsetLeft + speed + "px";
            break;
        case 38:  //向上
            box.style.top = box.offsetTop - speed+ "px";
            break;
        case 40:  //向下
            box.style.top = box.offsetTop + speed + "px";
            break;
    }
})
document.onkeydown = function(event){
    event = event || window.event;
    if(event.ctrlKey){  //按下 ctrl 后速度加快
        speed = 50;
    }else{
        speed = 10;  //松开 ctrl 不加速
    }
    dir = event.keyCode;
}
document.onkeyup = function(){  //松开按键时,div 不再移动
    dir = 0;
}

十八、JSON

JS 中的对象只有 JS 自己认识,其他语言都不认识

JSON(JavaScript Object Notation,JS 对象表示法)是一个特殊格式的字符串,该字符串可被任意的语言识别,并且可转换为任意语言中的对象,JSON 在开发汇总主要用于数据的交互

JSON 和 JS 对象的格式一样,但是 JSON 字符串中的属性名必须加双引号

JSON 分类:

— 对象{}

— 数组[]

JSON 中允许的值有字符串、数值、布尔值、null、普通对象(不包括函数对象)、数值六种

通过工具类 JSON 来互换 JSON 字符串与 JS 中的对象(IE7 及一下浏览器不支持)

json –> js 对象

JSON.parse(JSON字符串)
eval("("+JSON字符串+")")  //IE7中,但不建议使用
通过引入外部的 js 文件(自定义的 JSON 对象)来处理  //兼容 IE7

js 对象 –> json

JSON.stringify(js对象)

eval() 可用来执行一段字符串形式的 JS 代码,并将执行结果返回,若 eval() 执行的字符串中含有 {},它会将 {} 当成代码块,若不希望将其当成代码块解析,需在字符串前后各加一个 ()

但在开发中尽量不要使用 eval(),因为它的执行性能比较差,并且有安全隐患

性能

1、把 JS 写到外部文件中通过外部文件引入可在不页面同时引用,也可利用到浏览器的缓存机制

2、JS 中每条语句以 ; 结尾,若没写分号浏览器会自动添加,但是会消耗一些系统资源,有时浏览器会加错分号

3、把 JS 代码写在 HTML 下方,加载完页面后加载

4、使用 元素.style.样式名 = "样式值" 设置样式时,每修改一个样式,浏览器就重新渲染一次页面,这样执行的性能比较差,且要修改多个样式时不太方便

其他

console.time("计时器的名字") 可用来开启计时器

console.timeEnd("计时器的名字") 可用来终止计时器

往表格 <table> 里使用 appendChild 添加 <tr> 时会添加在 <tbody> 外部,因此需要获取 tbody 对象后对它使用 appendChild 来添加 tr

对于使用 for 循环遍历元素来给元素添加响应函数,需要注意 for 循环会在页面加载完成后立即执行,而响应函数会在被点击时才执行,当响应函数执行时,for 循环早已执行完毕

浏览器的默认行为

对于表单、超链接等,若不想点击后跳转或提交可在响应函数的最后添加 return false 取消默认行为

拖拽时浏览器默认会去搜索引擎中所有内容,这会导致拖拽功能异常,这是浏览器的默认行为,可通过 return false 来取消(但对 IE8 无效)

当滚轮滚动时,若浏览器由滚动条,滚动条会随之滚动,这是浏览器默认行为,可通过 return false 取消该默认行为

在文本框(如 input)中输入内容属于 onkeydown 的默认行为,可通过 return flase 取消默认行为,取消后输入内容不会出现在文本框中

注意若使用 addEventListener() 方法绑定响应函数时取消默认行为不能用 return false,需要使用 event.preventDefault() 来取消默认行为