gin49sz

导航

 

JavaScript

一、js编写位置

位置1:script标签中

<script type="text/javascript">
	var a = 1;
    ...
</script>

位置2:标签中绑定事件

<button onlick="alert(123);">Click</button>

位置3:超链接的href属性中

<a href="javascript: alert('xxx');">Click</a>

位置4:.js文件中

<script type="text/javascript" src="../js/demo.js"></script>

二、基本语法

1. 注释

多行注释

/*
	dad
	dsd
	ada
*/

单行注释

// asdf

文档注释

/*
 *dasdasd
 *adsdasd
 *sddssdd
 */

2. 数据类型

JS一共有6种数据类型,分别是

String 字符串
Number 数值
Boolean 布尔值
Null 空值
Undefined 未定义
Object 对象

String

转义字符

\	转义字符
\"	表示"
\n	表示换行
\t	表示制表 

Number

区分数据类型

typeof

var number1 = 1.2;
console.log( typeof(number1) );

>>> Number

js表示数字最大值

Number.MAX_VALUE

const MAX_NUM = Number.MAX_VALUE;
console.log(MAX_NUM + 1, (-1)*MAX_NUM -1);

>>> "Infinity" "-Infinity"

使用Infinity可以表示无穷大,且infinity本身也是一个数值型,此外-Infinity表示负无穷

var INFINI = Infinity;
console.log( typeof(INFINI) );

>>> Number

同理,JS表示的最小值(最小正数)

Number.MIN_VALUE

将字符串或者非数值型进行一些数学运算会返回NaN

var strPlus = "abc" * "bcd";
console.log(strPlus);

>>> NaN

并且,NaN也是一个数值型

var notANumber = NaN;
console.log( typeof(notANumber) );

>>> Number

Boolean

布尔值,truefalse

Null

空的对象

注意:Null使用typeof不会返回Null类型,没有这个类型

var Empty = null;
console.log( typeof(Empty) );

>>> Object

Undefined

当声明一个变量,但是不给这个变量赋值,那么这个变量的类型就是undefined

var vary1;
console.log( typeof(vary1) );

>>> Undefined

也可以声明变量的值为undefined,等同于未定义

var vary2 = undefined;

3. 强制类型转换

将其他数据类型转换为String、Number、Boolean

将其他类型转换为字符串

方法一:调用toString()方法 - 将其他类型转化为字符串

数值型

var num = 123;

//调用xx的yy方法 -> xx.yy
num_str = num.toString();

//该方法将num转换为字符串并返回(不会修改num的值)
console.log( num_str, typeof(num_str) );

>>> "123" String

可以通过toString返回的变量设置为num,则转换后i的值会直接覆盖原本的值

num = num.toString();

布尔型

var bool = true;
console.log(bool.toString());

>>> "true"

nullundefined 没有toString()方法,执行会报错

方法二:调用String()函数

var a = 123;
b = String(a);

console.log(b, typeof(b));

>>> "123" String

布尔类型同理,并且使用String()函数,可以将 null 和 undefined 也转化为字符串

var a = null;
var b = undefined;

console.log(String(a), String(b));

>>> "null" "undefined"

将其他类型转换为数值型

方法一:调用NUmber()函数

字符型

用法与String()一样,如果转换的内容不是纯数值(包含运算符也不可以)的字符串,则会被转化为NaN

var a = "123abc456";
console.log(Number(a));

>>> NaN

布尔型

布尔转化为数值, ture 会被转化为 1 , false 会被转化为 0

var a = true;
console.log(Number(a));

>>> 1

NullUndefined

null 会被转化为数值 0,而 undefined 会被转化为NaN

如果一个字符串中包含数值内容同时也包含非数值内容,是否可以仅将数值取出呢?

方式二:

parseInt()函数,解析字符串中的整数内容并转化为整数数值型,

parseFloat()函数,解析字符串中的浮点数内容并转化为浮点数数值型

var a = "123abc";
console.log(parseInt(a));

>>> 123
var b = "12.34abc";
console.log(parseFloat(b));

>>> 12.34

注意:如果转换的字符串内容中数值内容在非数值后面,则无法提取出数值

var a = "a123bc";
console.log(parseInt(a));

>>> NaN

并且这两个函数都不可以用在非字符串上,因为parseIntparseFloat本质上是先将参数通过String()转化为字符串然后再转化为字符,所以无论是布尔型还是 Null 或者 Undefined 都只能转化为 NaN

a = true;
console.log(parseInt(a));

>>> NaN

此外,还可以使用parseInt将小数数值转换为整数数值,原理和上述一直,并且parseInt会在读取到第一个非数值字符后就立刻转换,所以会舍去小数点后的内容,但总体上并不推荐这种方式取整数

var a = "12.34"
console.log(parseInt(a));

>>> 12

通过parseInt还可以将数值转化为不同的进制

var a = "070";
b = parseInt(a, 8);
c = parseInt(a, 10);
console.log(a);

>>> 56 70

将其他类型转换为布尔型

只有一种方法:使用Boolean()函数

var a = 123;
console.log(Boolean(a));

>>> true
var b = "";
console.log(Boolean(b));

>>> false

数值型

除了 0 和 NaN ,其余转换都是ture

字符型

除了 "" 其他的都是转换为true

NullUndefined

转化为 false

Object

转化为 true

4. 运算符

算数运算符

当使用两个非字符串类型的数据进行算数运算时候,会先将这两个变量转换为数值型,然后再进行运算

var a = 1 + null + true;
console.log(a);

>>> 2

运算符号是"+"且两个数据类型有一个是字符串类型的时候,非字符串的数据会被转换为字符串,然后会被拼接为一个字符串

var num = 13;
var str = "width=" + num + "px";
console.log(str);

>>> "width=13px"

可以利用这一点快速将数值型转化为字符串

var a = 1234;
a = a + "";
console.log(a);

>>> "1234"

注意:仅在含有字符串的加法是会转化为字符串,其他的算术运算"-"、"*"、"/"、"^"、"%"都是转化为数值型再运算

可以利用这一点快速将数值字符串转化为数值

var a = "123";
a = a - 0;
console.log(a);

>>> 123

其他非加运算符也是同理

一元运算符

+ 正号 - 不会对数值产生任何影响

- 负号 - 可以对数字进行取反

负号对于非数值取反,会将该数据类型转换为数值型,然后再取反

var a = true;
a = -a;
console.log(a, typeof(a));

>>> -1 Number

正号可以将非数值类型转换为数值型,与Number()原理一致

var a = "123";
a = +a;
console.log(a, typeof a);

>>> 123 Number

自增自减

++ 自增

-- 自减

调用自增自减后会修改原数据的值

a = 1;
a++;
console.log(a);

>>> 2

a++ 和 ++a 的区别

相同:调用后都会立刻改变原变量的值

不同:返回值不同,a++ 返回 a ,++a 返回 a+1

var a = 10;
a++;
console.log(a++);

>>> 11

逻辑运算符

! 非 - 对一个布尔值进行取反操作,如果对非布尔值,则会先转换为布尔值,然后取反

利用这一点,可以快速将一个数据类型转换为布尔型

var a = 13;
a = !!a;
console.log(a);

>>> ture

&& 与 - 对符号两侧的值进行与运算并返回结果

利用这一点,可以在执行某个函数 前进行一个判断,如果第一个值为false则不执行,为true则执行

var a = true;
var b = false;
a && console.log("123");
b && console.log("456");

>>> "123"

如果返回值为undefined,则表达式返回的结果也为undefined,但是可以利用非运算将undefined强制转换为false

var a = true;
var b = a && !!alert(111);
console.log(b);

>>> false

|| 或 - 对符号两侧的值进行或运算并返回结果

利用这一点,同样可以做到判断,如果第一个值为ture,则直接返回true,不执行第二项,如果是false,则执行第二项,同理,如果此时第二项返回值为undefined,则返回值也是undefined

var a = false;
var b = a || console.log(123);
console.log(b);

>>> 123 undefined

注意:JS中的或运算是一种 ”短路式“ 的运算,即如果第一项为true或者返回值为true,则会立刻终止,无论后一项返回值是undefined还是别的

关于这一点,JS中的运算逻辑是这样的

与运算:

  1. 如果第一个值为true,则检查第二个值,如果是undefined,则返回undefined
  2. 如果第一个值为false,则无论第二个值是什么,都返回false(发生短路)
  3. 如果第一个值为undefined,则无论第二个值是什么,都返回undefined

或运算:

  1. 如果第一个值为true,则无论第二个值是什么,都返回true(发生短路)
  2. 如果第一个值为false,则检查第二个值,如果是undefined,则返回undefined
  3. 如果第一个值为undefined,则检查第二个值,如果是true,则返回true;如果是false,则返回false;如果是undefined,则返回undefined

总而言之,对于与运算,第一个是true,第二个是什么就返回什么;
对于或运算,第一个是false或者undefined,第二个是什么就返回什么

对于非布尔值的与或运算

会将第一个转化为布尔型,然后根据情况决定是返回第一个值还是第二个值

a = 0 && "abc";
// 因为第一个数据是数值0,转化为布尔值是false,所以直接返回第一个值
console.log(a);

>>> 0

赋值运算符

= += -= *= %=

关系运算符

  • 当 数值或者非字符串 和 非数值类数据 进行比较的时候,会先将数据转化为数值型,然后再进行比较

  • 任何数值和 NaN 做任何关系运算,得到的结果都是false

  • 如果比较的两个数据都是字符串,不会转换为数值进行比较,而是分别比较对应位置上的ASCII码

    var result1 = "A" < "a";
    var result2 = "AB" < "Ab"
    console.log(result1, result2);
    
    >>> true true
    

    特别的

    console.log("111" < "5");
    
    >>> true
    
    //使用 "+" 强制转换为数值型
    console.log("111" < +"5");
    
    >>> false
    

    相等运算符

    == 判断是否相等,当使用 == ,如果两端的值类型不同,则会先转换为相同类型,然后再进行比较(一般都转化为数值)

    个别情况

    console.log( null == 0 );
    
    >>> false
    

    undefined 和 null 同源,他们比较结果是true

    console.log( null == undefined );
    
    >>> true
    

    NaN使用任何运算符结果都是false

    console.log( NaN == NaN );
    
    >>> false
    

    因此无法通过==来判断一个变量是不是NaN,如果一定要判断,可以使用js内置的函数isNaN()

    var a = NaN;
    console.log(isNaN(a));
    
    >>> true
    

    != 判断是否不相等,与相等

    === 判断是否全等(不会做类型转换)

    console.log(null === undefined);
    
    >>> false
    

    !== 判断是否不全等(不会做类型转换)

条件运算符

条件运算符也叫三元运算符

var a = 3 < 4 ? 3 : 4;

条件表达式 ? 语句1 : 语句2

执行的流程:先求条件表达式,如果成立,则执行语句1,否则执行语句2

//获取a 和 b 中的最大值
function get_max(a, b) {
    var max = a > b ? a : b;
    return max
}

var num = get_max(11, 23);
console.log(num)

>>> 23

也可以进行嵌套

function get_max(a, b, c){
    var max = a > b ? (
        a > c ? a : c
    ):(
        b > c ? b : c
    );
    return	max;
}

5. 代码块

JS中代码块是只一个{}中间的内容,代码块的作用就是分组,除此以外没有其他作用

{
    var a = 1;
    console.log(a);
}
console.log(a);

>>> 1 1

6. 流程控制

条件控制语句

var a = 10;
if (a < 11) console.log(a)

>>> 10

if只能执行紧随其后的一行,所以可以使用{}来创建一个代码块来执行多行

var a = 10;
if (a < 11) {
    a+=1;
    cosole.log(a);
}

>>> 11

prompt函数:弹出一个提示框,该提示框带有一个输入框,用户可以在文本框中输入一些内容,该函数会返回输入的内容

var score = prompt("请输入分数:");

排序输入三个整数

function order(num1, num2, num3) {
    if(isNaN(num1) || isNaN(num2) || isNaN(num3)){
        return false;
    }
    var min, mid, max;
    if(num1 > num2 && num1 > num3) {
        max = num1;
        if(num2 > num3){
            mid = num2;
            min = num3;
        }else{
            mid = num3;
            min = num2;
        }
    }else if(num2 > num1 && num2 > num3){
        max = num2;
        if(num1 > num3){
            mid = num1;
            min = num3;
        }else{
            mid = num3;
            min = num1;
        }
    }else{
        max = num3;
        if(num1 > num2){
            mid = num1;
            min = num2;
        }else{
            mid = num2;
            min = num1;
        }       
    }
    console.log("从小到大一次是:" + min + "," + mid + "," + max);
    return true;
}
var num1 = +prompt("请输入第一个数:");
var num2 = +prompt("请输入第二个数:");
var num3 = +prompt("请输入第三个数:");
if(order(num1, num2, num3)) alert("排序成功");
else alert("排序失败")

switch判断语句

对于将一个变量与多个值进行比较的时候,可以使用switch判断语句快速进行判断

格式:

switch(条件表达式){
	case 表达式:
		语句...
	case 表达式:
		语句...
	case 表达式:
		语句...
		...
	default:
		语句...
}

注意:当有一个表达式符合条件后,该表达式和在其后面的表达式全部都会执行

如果只想执行单一表达式,务必要在每个表达式后面加上一个break;

var num = 3;
switch(num){
    case 1:
        console.log("red");
        break;
    case 2:
        console.log("yellow");
        break;
    case 3:
        console.log("blue");
        break;
}

如果是含有区间的比较如何使用switch呢?

使用true作为条件表达式,如果表达式为true则执行,否则与下一条case比对(不如使用if else)

var age = +prompt("输入你的年龄");

switch(true){
    case age < 18:
        console.log("少年");
        break;
    case age >= 18 && age < 30:
        console.log("青年");
        break;
    case age >= 30 && age < 60:
        console.log("壮年");
    default:
        console.log("老年");  
}

循环控制语句

向页面中输入内容

document.write("<p>" + 123 + "</p>")

do...while循环

do{
	语句...
}while(条件表达式);

与while循环的不同之处:do...while会先执行一次循环体之后再进行判断

// 投资年利率为5%,求从1000增长到5000需要花费的年数
var fund = 1000;
var countYear=0;
while(fund < 5000){
    countYear++;
    fund *= 1.05;
}
console.log(countYear);

>>> 33
// 当用户输入数据不合法时候弹出重新输入框
var score = +prompt("请输入分数:");
do{
    if(score >= 60 && score <= 100){
        alert("及格");
        break;
    }else if(score >= 0 && score < 60 ){
        alert("不及格");
        break;
	}
    score = +prompt("请输入有效分数");
}while(true);

for循环

for(初始化表达式;条件表达式;更新表达式)

for循环流程:
	- 循环开始
	- 执行初始化表达式,初始化变量
        - 执行条件表达式,判断是否执行循环
        - 执行更新表达式,更新表达式执行完毕继续重复执行条件表达式
    - 循环结束

for循环中三个位置都可以被任意省略	for(;;){ 代码块... }
//打印1-100所有奇数之和
var sum = 0;
for(let num = 0; num < 100; num++){
    if(num%2 == 0){
        continue;
    }
    sum += num;
}
console.log("1-100奇数之和为" + sum);

>>> 2500
//打印所有的三位数水仙花数之和(个位十位百位的三次方之和是其本身)
var sum = 0;
for(let i = 100; i < 1000; i++){
    let si = parseInt(i%10);
    let de = parseInt((i/10)%10);
    let hu = parseInt(i/100);
    if(si**3 + de**3 + hu**3 == i){
        sum += i;
    }
}
console.log("100-999所有水仙花数之和是:" + sum);
//判断输入的值是不是质数
var num = +prompt("请输入一个数:");
var flag = true;
for(let i = 2; i < num; i++){
    if(num%i == 0){
        flag = false;
        break;
    }
}

if(flag) 
    alert("输入的是质数");
else 
    alert("输入的不是质数");

for循环的嵌套

//乘法表
var height = 10;
for(let i = 1; i < height; i++){
    for(let j = 1; j < i+1; j++){
        document.write("<span>" + j + "*" + i + "=" + i*j + "</span>");
    }
    document.write("<br/>");
}
//1-100全部质数
var max = 100;
var flag;
for(let i = 1; i <= max; i++){
    flag = true;
    for(let j = 1; j <= i; j++){
        if(i%j == 0){
            if(j != 1 && j !=i ){
                console.log(i + " 不是质数");
                flag = false;
                break;					
            }
        }
    }
    if(flag) console.log(i + " 是质数");
}

break和continue

break会立即终止离它最近的循环

continue会跳过当次循环

在JavaScript中,存在label标签,可以让内部循环直接终止最外层的循环(谨慎使用标签)

outer: for(;;){
    for(;;){
        for(;;){
            for(;;){
                break outer;
            }
        }
    }
}

同理,使用continue也可以直接终止外层当前次循环

outer: for(;;){
    for(;;){
        for(;;){
            for(;;){
                continue outer;
            }
        }
    }
}

对程序的执行进行计时

程序开始之前添加

console.time("test");	//提供的参数是计时器的名称,用于在程序中唯一标识一个计时器

程序结束之后添加

console.timeEnd("test");	//终止名称为传入参数的值

Math.sqrt() 可以对一个和数字进行开平方,由此可以对质数查询进行一次改进,当查询的数字超过原数的平方跟,则不再需要查询,通过这种方式可以对原程序进行一次优化

console.time("test");

//1-10000全部质数
var max = 10000;
var flag;
for(let i = 1; i <= max; i++){
    flag = true;
    for(let j = 1; j <= Math.sqrt(i); j++){
        if(i%j == 0 ){
            if(j != 1 && j !=i ){
                //console.log(i + " 不是质数");
                flag = false;
                break;					
            }
        }
    }
    if(flag){
        //console.log(i + " 是质数");
    }
}

console.timeEnd("test");

三、对象

1. 对象的分类

  • 内建对象
    • 由ES标准中定义的对象,在任何的ES的标准中都可以使用
    • 例如:Math、String、Number、Boolean、Function、Object ......
  • 宿主对象
    • 由JS的运行环境提供的对象,目前来讲主要指的是浏览器提供的对象
    • 例如:BOM、DOM
  • 自定义对象
    • 由开发人员自己创建的对象

2. 对象的基本用法

创建对象

使用new关键字调用函数,是构造函数constructor

构造函数是专门用来创建对象的

var obj = new Object();
console.log(typeof onj);

>>> object

增:向对象中添加属性

语法:对象.属性名 = 属性值

obj.name = "孙悟空";
obj.gender = "男";
obj.age = 18;

查:读取对象中的属性

语法:对象.属性名

如果读取对象中没有定义的属性,会返回undefined

console.log(obj.name);

改:修改对象中的属性

语法:对象.属性名 = 新值

obj.name = "SWK";

删:删除对象中的属性

语法:delete 对象.属性名

delete obj.name;

属性名和属性值

  • 属性名

    • 不强制要求遵守标识符的规范,尽量要遵守规范

    • 如果有特殊的属性名,则不能采用.的方式来操作

      语法:对象["属性名"]

      obj["123"] = 456;
      console.log(obj["123"]);
      
      >>> 456
      
  • 属性值

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

      var obj2 = new Object();
      obj2.name = "猪八戒";
      obj.friend = obj2;
      console.log(obj.friend);
      
      >>> {'name':'猪八戒'}
      

      也可以直接取对应的属性值

      console.log(obj.friend.name);
      
      >>> "猪八戒"
      

in运算符

如何查看某个对象是否拥有某个属性?

in运算符

  • 通过该运算符可以检查一个对象中是否含有指定的属性

  • 语法:"属性名" in 对象 注意:属性名要加上引号

    console.log("friend in obj");
    
    >>> true
    
    console.log("friend in obj2");
    
    >>> false
    

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

基本数据类型

修改b的值不会对a产生影响

var a = "Li Ming";
var b = a;
b = "Zhang He";
console.log(a);

>>> "Li Ming"

引用数据类型

修改b的值会影响a

var a = new Object();
a.name = "Li Ming";
var b = a;
b.name = "Zhang He";
console.log(a.name);

>>> "Zhang He"

为什么会有这样的结果?

JS中的变量都是保存到栈内存中的

当创建基本数据类型的时候

var a = "Li Ming";
a.age = 18;
var b = a;

对应内存中的栈结构

变量
a "Li Ming"
b "Li Ming"

修改b的值

b = "Zhang He";

对应内存中的栈结构

变量
a "Li Ming"
b "Zhang He"

所以可以得出,基本数据类型的值是直接在栈内存中存储,值与值之间是独立的,修改一个变量不会影响其他变量

当创建引用数据类型(对象)的时候,对象会被创建在堆内存中,而变量被创建在栈内存中,同时将对象的内存地址存储在值的位置

var a = new Object();
a.name = "Li Ming";
a.age = 18;
var b = a;

对应内存中的栈结构

变量
a 0x0001(地址,指向堆内存中的对象)
b 0x0001
+--------+------+				  +----------------+
|	a	 |0x0001|---------------> | name="Li Ming" |
+--------+------+             /   | age=18         |
|   b    |0x0001|------------/    |			       |
+--------+------+				  +----------------+
	栈内存								堆内存

但是直接修改变量的值而不通过变量取修改对象,则不会受到影响

var a = new Object();
a.name = "Li Ming";
a.age = 18;
var b = a;
b = null;
console.log(a);

>>> {'name':'Li Ming', 'age':18}

此外,直接比较两个基本数据类型,就是比较值,但是比较两个引用数据类型时候,比较的是对象的内存地址

var a = new Object();
a.name = "Li Ming";
a.age = 18;
var b = a;
console.log(a == b);

>>> true
var a = new Object();
a.name = "Li Ming";
var b = new Object();
b.name = "Li Ming";
console.log(a == b);//内存地址不一样,所以不会返回true

>>> false

对象字面量

对象字面量是创建对象的一种方式,相比new Object()更为直观和易读

语法:{属性名: 属性值, 属性名: 属性值 ...}

var a = {
    name: "猪八戒",
    age: 44,
    gender: "男"
    friend: {
    	name: "沙和尚";
	}
};
//也叫名值对,名和值之间用':'连接,多个名值对之间用','隔开,此外,最后一个名值对后面不要加',',浏览器会默认删掉这个','

对象的属性名可以加""也可以不加"",如果使用特殊名字作为属性名,则必须加"",例如{"123": 456}

枚举对象中的属性

使用for...in...语句可以枚举对象中的属性,创建一个变量依次被赋值对象中的一个属性

格式:

for(let 变量 in 对象){
	语句...
}
var obj = {
    name: "Li Ming",
    age: 18;
}
for(let i in obj){
    console.log(i);
}

>>> name age

可以使用 对象[属性名] 的方式依次取到所有的属性值

var obj = {
    name: "Li Ming",
    age: 18;
}
for(let i in obj){
    console.log(obk[i]);
}

>>> "Li Ming" 18

3. 函数

创建函数

使用构造函数来创建函数

函数也是一个对象,可以封装一些功能和需求,在需要的时候可以进行调用

var debugText = new Function();
console.log(typeof debugText);

>>> function

可以将要封装的代码以字符串的形式传递给构造函数

var debugText = new Function("console.log('这是一个函数');");
//封装到函数中的代码不会立即执行,而是在函数调用的时候执行
debugText();

>>> "这是一个函数"

函数对象具所有普通对象的功能

注意:函数对象有一些已经定义的属性名,且这些属性名有的不可以修改,例如name属性就是函数的名称

var debugText = new Function("console.log('这是一个函数');");
debugText.username = "abc";
console.log(debugText.username);

>>> "abc"

使用函数声明来创建函数

语法1:函数声明

function 函数名([形参1, 形参2, 形参3, ...]){
	语句...
}
//调用
函数名()
function debugText(sentence){
    console.log("以下是输出的内容:"sentence);
}
debugText("abcd");

>>> 以下是输出的内容:abcd

语法2:函数表达式(匿名函数)

var 函数名 = function([形参1, 形参2, 形参3, ...]){
	语句...
};
//调用
函数名()
var debugText = function(sentence){
    console.log("以下是输出的内容:"sentence);
};	//匿名函数创建使用了赋值运算,最好在后面加上一个分号
debugText("abcd");

>>> 以下是输出的内容:abcd

参数传递

  • 函数的实参可以是任意数据类型

    实参可以是一个对象

    var consoleInfo = function(obj){
        for(let i in obj){
            console.log(obj[i]);
        }
    };
    var cartoon = {
        name: "孙悟空",
        age: 18,
        address: "花果山",
        gender: "男"
    }
    consoleInfo(cartoon);
    

    实参也可以是一个函数

    var runFunc = function(func, obj){
        console.time("runFunc");
        func(obj);
        console.timeEnd("runFunc");
    };
    runFunc(consoleInfo, cartoon);
    

    也可以将匿名函数作为实参(常用)

    runFunc(function(obj){
        for(let i in obj){
            console.log(obj[i]);
        }
    }, cartoon);
    
  • 在传递参数的时候,如果实参的数量小于形参,则函数中对应的形参会被赋值undefined

返回值

返回值可以是任意的数据类型(包括对象和函数),如果return语句后不跟任何值,则相当于返回一个undefined

使用函数返回一个函数

function fun1(){
	function fun2(){
        console.log("fun2 is running...");
    }
    return fun2;
}
fun1()();

>>> fun2 is running...

区分调用函数和函数对象

  • func1():在函数对象后面加上()意味着调用函数,相当于使用函数的返回值
  • func:只是单纯的函数对象,相当于直接使用一个对象

立即执行函数

匿名函数必须赋值给一个变量用于调用,否则会产生Uncaught SyntaxError: Function statements require a function name错误,但是也可以使用立即执行函数表达式(IFE)来立即执行一个匿名函数,这个函数只能执行一次

(function(){
    console.log("anonymous function is running");
})();

函数作为对象值

var obj = new Object();
obj.name = "Li Ming";
obj.action = function(){
	console.log("Hello, I'm Li Ming");
}
obj.action();

>>> "Hello, I'm Li Ming"

也可以使用对象字面量创建函数属性值

var obj = {
    name: "Li Ming",
    action: function(){
        alert("Hello, I'm Li Ming");
    }
}
obj.action();

>>> "Hello, I'm Li Ming"

如果一个函数作为一个对象的属性保存,那么我们称这个函数是对象的一个方法,调用这个函数称为调用这个对象的方法,但方法和函数只是名称上的区别,本质都是函数对象

作用域

作用域指一个变量作用的范围

在JS中,作用域一共分为两种

  • 全局作用域

    • 直接编写在script标签中的JS代码,都在全局作用域

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

    • 在全局作用域中,有一个全局对象window,它代表浏览器的窗口,可以直接使用

      console.log(window);
      
      >>> [object window]
      
    • 在全局作用域中,创建的变量都会作为window对象的属性保存,创建的函数都会作为window的方法保存

      var num = 10;
      console.log(window.num);
      
      >>> 10
      
    • 变量的声明提前:使用var声明的变量会在所用程序之前声明(先执行程序中的所有var,包括函数中的),但并没有初始化变量,也就是说,如果去输出一个在输出之后声明并初始化的变量,则这个变量的值不会得到

      console.log(a);
      var a = 1;
      
      >>> undefined
      

      包括函数的声明,也是在所有程序之前声明函数变量,但是并没有赋给函数体,因此使用一个在使用之后创建的函数变量也是undefined

      注意:这仅限于函数表达式创建的形式,如果是函数声明function func1(){},则该函数会在代码执行之前创建

      console.log(func1);
      var func1 = function(){
          return true;
      };
      
      >>> undefined
      
      console.log(func1);
      function func1(){
          return true;
      };
      
      >>> true
      
    • 全局变量在页面的任意部分都可以访问到

  • 函数作用域(局部作用域)

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

    • 在函数作用域中可以访问到全局作用域的变量,但是在全局中无法访问到局部
  • 当函数和全局有同名变量,先在自身作用域中寻找

    • 在函数中访问全局变量可以使用window对象
  • 在函数作用域中也有声明提前的特性,使用var关键字声明变量,会在函数中所有代码执行之前声明

    • 在函数中不使用var声明的变量会成为全局变量,即直接使用一个未在函数内定义的变量,会自动创建一个全局的变量,等同于在函数中使用了window.变量
  • 定义一个形参,等同于在函数的作用域中声明了变量

DEBUG

打开浏览器的开发者调试工具,然后在Sources中选择调试代码所在的文件,设置断点即可开始调试

DEBUG

this

解析器在调用函数每次都会向函数内部传递一个隐含的参数,隐含的参数就是 this ,this指向的是一个对象,这个对象称为函数执行的上下文对象,根据函数调用方式的不同,this指向不同的对象

  1. 以函数的形式调用,this 永远都是window
  2. 以方法的形式调用,this就是调用方法的对象
function func(){
    console.log(this.name);
}

函数中的this

var name = "沙和尚";
function sayName(){
    func();
}
sayName();

>>> "沙和尚"

方法中的this

var obj = {
    name: "孙悟空",
    sayName: func
};
obj.sayName();

>>> "孙悟空"

使用工厂方式创建对象

在前面的内容中如果要创建多个对象,必须反复使用对象字面量或者反复声明对象然后在添加属性和属性值,有没有一种方法可以基于一个模板创建类似格式的对象?

使用工厂方法创建对象,本质是写一个返回对象的函数,创建的对象是存储在不同位置的数据类型,相互之间不会影响

function createPerson(name, age, gender){
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.gender = gender;
    obj.sayName = function(){
    console.log(this.name);  
    };
    return obj;
}
var Swk = createPerson("孙悟空", 18, "男");
Swk.sayName();

>>> "孙悟空"

使用工厂方法创建的对象,使用的构造函数都是Object,所以创建的对象都是Object这个类型,因此无法区分不同类型的对象

构造函数创建对象

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

构造函数和普通函数的区别在于,构造函数调用时候需要使用new关键字来调用,普通函数则直接调用

function Person(){
    
}
var a = new Person();
var b = Person();
console.log(typeof a, typeof b);

>>> Object undefined

构造函数的执行流程

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

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

    • 当以函数的形式调用时,this是window

    • 当以方法的形式调用时,谁调用方法this就是谁

    • 当以构造函数的形式调用时,this就是新创建的那个对象

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

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

function Person(name, age, gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
    this.sayName = function(){
        console.log(this.name);
    }
}
var Zbj = new Person("猪八戒", 28, "男");
Zbj.sayName();

>>> "猪八戒"

使用同一个构造函数创建的对象属于一类对象,称之为,通过构造函数创建的对象,称之为类的一个实例

当直接输出新建的对象,可以得到的对象类别为构造函数的函数名

console.log(Zbj);

>>> Person{"name": "猪八戒", "age": 28, "gender": "男"}

instanceof

使用instanceof运算符可以判断某个对象是否是某个类的实例

function Person(){}
function Dog(){}
var Li = new Person();
console.log(Li instanceof Person, Li instanceof Dog);

>>> true false

基于构造函数创造的类,本质上也是Object的泛化,因此使用任何基于构造函数创建的类的实例,在使用instanceof Object的时候都会返回true

console.log(Li instanceof Object);

>>> true

解决函数复用问题(原型)

构造函数创建的实例中的方法,如果使用匿名函数的方式,每次创建对象都会为这个对象创建一个新的方法,但这些方法的内容都是相同的,因此会占据大量不必要的空间

方案一、将函数写到全局作用域中,然后在构造函数中使用这个函数对象(不推荐,会污染全局作用域的命名空间)

function sayName(){
    console.log(this.name);
}

function Person(name, age, gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
    this.sayName = sayName;
}
var Zbj = new Person("猪八戒", 28, "男");
Zbj.sayName();

>>> "猪八戒"

方案二、使用原型

原型 prototype

我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype

function Person(name, age, gender){}
console.log(Person.prototype);

原型

prototype属性对应一个对象,就是所谓的原型对象

  • 如果函数作为普通函数调用,则prototype没有任何作用

  • 如果函数以构造函数的形式调用,则它所创建的对象中都会有一个隐含的属性,指向该构造函数的原型对象,我们可以通过__proto__来访问该属性,与构造函数的原型对象相同

    function MyClass(){}
    var mc = new MyClass();
    console.log(mc.__proto__ == MyClass.prototype);
    
    >>> true
    
    对象原型示例

原型对象保存一块区域,这块区域仅有基于该原型 所创建类的 全部实例 可以访问

可以使用原型对象.变量名 = 对象在这一块区域中创建内容,当对象自身访问对应的属性时候,会先在自身寻找,如果不存在,则会在原型对象中找

function Person(){
}
Person.prototype.nation = "Han";
var a1 = new Person();
console.log(a1.nation);

>>> "Han"

通过这一点,我们就可以解决前面在全局中声明函数问题,通过在原型中添加方法,既减少了空间,又避免了污染全局命名空间

function Person(name){
    this.name = name;
}
Person.prototype.sayName = function(){
    console.log(this.name);
};
var a1 = new Person("Li Ming");
a1.sayName();

>>> "Li Ming"

以后在创建构造函数的时候,可以将这些对象共有的属性和方法统一添加到构造函数的原型对象中

4. 原型对象

检查对象自有属性

使用in检查对象中是否含有某个属性的时候,如果对象中没有,但是原型中有,也会返回true

function MyClass(){}
MyClass.prototype.name = "anonymous";
var mc = new MyClass();
console.log("name" in mc);

>>> true

自有属性(Own Properties)

自有属性是直接在对象实例上定义的属性。这些属性与对象实例紧密相关,并且它们的值可以被对象实例直接访问和修改。自有属性可以通过各种方式添加到对象上,例如使用点符号(.)或方括号([])来为对象赋值。

const myObject = {
  ownProperty: "I am an own property"
};

console.log(myObject.ownProperty); // 输出: "I am an own property"

自有属性可以通过 Object.hasOwnProperty() 方法来检测,该方法会检查属性是否为对象自身的属性,而不是从原型链继承的属性。

继承属性(Inherited Properties)

继承属性是通过对象的原型链继承而来的属性。在JavaScript中,对象的原型是一个指向另一个对象的引用,这意味着对象可以继承其原型上的属性和方法。当试图访问一个对象的属性时,如果该属性在对象自身上不存在,JavaScript 会沿着原型链向上查找,直到找到该属性或到达原型链的末端(通常是 Object.prototype)。

可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性,只有对象自身含有该属性才会返回true

console.log(mc.hasOwnProperty("name"));

>>> false

原型对象的原型

当查看实例对象,类和原型是否含有方法hasOwnProperty()的时候,注意到他们本身都没有这个方法

function MyClass(){}
var mc = new MyClass();
console.log(mc.hasOwnProperty("hasOwnProperty"));
console.log(MyClass.hasOwnProperty("hasOwnProperty"));
console.log(MyClass.prototype.hasOwnProperty("hasOwnProperty"));

>>> false false false

那么这个方法是从何而来?因为一切都是对象,所以原型也是对象,所以原型对象也有自己的原型

当我们使用一个对象的属性或者方法的时候,会在自身中寻找

  • 自身中有则直接使用

  • 如果没有则去原型对象中寻找,如果原型对象中有则使用

  • 如果没有则去原型对象的原型中寻找

    console.log(MyClass.prototype.__proto__.hasOwnProperty("hasOwnProperty"));
    
    >>> true
    
  • 原型的原型,是否还有原型? --没有了,原型的原型(Object)的原型是null,如果原型的原型中没有,则返回undefined

toString方法

当打印一个对象的时候,实际上是调用这个对象的toString方法,这个方法来自原型的原型,也就是 Object对象,当我们调用这个方法并打印,在浏览器的控制台会看到[object Object],但是实际上这和直接打印对象时候的结果并不以一样,为什么呢?

在JavaScript中,当你在浏览器的控制台(Console)中打印(或使用 console.log())一个对象时,浏览器会调用这个对象的 toString() 方法来显示对象的字符串表示。然而,通常情况下,Object.toString() 方法返回的是一个固定的字符串模板,例如 "[object Object]",而不是对象的详细信息。

这里存在一个特殊情况,即当你在控制台中直接输入一个对象变量并回车时,控制台可能会使用一个自定义的 toString() 方法来展示对象的详细信息。这是因为浏览器的控制台为 Object.prototype 添加了一个自定义的 toString() 方法,用于以更友好的方式展示对象的内容。

这个自定义的 toString() 方法通常会检查对象是否有 toJSON() 或者其他可以转换为字符串的方法,如果有,它会使用这些方法来获取对象的详细信息。如果没有,它会尝试使用对象的属性来构造一个字符串表示。

可以通过向类中添加toString方法,来修改打印对象的返回值(当类自身有toString时候,就会直接调用类的方法,不会再往原型查询该方法)

function Student(name){
    this.name = name;
}
Student.prototype.toString = function(){
    return "the func is modified!"
}
var student1 = new Student("Li Ming");
console.log(student1.toString());

>>> "the func is modified!"

5. 程序垃圾回收机制

程序运行的时候会产生垃圾,需要一个垃圾回收机制,来处理程序运过程中产生的垃圾

  • 垃圾定义:没有被任何一个变量或者属性引用的对象

  • 在JS中拥有自动的垃圾回收机制,会自动将这些垃圾对象从内存中销毁,我们不需要也不能进行垃圾回收操作

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

6. 内建对象和宿主对象

内建对象是JS标准中定义的对象

数组(Array)

  • 数组也是一个对象
  • 数组与普通对象功能类似,也是用来存储一些值
  • 不是普通的对象使用字符串作为属性名,而数组使用数字作为索引(对应属性)操作元素(对应属性值)
  • 索引:从0开始的整数
  • 数组的存储性能好于普通对象,因此在开发中经常使用数组来存储一些数据
创建数组对象

方法一:使用new关键字调用函数constructor创建数组

var arr = new Array();

使用方法一可以在创建的时候就指定数组中的元素

var arr = new Array(1, 2);

方法二:使用字面量来创建数组

var arr = [];

使用方法二也可以在创建对象的时候就指定数组中的元素

var arr = [1, 2];

数组中的元素可以是任意类型

obj = {
    name: "孙悟空"
}
function func(){
    return true;
}
arr = [1, 2, "4", true, obj, null, func, [1, 2, 3]];
console.log(arr[4].name, arr[6](), arr[7][0];

>>> "孙悟空" true 1
操作数组对象

添加元素(方法一

语法:数组[索引] = 值

arr[0] = 10;
arr[1] = 11;
console.log(arr);

>>> [10, 11]

读取元素

语法:数组[索引]

如果索引不存在,则会返回undefined

获取数组的长度

语法:数组.length

对于连续的数组,其长度是数组中元素的个数

对于非连续的数组,其长度是数组中最大索引+1

arr[10] = 10;
console.log(arr.length);

>>> 11

修改数组的长度

语法:数组.length = 长度

  • 如果修改的长度大于原长度,则多出的部分用undefined填充

  • 如果修改的长度小于原长度,则多出的元素(索引高的元素优先)会被删除

使用length属性添加元素(方法二

语法:数组[数组.length] = 元素

方法1:push

语法:数组.push(元素1, 元素2, ...)

  • 该方法可以向数组末尾添加一个或多个元素,并返回数组的新的长度

  • 可以将要添加的元素作为该方法的参数传递

    • 这些元素会添加到数组的末尾
    • 该方法的返回值是新数组的长度
var arr = [];
var result = arr.push("唐僧", "蜘蛛精", "白骨精");
console.log(arr, result);

>>> ["唐僧", "蜘蛛精", "白骨精"] 3
方法2:pop

语法:数组.pop()

  • 该方法可以删除数组的最后一个元素,并返回这个元素
var arr = ["唐僧", "蜘蛛精", "白骨精"];
var result = arr.pop();
console.log(arr, result);

>>> ["唐僧", "蜘蛛精"] "白骨精"
方法3:unshift

语法:数组.unshift(元素1, 元素2, ...)

  • 向数组开头添加一个或多个元素,并返回新的数组长度
  • 向数组开头添加元素,其他元素的索引会改变
var arr = ["唐僧", "蜘蛛精", "白骨精"];
result = arr.unshift("牛魔王");
console.log(arr, result);

>>> ['牛魔王', '唐僧', '蜘蛛精', '白骨精'] 4
方法4:shift

语法:数组.shift()

  • 可以删除数组的第一个元素,并将删除的元素返回
var arr = ["唐僧", "蜘蛛精", "白骨精"];
result = arr.shift();
console.log(arr, result);

>>> ['蜘蛛精', '白骨精'] "唐僧"
遍历数组对象

所谓遍历数组,就是将数组中的所有元素都取出来

使用for循环

var arr = [1,2,3,4,5,6];
for(let i = 0; i < arr.length; i++){
    console.log(arr[i]);
}

练习

function Person(name, age){
    this.name = name;
    this.age = age;
}
var perArr = [];
perArr[0] = new Person("Zhang He", 21);
perArr[1] = new Person("Sun Shangxiang", 16);
perArr[2] = new Person("Liu Shan", 8);
perArr[3] = new Person("Liu Bei", 34);
perArr[4] = new Person("Zhao Yun", 22);
perArr[5] = new Person("Diao Chan", 15);
//创建一个函数,可以将perArr中所有的未满18岁的Person提取出来,然后封装到一个新的数组并返回
function SelectYoung(arr){
    var youngArr = [];
    for(let i=0; i < arr.length; i++){
        age = arr[i].age
        if( age < 18){
            youngArr.push(arr[i]);
        }
    }
    return youngArr;
}
var youngArr = SelectYoung(perArr);
console.log(youngArr);

>>> [Person, Person, Person]
    0: Person {name: 'Sun Shangxiang', age: 16}
    1: Person {name: 'Liu Shan', age: 8}
    2: Person {name: 'Diao Chan', age: 15}

使用forEach遍历数组(仅支持IE8以上的浏览器)

语法:数组.forEach(匿名函数)

  • 这种作为参数传递给另一个函数的函数,称为回调函数
  • 数组中有多少元素,就会执行该回调函数多少次,每次执行会将遍历到的元素以实参的形式传递进来
  • 浏览器会在回调函数中传递三个参数
    • 第一个是正在遍历的元素
    • 第二个是当前正在被遍历元素的索引
    • 第三个是正在被遍历的数组
var arr = [1, 2, 3, 4];
arr.forEach(function(value, index, obj){
   console.log(value, index, obj) 
});

>>>  1 0 (4) [1, 2, 3, 4]
     2 1 (4) [1, 2, 3, 4]
     3 2 (4) [1, 2, 3, 4]
     4 3 (4) [1, 2, 3, 4]
方法5:slice

语法:数组.slice(开始, 结束)

  • 可以用来从数组提取指定元素

  • 参数:

    1. 截取开始的位置的索引,包含开始索引

    2. 截取结束的位置的索引,不包含结束索引

      • 截取位置的参数可以不写,表示到结束
    3. 索引可以传递一个负值,表示从后往前(-1表示倒数第一个,-2表示倒数第二个)

  • 该方法不会改变元素数组,而是截取到的元素封装到一个新数组中

var arr = [1, 2, 3 ,4 ,5];
var result = arr.slice(1, 2);
console.log(result);

>>> 2
方法6:splice

语法:数组.splice(开始, 删除元素个数)

  • 可以用于删除数组中的指定元素
  • 使用splice会影响原数组,会将指定元素从原数组中删除,并将删除的元素作为返回值返回
  • 参数 :
    1. 第一个表示开始位置的索引
    2. 第二个表示删除的数量
    3. 第三个及以后,可以传递一些新的元素,这些元素将自动插入到开始位置索引的前面
var arr = [1, 2, 3 ,4 ,5]; 
arr.splice(2, 2);
console.log(result, arr);

>>> [3, 4] [1, 2, 5]
var arr = [1, 2, 3, 4, 5];
arr.splice(0, 1, -1);
console.log(arr);

>>> [-1, 2, 3, 4, 5]

练习

var arr = [1,2,3,4,5,3,2,3,5,6,7,5,3,4,5,6,7,6,5,4,5,6,7,3,2,2,1];
//去除数组中的重复内容
function DeleteRepeat(arr){
    //遍历数组,获取每一项
    arr.forEach(function(value, index, arr){
        //遍历数组当前项后的每一项
        for(let i=index+1; i < arr.length; i++){
            //判断遍历的项与当前项比较,若相同的则删除
            if(value == arr[i]){
                arr.splice(i, 1);
                //删除后将i左移一位,以免跳过下一项
                i--;
            }//endif
        }//endfor
    });
}
DeleteRepeat(arr);
console.log(arr);

>>> [1, 2, 3, 4, 5, 6, 7]
剩余方法

contact:可以连接两个或更多数组,并将新的数组返回

语法:数组1.contact(数组2, 数组3, ...)

  • 该方法不会对原数组产生影响
  • 可以连接多个多个数组,且也可以是元素
var arr = [1, 2, 3];
var result = arr.contact([4, 5, 6], 7, 8);
console.log(result);

>>> [1, 2, 3, 4, 5, 6, 7]

join:将数组转换为一个字符串

语法:数组.join(字符串)

  • 该方法不会对原数组产生影响,将转换后的字符串作为结果返回
var arr = [1, 2, 3, 4];
result = arr.join();
console.log(result, typeof result);

>>> "1, 2, 3, 4" string
  • 在join()中可以指定一个字符串作为参数,这个字符串将会称为数组中元素的连接符,默认是逗号
var arr = [1, 2, 3, 4];
result = arr.join("-");
console.log(result);

>>> "1-2-3-4"

reverse:颠倒数组中元素的顺序

语法:数组.reverse()

  • 该方法用来反转数组,且会修改原数组
var arr = [1, 2, 3, 4, 5];
arr.reverse();

>>> [5, 4, 3, 2, 1]

sort: 对数组进行排序

语法:数组.sort(回调函数)

  • 该方法可以用来给数组元素排序,且会修改原数组
var arr = [4, 5, 2, 3, 1];
arr.sort();

>>> [1, 2, 3, 4, 5]
  • 默认使用unicode进行排序,因此对于数字进行排序可能会得到错误的结果,最好指定排序的规则(回调函数)
    • 回调函数中需要定义两个形参,浏览器将会分别使用数组中的元素作为实参去调用回调函数
    • 浏览器会根据回调函数的返回值来决定元素顺序,如果返回一个大于0的值,则元素会交换位置;反之,如果返回一个小于0的值,则元素的位置不变;如果返回一个0,则表示两个值相等,也不交换位置
var arr = [5, 1, 3, 4, 6, 2, 7];
arr.sort(function(a, b){
    rreturn a - b;
});
console.log(arr);

>>> [1, 2 ,3 ,4, 5, 6, 7]

函数对象(Funciton)

call和apply

语法:函数名.call() 函数名.apply()

  • call和apply都是函数对象的方法,需要通过函数对象来调用
  • 当使用call和apply,都会调用函数执行
  • 在调用call和apply时候,可以将一个对象指定为第一个参数,那么此时这个对象将会成为这个函数执行时候的上下文,即函数中的this指向的就不是window,而是传入的对象
function sayName(){
    console.log(this.name);
}
var obj = {
    name: "Li Ming"
};
var name = "Zhang Hua";
sayName();
sayName.call(obj);
sayName.apply(obj);

>>> "Zhang Hua"
	"Li Ming"
	"Li Ming"
  • call方法可以将实参在对象之后依次传递
function sayName2(a, b){
    console.log(this.name, a+b);
}
sayName2.call(obj, 1, 3);

>>> "Zhang Hua" 4
  • apply方法不可以依次传递实参,需要将实参封装到一个数组中统一传递
sayName2.call(obj, [1, 3]);

>>> "Zhang Hua" 4
agruments

在调用函数时,浏览器每次都会传递两个隐含的参数:

  1. 函数的上下文this

  2. 封装实参的对象arguments

    • arguments是一个类数组对象,即使不定义形参,也能通过其来使用实参
    console.log(arguments instanceof Array);
    
    >>> false
    
    • arguments有一个属性callee,对应的是一个函数对象,就是当前正在执行的函数对象

时间对象(Date)

在JS中使用Date对象来表示一个时间

创建一个Date对象,如果直接使用构造函数,则会封装为当前代码执行的时间

var d = new Date();
console.log(d);

>>> Tue Apr 09 2024 09:09:54 GMT+0800 (中国标准时间)

创建一个指定的时间,需要在构造函数中传递一个表示时间的字符串作为参数

var d2 = new Date("12/03/2024 11:10:30");
console.log(d2);

>>> Tue Dec 03 2024 11:10:30 GMT+0800 (中国标准时间)

日期的格式:

月份/日/年 时:分:秒

方法1:getDate

语法:Date对象.getDate()

返回当前Date对象的日

var d = new Date();
var date = d.getDate();
console.log(date);

>>> 9
方法2:getDay

语法:Date对象.getDay()

返回当前Date对象是周几(0表示周日)

方法3:getMonth

语法:Date对象.getMonth()

返回当前Date对象的月份(从0开始 - 0表示1月,1表示2月,2表示3月 ...)

//从1开始输出月份
var month = +(d.getMonth()) + 1;
方法4:getFullYear()

语法:Date对象.getFullYear()

返回当前Date对象的年份

其他获取时间

getHours:返回小时

getMinutes:返回分钟

getSeconds:返回秒

getMilliseconds:返回毫秒

方法5:getTime

语法:Date对象.getTime()

获取当前日期对象的时间戳

时间戳:指的是从标准时间1970/1/1 00:00:00到该Date对象的毫秒数

作用:统一单位,利于计算机存储时间

可以调用Date类的now方法快速返回一个当前时间的时间戳

var time = Date.now();
console.log(time);

>>> 1712626627066

利用时间戳,可以测试代码的执行性能

var start = Date.now();

代码...

var end = Date.now();
console.log("执行了" + (end - start) + "毫秒");

数学对象(Math)

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

例如Math.PI表示圆周率

方法1:abs

语法:Math.abs(数值对象)

返回一个数值对象的绝对值

方法2:ceil

语法:Math.ceil(数值对象)

返回一个数值对象进行上舍入后的结果(向上取整)

方法3:floor

语法:Math.floor(数值对象)

返回一个数值对象进行下舍入后的结果(向下取整)

与parseInt不同的之处:

  • parseInt可以传递字符串和数值,且字符串可以包含非数字部分,例如"132.12px"
  • Math.floor必须传递数值型,否则会返回NaN
方法4:round

语法:Math.round(数值对象)

返回一个数值对象进行四舍五入后的结果

方法5:random

语法:Math.random()

返回一个0 ~1的随机数

  • 可以利用这一点和取整,来随机获得一个0~10之间的整数(包括0和10)
console.log( Math.round(Math.random()*10) );

>>> 9
  • 生成一个x~y之间的随机数
var x = 1;
var y = 8;
//生成一个1~8之间的整数
var randomNum = Math.round(Math.random)*(y-x)+x;
console.log(randomNum);

>>> 4
方法6:max

语法:Math.max(数值1, 数值2, 数值3, ...)

获取多个数中的最大值

方法7:min

语法:Math.min(数值1, 数值2, 数值3, ...)

获取多个数中的最小值

方法8:pow

语法:Math.pow(数值x, 数值y)

返回x的y次幂

方法9:sqrt

语法:Math.sqrt(数值)

返回数值的平方

包装类

三种包装类

在JS中提供了三种包装类,可以将基本数据类型转换为对象

  • String()
    • 可以将基本数据类型字符串转换为String对象
  • Number()
    • 可以将基本数据类型数值类转换为Number对象
  • Boolean
    • 可以将基本数据类型布尔值转换为Boolean对象
var num = new Number(3);
console.log(typeof num);

>>> Object

将基本数据类型转换为对象之后,就可以利用对象的方式使用这个变量

var num = new Number(12);
num.addOne() = function(){
	console.log(this);
}

>>> Number {12, addOne: ƒ}

注意:一般实际开发过程中不会使用基本数据类型的对象,否则可能会有一些预期以外的结果

var num1 = new Number(123);
var num2 = new Number(123);
//num1 和 num2 在内存中指向不同的对象
console.log(num1 == num2);

>>> false
var bool = new Boolean(false);
if(bool){
    console.log("running...");
}


>>> running

那么这些数据对象存在的意义是什么呢?

当我们对一些基本数据类型的值去调用属性和方法的时候,浏览器会基于这个基本数据类创建一个 临时的包装类 将其转换为对象,然后调用对象的方法

var s = 123;
s = s.toString();
console.log(s, typeof s);

>>> "123" string

临时的包装类会在方法和属性调用完毕后立即被销毁

var num = 3;
num.hello = "hello";
//此时基于3创建了一个临时的包装类Number,并向其添加了一个hello属性,然后销毁了这个对象

console.log(num.hello);
//在打印num的hello属性的时候,又临时创建了一个临时包装类Number,并读取它的hello属性,但是这个包装类不是前一个,因此没有这个属性,打印不存在的属性,会返回undefined

>>>undefined
字符串的方法

在底层,字符串是以字符数组的形式保存

Hello -> ["H", "e", "l", "l", "o"]

利用数组的方法可以操作字符串

var hello = "Hello";
console.log(hello[0]);

>>> "H"

length:属性,可以获取字符串的长度

charAt:方法,根据索引返回指定位置的字符,不改变原字符串,和 字符串[索引] 获取结果一样

charCodeAt:方法,根据索引返回指定位置字符的编码(unicode)

fromCharCode:方法,根据字符编码获取字符,该方法属于构造函数,因此使用时候要加上String.

result = String.fromCharCode(669);
console.log(result);

>>> "B"

contact:方法,连接字符串(和数组使用方法相同)

indexOf:方法,检索一个字符串中是否含有指定内容,有则返回该字符第一次出现的索引,没有则返回-1

lastIndexOf:方法,检索一个字符串中是否含有指定内容,有则返回该字符最后一次出现的索引,没有则返回-1

slice:方法,截取字符串(和数组使用方法相同)- 注意:字符串中没有splice方法

substring:方法,与slice方法基本一样,但是substring不接受负值,如果有负值则视为0,并且会自动交换参数(如果后者小于前者)

var str = "abc";
var result = str.substring(1, -1);
//-1会被识别为0,然后进行交换,变成substring(0, 1)

>>> "a"

split:方法,可以将一个字符串拆分为数组,需要传递一个参数表示拆分的依据(也可以传递一个空字符串,split会将每一个字符都作为新数组的一个元素,然后返回这个数组)

var str = "a,b,c,ef";
var result = str.split(",");
console.log(result)

>>> ["a", "b", "c", "ef"]

toLowerCase:方法,将字符串全部转为小写

toUpperCase:方法,将字符串全部转为大写

正则表达式

正则表达式简介

定义一种规则,用于让机器检查一个字符串是否符合某种要求,或者将符合规则的部分提取出来,这种规则就是正则表达式

创建正则表达式

使用构造函数创建(较为灵活)

语法:var 变量 = new RegExp(正则表达式,匹配模式),使用typeof检查变量,返回Object,创建时候默认区分大小写

// 创建一个正则表达式,检查一个字符串中是否含有a(默认区分大小写)
var reg = new RegExp("a");

匹配模式作为第二个参数,可以是"i" - 忽略大小写、"g" - 全局匹配模式

// 创建一个正则表达式,检查一个字符串中是否含有a/A(不区分大小写)
var reg = new RegExp("a", "i");

也可以使用字面量的方式创建一个正则表达式(较为方便)

语法:var 变量 = /正则表达式/匹配模式(不是字符串)

var reg = /a/i;

不是用其他符号,直接用字符串字母或者数字,创建的是满足 同时拥有且连续 才会返回true的正则表达式的内容

1. 检查一个字符串中是否有a或者b

var reg = /a|b/;	// 表示a或者b

简化方式

var reg = /[ab]/;	//表示a或者b

可以通过 -表示区间

var reg = /[a-z]/;	//[a-z]表示匹配任意的小写字母,也可以用[a-d]表示从a到d
  • [a-z]表示匹配任意小写字母
  • [A-Z]表示匹配任意大写字母
  • [A-z]表示匹配任意字母
  • [0-9]匹配任意数字

可以相互套用

var reg = /a[bcd]e/;	//匹配a开头e结尾,中间b或者c或者d的字符串

2. 检查一个字符串中不含有a

var reg = /[^a]/;	//[^ ]表示匹配不含a字符的字符串

表示不含任何数字

var reg = /[^0-9]/;
正则表达式方法

test

语法:正则表达式.test(要匹配的字符串)

使用这个方法可以检查一个字符串是否符合正则表达式的规则,如果符合返回true,否则返回false

var reg = new RegExp("ab", "i");
var str = "bbaa";
var result = reg.test(str);
console.log(result);

>>> true

var str = "AAbb";
console.log(reg.test(str));

>>> true
搭配字符串的方法

split方法可以传递一个正则表达式作为参数,可以实现根据给定规则的字符来拆分字符串

这里默认全局匹配

var str = "1a2b3c4d5e6f";
var reg = /[0-9]/;
var result = str.split(reg);
console.log(result);

>>> ['', 'a', 'b', 'c', 'd', 'e', 'f']

search方法类似indexOf,可以搜索字符串中是否含有给定内容,并返回第一次出现的索引,可以传递一个正则表达式作为参数,

这里仅支持匹配第一个符合的内容(无论有没有设置全局都只返回一个)

var str = "1a2b3c4d5e6f";
var reg = /[0-9]d/;
var result = str.search(reg);
console.log(result);

>>> 6

match方法,可以根据正则表达式,从一个字符串中将符合条件的内容提取出来

  • 默认情况下,只会返回第一个符合的内容
var str = "1a2b3c4d5e6f";
var reg = /[0-9]/;
var result = str.match(reg);
console.log(result);

>>> ['1', index: 0, input: '1a2b3c4d5e6f', groups: undefined]
  • 设置为全局匹配,就会匹配到所有的内容
var str = "1a2b3c4d5e6f";
var reg = /[0-9]/g;
var result = str.match(reg);
console.log(result);

>>> ['1', '2', '3', '4', '5', '6']

replace方法,可以将字符串中指定内容替换为新的内容,需要传递两个参数,第一个是正则表达式,第二个是用于替换的字符串

与上述同理,默认只匹配第一个,设置为全局匹配就会匹配到所有内容

var str = "1a2b3c4d5e6f";
var reg = /[0-9]/g;
var result = str.replace(reg, "@@@");
console.log(result);

>>> "@@@a@@@b@@@c@@@d@@@e@@@f"
正则表达式语法

1. 匹配n个连续出现的相同字符

  • 通过量词可以设置一个内容出现的次数

  • {n} 表示正好出现n次

匹配连续出现的9个a - "aaaaaaaaa"

var reg = /a{9}/;
  • 量词只对其前面出现的一个内容起作用

匹配a然后连续出现3个b - "abbb"

var reg = /ab{3}/;
  • 如果想表示多个重复出现的字符,用( )括起来

匹配连续出现的3个ab - "ababab"

var reg = /(ab){3}/;
  • {n,m}表示出现n~m次

匹配出现的1~3个ab

var reg = /(ab){1,3}/;
  • {n,}表示出现n次以上(包含n次)

  • n+表示至少一个({1,}),n*表示零个或多个{0,},n?表示零个或一个{0,1}

var reg = /ab+c/;	// abc、abcccccc
var reg = /ab*c/;	// ab、abc、abccccc
var reg = /ab?c/;	// ab、abc

2. 匹配以某个字符开头或者结尾

^字符,表示以该字符开头

检查一个字符串以a开头

var reg = /^a/;	//ahdisds

字符$,表示以该字符结尾

检查一个字符串以a结尾

var reg = /a$/;	//dsadada

如果在正则表达式中同时使用 ^ 和 $ ,则要求字符串必须完全符合正则要求

练习

创建一个正则表达式,用来检查一个字符串是否是一个合法手机号

手机号特点:

  1. 11位
  2. 以1开头
  3. 第二位是3~9任意数字
  4. 三位以后任意数字
var phoneReg = /^1[3-9][0-9]{9}$/;
var phoneNumber = "13345672345";
console.log(phoneReg.test(phoneNumber));

>>> true

3. 匹配字符串中的.

正则匹配中的.表示任意字符

检查一个字符串含有任意三个字符

var reg = /^.{3}$/;

如果.具备了特殊含义,那么如何检查字符串中的.呢?

使用转义字符\

检查一个字符串满足 任意字符开头.任意字符结尾 的格式

var reg = /^.\..$/;	//a.c 3.a 4.2

同理,如果要匹配斜杠,应该使用\\

var reg = /\\/;	//dada\asdas

注意:使用构造函数时,由于参数是字符串,而\是字符串的转义字符,因此使用构造函数检查一个字符串是否含有\要使用\\\\(第一个表示转义第二个斜杆为字符中的普通斜杠,第三个表示转义第四个为普通斜杠,第第二个表示正则匹配中的转义斜杠,第四个表示最后用于匹配的斜杠)

var reg = new RegExp("\\\\");
var str = "dashdiasda\sdadasdasda";
console.log(reg.test(str));

其他特殊字符

特殊字符 含义
\w 任意字母数字(包含下划线)
\W 非字母数字
\d 任意数字
\D 任意非数字
\s 空格
\S 非空格
\b 单词边界
\B 除了单词边界

单词标签指的是一个独立单词两侧的空格,换行或者标点符号

//去掉字符串两端中的空格
var str = "      Hello Li Ming        ";
var reg = /^\s*|\s*$/;	//如果匹配的开头字符,应该避免使用[]构建或集,否则会被识别成[^]否定集
str = str.replace(reg, "");
console.log(str);

>>> "Hello Li Ming"

电子邮件的匹配格式

电子邮件格式

任意字母数字下划线.任意字母下划线@任意字母数字.任意字母(2-5位).任意字母(2-5位)

w{3,} (\.\w+)* @ [A-z0-9]+ (\.[A-z]{2-5}){1,2}

var str = "wqewe1233@ew2w.com";
var reg = /^\w{3,}(\.\w+)*@[A-z0-9]+(\.[A-z]{2,5}){1,2}$/;	
var result = reg.test(str);
console.log(result);

>>> true

宿主对象DOM

DOM简介

DOM全程Document Object Model文档对象模型

JS中通过DOM来对HTML文档进行操作,只要理解DOM就可以操作Web对象

文档

  • 一个HTML网页就是一个文档

对象

  • 对象表示将网页中的每一个部分都转换为一个对象

模型

  • 使用模型来表示对象之间的关系,方便获取对象

模型

节点

  • 节点Node是构成网页的最基本的组成部分,网页中的每一个部分都可以称为是一个节点,但节点之间也有不同
    • 文档节点
    • 元素节点 - 标签
    • 属性节点 - 元素的属性
    • 文本节点 - HTML标签中的文本属性
- nodeName nodeType nodeValue
文档节点 #document 9 null
元素节点 标签名 1 null
属性节点 属性名 2 属性值
文本节点 #text 3 文本内容

节点

浏览器已经提供了文档节点对象,这个对象就是window的属性,可以在页面中直接使用,文档节点代表整个网页

console.log(document);//window.document

>>> #document

通过id获取一个节点对象

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>Title</title>
</head>
<body>
<div>
	<button id="btn">点我</button>
</div>
<script type="text/javascript">
	var btn = document.getElementById("btn");
	console.log(btn);
</script>
</body>
</html>
>>> <button id="btn">点我</button>

通过btn可以对该节点进行操作

var btn = document.getElementById("btn");
btn.innerHTML = "Click Me";
console.log(btn);

>>> <button id="btn">Click Me</button>
事件

事件就是文档或者浏览器窗口中发生一些特定交互的瞬间,JS和HTML之间的交互是通过事件实现的

例如:点击,鼠标移动,关闭窗口,键盘等等

如何处理一个事件?

我们可以在事件对应的属性中设置一些JS代码,当事件触发的时候,这些代码会被执行
但是这种写法结构和行为耦合,不方便维护,不推荐

<button id="btn" click="console.log(1)">点我</button>

还可以为按钮的对应事件绑定处理函数的形式来响应事件,当事件被触发的时候,其对应的函数会被调用

var btn = document.getElementById("btn");
//为按钮绑定一个单击事件,其实就是将一个函数赋给对象的事件属性
btn.onclick = function(){
    this.innerHTML = "Click Me";
}
文档的加载

当将上述的代码放到HTML文件的标签中的时候,JS代码就无法获取到对应的节点对象,这是因为HTML在执行的时候是从上到下运行的,当标签中的内容执行的时候,标签还没有被加载,因此无法被获取到

如果必须将代码放到html的头部执行,则可以通过事件来完成(使用页面加载完成事件绑定代码的执行 - onload)

<head>
	<meta charset="utf-8">
	<title>DataStructure</title>
	<script type="text/javascript">
		//为window绑定事件,该事件对应的响应函数会在页面加载完毕之后执行
		window.onload = function(){
			btn.onclick = function(){
				this.innerHTML = "Click Me";
			}
		}
	</script>
</head>

DOM查询

获取元素节点

通过document对象调用
  1. getElementById()

    通过id属性获取一个元素节点对象

  2. getElementsByTagName()

    通过标签名获取一组元素节点

  3. getElementsByName()

    通过name属性获取一组元素节点兑现

//为id为btn01的按钮绑定事件    
var btn01 = document.getElementById("btn01");
btn01.onclick = function(){
    //查找#bj节点 
    var bj = document.getElementById("bj");
    bj.style = "box-shadow:0 0 0;";
};

//查找所有li节点   
var btn02 = document.getElementById("btn02");  
btn02.onclick = function(){
    //getElementsByTagName可以根据标签名返回对象,返回一个数组对象
    var lis = document.getElementsByTagName("li");
    for(let i=0; i<lis.length; i++){
        console.log(lis[i].innerHTML);
    }
}

//查找name=gender的所有节点  
var btn03 = document.getElementById("btn03");  
btn03.onclick = function(){
    var inputs = document.getElementsByName("gender");
    for(let i=0; i<inputs.length; i++){
        //通过 元素.属性名 可以获取元素节点的属性值,class除外
        //读取class应该用 元素.className
        console.log(inputs[i].value);
    }
}
通过具体的元素节点调用
  1. getElementsByTagName()

    方法,返回当前节点的指定标签名后代节点

  2. childNodes

    属性,表示当前节点的所有子节点

    注意:文本,注释,元素都算作是Nodes,换行也算是文本(IE仅限IE9及以上,IE8及以下不识别文本节点)

    如果想要忽略文本,可以使用children属性

  3. firstChild

    属性,表示当前节点的最后一个子节点,与childNodes同理,文本也算节点

    如果想要忽略文本,可以使用firstElementChild属性,但是不支持IE8及以下

  4. lastChild

    属性,表示当前节点的最后一个子节点,类似firstChild,要忽略文本,使用lastElementChild

获取父节点和兄弟节点
  1. parentNode

    属性,表示当前节点的父节点

    innerHTML:返回元素的所有HTML内容(包含标签)

    innerText:返回元素的所有文本内容(不包含标签)

  2. previousSibling

    属性,表示当前节点的前一个兄弟节点,文本也算节点,要忽略文本,使用previousElementSibling(获取前一个兄弟元素)

  3. nextSibiling

    属性,表示当前节点的后一个兄弟节点

dom查询剩余方法
  1. 获取body

    在document中有一个属性body就表示dom中的body元素

var body = document.body;
  1. 获取html
var html = document.documentElement;
  1. 获取页面所有元素
var all = document.all;
//等同于获取页面中的*
var all = document.getElementsByTagName("*");
  1. 根据元素的class属性值获取一组元素节点对象
var boxes = document.getElementsByClassName("box");
querySelector和querySelectorAll

选择符合多个条件的元素,例如选择是div且样式为box的元素(且该方法支持IE8),可以使用querySelector方法

var div = document.querySelector("div.box");
console.log(div);

>>> <div class="box">..</div>

该方法相比ById,ByTagName,ByName,ByClassName更为灵活,但是该方法只会返回满足条件的第一个元素,即返回的对象为一个Node对象

如果想要返回满足条件的多个对象,可以使用querySelectorAll方法,该方法与querySelector用法类似,即使符合条件的元素只有一个,也会返回NodeList对象

var div = document.querySelectorAll("div.box");
console.log(div);

>>> NodeList []

DOM增删改

方法 描述
appendChild() 把新的子节点添加到指定节点
removeChild() 删除子节点
replaceChild() 替换子节点
insertBefore 在指定的子节点前面插入新的子节点
createAttribute 创建属性节点
createElement 创建元素节点
createTextNode 创建文本节点
getAttribute() 返回指定的属性值
setAtrribute() 修改指定的属性
创建并添加节点

createElement 创建元素节点

语法:var 变量 = document.createElement("元素名")

createTextNode 创建文本节点

语法:var 变量 = document.createElement("文本")

appendChild 添加子节点

语法:父节点.appendChild(子节点)

// 1. 创建广州节点<li>广州</li>
var gzli = document.createElement("li");
var gzText = document.createTextNode("广州");
gzli.appendChild(gzText);
console.log(gzli);

>>> <li>广州</li>

insertBefore 在 指定子节点 前面添加 新的子节点(注意,该方法是父节点的方法)

语法:父节点.insertBefore(新的子节点, 指定子节点)

// 2. 获取#bj节点
var bj = document.querySelector("#bj");

// 3. 将广州节点添加到#bj前面   
var city = document.querySelector("#city"); 
city.insertBefore(gzli, bj);

replaceChild 用 新的子节点 替换 指定子节点

语法:父节点.replaceChild(新的子节点, 指定子节点)

// 3. 将广州节替换#bj节点   
var city = document.querySelector("ul#city"); 
city.replaceChild(gzli, bj);
移除节点

removeChild 移除子节点

语法:父节点.remove(子节点)

// 2. 删除#bj节点   
var city = document.querySelector("ul#city"); 
if(bj){
    city.removeChild(bj);                    
}

如何在不获取父节点的情况下删除子节点?或者说替换子节点?使用 parentNode

// 删除#bj节点   
bj.parentNode.removeChild(bj);

// 将广州节替换#bj节点  
bj.parentNode.replaceChild(gzli, bj);
innerHTML

使用innerHTML也可以完成一些增删改操作

  • 语法:节点.innerHTML = "HTML文本"

​ 将html文本直接替换该节点的全部子节点

  • 语法:节点.innerHTML += "HTML文本"

    将html文本添加到该节点的最后一个子节点后面

通过DOM修改CSS

通过JS修改元素样式

语法:元素.style.样式名 = 样式值

注意:含有"-"的css样式名在JS中不合法,要改成驼峰式命名

通过.style设置的样式都是内联样式,拥有较高优先级,但是无法覆盖!important的样式

var box1 = document.getElementById("box1");
box1.style.width = "300px";
box1.style = "backgroundColor: red;border: 1px solid red;"; //不可以使用background-color
通过JS获取元素样式

语法:var 变量 = 元素.style.样式名

通过style属性只能获得内联样式,无法获取内部样式和外部样式

console.log(box1.style.width);
console.log(box1.style.height);

>>> "300px" ""
获取元素的当前显示样式

语法:元素.currentStyle.样式(仅支持IE浏览器)

var height = box1.currentStyle.height;

在其他浏览器中可以使用getComputedStyle方法来获取元素的样式,该方法会返回一个对象,封装了当前元素对应的样式

语法:var 变量 = 元素.getComputedStyle(参数1, 参数2)

  • 参数1:要获取样式的元素

  • 参数2:可以传递一个伪元素,一般传递是null

如果要获取具体某个样式的值,在后面加上.样式名,不支持IE8即以下浏览器

var box = document.getElementById("box");
var box_style = getComputedStyle(box, null);
console.log(box_style.backgroundColor);

>>> rgb(135, 206, 235)

注意:无论是 currentStyle 还是 getComputedStyle 获取的样式都是只读的,不能进行修改

其他样式的相关属性

clientWidth和clientHeight

  • 这两个属性都可以获取元素的可见宽度和高度
  • 这些属性都是不带单位的,返回都是一个数字,表示该元素的像素值,可以直接进行计算
  • 可见宽度和高度,包括content和padding,不包括border
  • 这些属性都是只读的,不可以修改

offsetWidth和offsetHeight

  • 获取元素整个的宽度和高度,包括border

offsetParent

  • 获取当前元素的定位父元素

  • 会获取到离当前元素最近的开启了定位的祖先元素(必须是absolute或者fixed或者relative,不可以是sticky)

offsetLeft和offsetTop

  • 获取当前元素相对于其父元素的水平偏移量和垂直偏移量

scrollWidth和scrollHeight

  • 可以获取元素整个滚动区域的宽度和高度

scrollLeft和scrollTop

  • 可以获取滚动条在水平和垂直方向移动的距离

利用scrollHeight、scrollTop和clientHeight设置一个滚动到底部自动勾选checkbox的元素

当scrollHeight - scrollTop == clientHeight成立时候,表示滚动条已经到底部

window.onload = function(){
    var box1 = document.getElementById("box1"); // 添加该事件的必须overflow设置为scroll或者auto且有垂直滚动条
    var checkbox = document.getElementById("btn01");
    var inputs = document.getElementsByTagName("input");
    // 滚动条滚动时候查询是否到底

    box1.onscroll = function(){
        console.log(box1.scrollHeight, box1.scrollTop, box1.clientHeight);
        if(box1.scrollHeight - box1.scrollTop == box1.clientHeight){
            // 到底可用
            inputs[0].disabled = false;
            inputs[1].disabled = false;
        }
    }
}
事件对象(event)

当事件的响应函数被触发时,浏览器会将一个事件对象(event)作为实参传递进响应函数,在事件对象中封装了当前事件相关的一切信息,比如:鼠标坐标、键盘是否被触发等等

鼠标/键盘属性

clientX和clientY

  • 可以获取鼠标指针的水平和垂直坐标(相对视口的左上角)(注意,在ie8及以下,没有event对象,event是window的一个属性,因此要使用window.event获取event对象)
var event = event || window.event;// 兼容性配置,如果有event(非ie8)则使用event,否则使用window.event
areaDiv.onmousemove = function(event){
        // 在showMsg中显示鼠标的坐标
        var x = event.clientX;
        var y = event.clientY;
        showMsg.innerText = "x=" + x + " y=" + y;                         
    }
};

通过事件,设置一个跟随鼠标的div

window.onload = function(){
    /**
     * 使div可以跟随鼠标移动
    */
   //获取box1
    var box1 = document.getElementById("box1");
    document.onmousemove = function(event){
        var left = event.clientX;
        var top = event.clientY;

        // 设置div的偏移量
        box1.style.left = left  + "px";
        box1.style.top = top + "px";
        box1.style.transform = "traslate(-50% -50%)"
    };
};

clientX和clientY是获取鼠标在当前视口的可见坐标,因此如果body的长度超出视口(出现滚动条),坐标的位置会跑到视口位置的上方(此时body的顶端位于视口上方),如何解决这个问题呢?

pageX和pageY

  • 可以获取鼠标指针相当于整个文档左上角的水平和垂直距离
1)事件响应的冒泡

执行下面的程序

window.onload = function(){
    // 为s1绑定一个单击响应函数
    document.getElementById("s1").onclick = function(){
        console.log("s1");
    };
    // 为box1绑定一个单击响应函数
    document.getElementById("box1").onclick = function(){
        console.log("box1");
    };
    // 为body绑定一个单击响应函数
    document.body.onclick = function(){
        console.log("body");
    };
}
<body>
    <div id="box1">
        我是box1<span id="s1">我是span</span>
    </div>
</body>

当点击span的时候,会发现同时打印了s1、box1、以及body,这种情况被称为是事件响应的冒泡(bubble)

  • 所谓的冒泡指的就是事件向上传导,当后代元素上的事件被触发时,其祖先元素的相同事件也会被触发
  • 在开发中大部分冒泡是有用的,例如上述的跟随鼠标,如果页面中有一个同级的div,鼠标移动到div中也会将坐标传递到document
  • 如果不希望出现事件冒泡,可以使用event.cancelBubble = true取消冒泡
document.getElementById("s1").onclick = function(event){
    console.log("s1");
    event.cancelBubble = true;
};
2)冒泡的实例:事件委派
  • 只绑定一个元素即可应用到多个子元素上,即使是该元素后添加的子元素上

  • 利用冒泡,将事件绑定到这些子元素以及后面添加的子元素的父元素上,当点击时候就会触发父元素的事件

window.onload = function(){
    document.getElementById("u1").onclick = function(){
        alert("点击了超链接");
    };
    document.getElementById("btn01").onclick = function(){
        var li = document.createElement("li");
        li.innerHTML = "<a href='#'>新添加的超链接</a>";
        document.getElementById("u1").appendChild(li);
    };
};
<body>
    <button id="btn01">添加超链接</button>
    <ul id="u1">
        <li><a href="#">超链接一</a></li>
        <li><a href="#">超链接二</a></li>
        <li><a href="#">超链接三</a></li>
    </ul>
</body>

期望点击事件的元素可能和包裹他们的祖先元素中间有多个元素,如果不希望给这些元素添加事件,要在祖先元素的事件中对这些元素进行区分,那么如何在父元素中得知事件是由什么触发的呢?

3)target

event中的target表示触发事件的对象,在上述案例中,可以使用target来区分触发事件的是a标签还是其他标签

document.getElementById("u1").onclick = function(event){
    if(event.target.nodeName == "A"){
        alert("点击了超链接");
    }
};

在JavaScript中,nodeName属性是DOM(文档对象模型)中Node接口的一个属性,用于获取节点的名称。这个属性对于不同类型的节点返回不同的值。以下是根据您提供的搜索结果中包含的信息,对nodeName属性的详细解释:

  1. 对于元素节点,nodeName属性返回大写的标签名。例如,对于<body>元素,document.body.nodeName将返回字符串"BODY"。
  2. 对于属性节点,nodeName返回属性的名称。例如,如果您有一个<input type="text" id="myInput">元素,并且您想获取id属性的名称,您可以使用document.getElementById("myInput").getAttributeNode("id").nodeName,这将返回字符串"ID"。
  3. 对于文本节点,nodeName属性返回"#text"。类似地,对于注释节点,它返回"#comment"。
  4. 对于文档节点,nodeName属性返回"#document"。
  5. nodeName属性是只读的,这意味着您不能更改节点的名称。
  6. 所有主流浏览器都支持nodeName属性,并且它是DOM Level 1的一部分。
  7. 值得注意的是,对于元素节点,nodeNametagName属性相似,但tagName总是以大写形式返回,而nodeName对于其他类型的节点(如文本、注释等)有不同的返回值8。
4)事件的绑定

使用对象.事件 = 函数的方式绑定响应函数,它只能同时为一个元素一个事件绑定一个响应函数,不能绑定多个,如果绑定多个,则后边会覆盖前边,如果想要在后续为前面绑定过的元素再绑定一个响应事件,要使用addEventListener方法

addEventListener()

  • 通过这个方法也可以给对应的元素绑定响应函数
  • 参数
    1. 事件的字符串,不要on:"click","mousemove"
    2. 回调函数,当事件触发时该函数会被调用
    3. 是否在捕获阶段触发事件,需要一个布尔值,一般都传false
  • this是绑定事件的对象
var btn01 = document.getElementById("btn01");
btn01.addEventListener("click", function(){
   	console.log(1);
}, false);
// 使用addEventListener可以绑定多个事件,当触发后会按照顺序执行
btn01.addEventListener("click", function(){
   	console.log(2);
}, false);

>>> 1
>>> 2

attachEvent()

  • 与addEventListener一样,但是仅限于IE8
  • 参数
    1. 事件的字符串,要on:"onclick"、"onmousemove"
    2. 回调函数
  • 这个方法也可以为同一个事件绑定多个处理函数,但是不同的是它是后绑定的先执行
  • this是window
var btn01 = document.getElementById("btn01");
btn01.attachEvent("onclick", function(){
   	console.log(1);
});
// 使用addEventListener可以绑定多个事件,当触发后会按照顺序执行
btn01.attachEvent("onclick", function(){
   	console.log(2);
});

>>> 2
>>> 1

兼容函数

function bindEvent(obj, eventStr, callBack){
    if(obj.addEventListener){
        obj.addEventListener(eventStr, callBack, false);
    }else{
        obj.attachEvent("on" + eventStr, function(){
            // 在匿名函数中调用回调函数,可以避免this变成window对象
            callBack.call(obj);
        });
    }
}
5)事件的传播

事件的传播包含三个阶段

第一阶段:捕获阶段,从最外层的祖先元素,向目标元素进行捕获,但是此时默认不会触发事件

第二阶段:目标阶段,事件捕获到目标元素,捕获结束触发目标事件

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

  • 默认在捕获阶段不会触发响应函数,但是如果希望触发,可以将addEventListener的第三个参数设置为true,但一般不会这么做,因为如果在捕获阶段触发事件,点击该元素就会先执行其祖先元素的函数不符合常理
6)拖拽

拖拽的流程

  1. 当鼠标在被拖拽元素上按下时,开始拖拽:onmousedown
  2. 当鼠标移动时被拖拽元素跟随鼠标移动:onmousemove
  3. 当鼠标松开时,被拖拽元素固定在当前位置:onmouseup

拖拽的函数,如果想要使用调用函数的call方法,或者给要使用的元素添加这个类方法

function addDragEvent(){
// 点击
this.onmousedown = function(event){
    var setoff_x = event.clientX - this.offsetLeft;
    var setoff_y = event.clientY - this.offsetTop;   

    // 移动
    document.onmousemove = (event) => {
        var x = event.clientX;
        var y = event.clientY;                        
        this.style.left = x - setoff_x + "px";
        this.style.top = y - setoff_y + "px";
    };
    // 一次性事件,触发后取消自己
    document.onmouseup = function(){
        // 取消document的onmousemove
        document.onmousemove = null;
        document.onmouseup = null;
    };

    return false; //关闭默认的拖拽搜索(在浏览器中拖拽一个内容,浏览器会默认去搜索这个内容)
};
}
addDragEvent.call(document.getElementById("box3"));
7)滚轮事件

onwheel鼠标滚动事件,鼠标向上滚动返回150,向下滚动返回-150

var box1 = document.getElementById("box1");
box1.addEventListener("wheel", function(event){
    var wheel = event.wheelDelta;
    if(wheel > 0){
        this.style.height = this.clientHeight > 10 ? this.clientHeight - 10 +"px" : "10px";
    }else{
        this.style.height = this.clientHeight + 10 + "px";
    }
    // 关闭浏览器的滚动
    // 使用addEventListener取消默认行为时,不能使用return false
    event.preventDefault();
});
8)键盘事件

键盘事件

onkeydown

  • 键盘被按下

  • 对于onkeydown来说如果一直按着某个按键不松手,则事件会一直触发

  • 当onkeydown连续触发时,第一次和第二次之间会间隔稍微长一点,其他的会非常快

onkeyup

  • 键盘被松开
  • 松开后只会触发一次

一般会被绑定到会被获取焦点的对象,例如输入框或者是document

宿主对象BOM

  • BOM指的是浏览器对象

  • BOM可以让我们通过JS来操作浏览器

  • BOM中提供了一组对象,用来完成对浏览器的操作

  • BOM对象包含的内容

    • Window

      • 代表整个浏览器的窗口,同时window也是网页中的全局对象

      window的方法

      alert() confirm() prompt()

      setInterval() 定时调用,可以让一个函数每隔一段时间调用一次

      • 参数:

        1. 回调函数
        2. 每次调用的事件间隔,单位毫秒
      • 返回值:

        返回一个number型的数据,表示定时器的唯一标识,通过这个标识可以关闭定时器

      clearInterval() 关闭定时器,传递一个唯一标识定时器的变量或者数值,可以接受任意参数,但是只有存在的定时器标识才有反应

      使用setInterval关闭页面的输入防抖

      window.onload = function(){
          var pressKey = null;
          var speed = 0;
          var box = document.getElementById("box");
          var timer = setInterval(function(){
              switch(pressKey){
              case "ArrowLeft":
                  box.style.left = box.offsetLeft - speed + "px";
                  break;
              case "ArrowRight":
                  box.style.left = box.offsetLeft + speed + "px";
                  break;
              case "ArrowUp":
                  box.style.top = box.offsetTop - speed + "px";
                  break;
              case "ArrowDown":
                  box.style.top = box.offsetTop + speed + "px";
                  break;
              }
          }, 10);
      
          document.onkeydown = function(event){
              pressKey = event.key;
              if(event.ctrlKey){
                  speed = 30;
              }else{
                  speed = 10;
              }
          };
          document.onkeyup = function(event){
              pressKey = null;
          }
      };
      

      setTimeout() 延时调用,一个函数不立刻执行,而是隔一段时间后再执行,只会执行一次

      clearTimeout() 关闭延时调用

      延时调用和定时调用可以相互替代

    • Navigator

      • 代表当前浏览器的信息,可以使用navigator.userAgent来判断当前浏览器是什么
      console.log(navigate..userAgent)
      
    • Location

      • 代表当前浏览器的地址栏,通过Location可以获取地址栏信息,或者操作浏览器跳转页面
      console.log(location);
      
      >>> 直接打印获取当前页面的信息
      

      赋值网址,自动跳转到对应网页

      location = "https://www.baodu.com";
      

      location的属性

      hash 返回或者设置#后的URL

      host 返回或者设置主机名和端口号

      hostname 返回或者设置主机名

      href 返回或者设置完整URL

      pathname 返回或者设置当前段URL路径部分

      port 返回或者设置当前URL协议

      search 返回或者设置?开始的查询部分

      location的方法

      assign() 传递一个页面地址的字符串,跳转到对应网站,和给location赋值一样

      reload() 重新刷新页面,可以传递一个布尔值,表示是否强制清空缓存

      replace() 传递一个路径字符串,使用一个新的页面代替当前页面(无法回退,不会生成历史记录)

    • History

      • 代表浏览器的历史记录,可以通过该对象来操作浏览器的历史记录(只能向前或者向后翻页,不能真的查看历史)

      history.back() 跳转到上一个界面

      history.forward() 跳转到下一个界面

      history.go() 接受一个参数n,跳转到下n个界面(正数下n个界面,负数上n个界面)

    • Screen

      • 代表用户的屏幕信息,通过该对象可以获取到用户的显示器的相关的信息

这些BOM对象都是作为window对象的属性获得

console.log(window.navigate);

类的操作

通过style.样式名 = 样式的操作,执行性能比较差,且浏览器需要重新渲染页面,使用class实现一行修改多个样式

  • className

语法:对象.className = "选择器"

通过修改class的属性来见解修改样式,但是会删除原有样式,如果是想在原有样式基础上添加样式,应该使用

对象.className += " 选择器"(此处选择器前有一个空格)

可以定义一个函数判断指定元素中是否含有某个样式

function hasClass(obj, css){
    var reg = new RegExp("\\b" + css + "\\b");
    return reg.test(obj.className);
}

可以定义一个函数向指定元素中添加样式

function addClass(obj, css){
    if(!hasClass(obj, css))
    	obj.className += " "+css;
}

可以定义一个函数删除指定元素中的指定样式

function removeClass(obj, css){
    var reg = new RegExp("\\b" + css + "\\b");
    obj.className = obj.className.replace(reg, "");
}

可以定义一个函数切换指定元素中的指定样式

function toggleClass(obj, css){
    // 切换元素中的指定样式,指定一个样式,该元素中如果有,则删除,如果没有,则添加
    if(hasClass(obj, css))
        removeClass(obj, css);
    else
        addClass(obj, css);
        
}

JSON

js中的对象只有自己认识,其他语言都不认识,所以要将js中的对象转化为所有语言都认识的格式 - 字符串

转化而来的字符串有自己的名字,即JSON,JSON就是一个特殊的字符串,可以被任意语言所识别

JSON的格式与js对象基本一致,但是属性名必须以字符串的形式,且值只能允许字符串,数值,布尔,null,对象和数组

JSON分类:

  1. 对象{}
  2. 数组[]
将JSON转化为JS中的对象

JS中提供一个工具类,叫JSON,可以将一个JSON转换为JS对象
JSON --> JS对象

语法:var 变量= JSON.parse(JSON字符串)

var json = '{"1":"aa", "2":"bb"}';
var obj = JSON.parse(json);

var json2 = '[a, b, c]';
var arr = JSON.parse(json2);
将JS中的对象转化为JSON

JS对象 --> JSON

语法:var 变量 = JSON.stringfy(JS对象);

var obj = {name: "abc", age: 17};
var json = JSON.stringfy(obj);
posted on 2024-04-14 10:42  树深时见鹿nnn  阅读(7)  评论(0编辑  收藏  举报