【JavaScript超详细的学习笔记-上】JavaScrip超详细的学习笔记,共27部分,12多万字

**

想要获取笔记的可以点击下面链接获取,或者私信我

**
JavaScript超详细的学习笔记,点击我获取
在这里插入图片描述

一,JavaScript详细笔记

1,基础知识

1-1 基础知识

// 1,标识符命名规则:第一个字母必须是字母,下划线或一个美元符号。不能数字开头,通常不用汉字;
// 2,空格忽略,所以可以在代码中随意使用空格和换行;
// 3,注释 // 单行注释; /* 注释内容 */ 多行注释;
// 4,直接量(literal) 12 1.2 “hello wold” true 等等;(一般赋给变量或者用在表达式里面);
// 5,严格模式 顶部添加 “use strict”;
// 6,语句以一个分号结束,如果省略分号,则由解析器确定语句结尾;

1-2 变量

function show(){
	var msg="aini";
}
show();
console.log(msg)

// 函数内用 var 定义的变量是局部变量,只能在函数内使用;
function show(){
	msg="aini";
}
show();
console.log(msg)
// 不用 var,而直接赋值就是全局变量,可以在函数外边使用;

注:虽然省略了 var 可以定义全局变量,但不推荐使用这种做法,因为正在局部作用域中的全局变量很难维护。

1-3 数据类型

// 1,js 不区分浮点数和整数;

// 2,除了十进制,整数还可以用八进制和十六进制来表示;16 进制前面加 0x;8 进制前面必须有 0(或者 0o);计算或输出的时候会换算成 10 进制输出;(严格模式下 8进制无效,ES6 页不能使用的)

// 3,极大数或极小数可以用科学计数法 e 如 3e+10=3^10/3e-10=3^(-10) 加号可省略;默认情况下,会将那些小数点后面带有 6 个零以上的浮点数转换为以 e 表示的数值;
console.log(0.1 + 0.2); 不等于 0.3,而是等于 0.30000000000000004// 所以浮点数加减不准确;

4,数据范围,由于内存限制,ESmascrip 并不能保存任意的数值;其能够表示的最小值保存在 Number.MIN_Value 中,在绝大多数浏览器中,这个值是5e-324;表示最大值保存在 Number.MAX_Value 中,值为1.7976931348623157e+308

console.log(Number.MAX_VALUE); 1.7976931348623157e+308
console.log(Number.MIN_VALUE); 5e-324
console.log(Number.MAX_SAFE_INTEGER); 9007199254740991 // 安全整数值;
console.log(Number.MIN_SAFE_INTEGER); -9007199254740991

5,想确定一个数是不是有穷的,可以使用 isFinite()函数,这个函数会判断参数是否位于最小值与最大值之间;

var a=10;  // 返回为 TRUE
console.log(isFinite(a));

6,访问 Number.POSITIVE_INFINITY 和 Number_NEGATIVE_INFINITY 也可以得到正负infinity 值;既这两个属性中分别保存着 infinity 和-infinity;

console.log(Number.POSITIVE_INFINITY); infinity
console.log(Number.NEGATIVE_INFINITY); -infinity

7,NaN 非数值(not a number)用于返回一个本来要返回数值却未返回数值的情况;

注:除以 0 的结果是 infinite,不是 NaN; NaN 与任何值不相等,包括其本身;任何涉及 NaN 的计算都会返回 NaN;

console.log(3/"a"); NaN
console.log(NaN==NaN); flase

8,isNaN()函数 接受一个参数;判断这个参数可以是任何类型,其判断改参数是否“不是数值”,这个函数首先尝试把这个参数转换成数值;某些不是数值的值会直接转换成数值,比如字符串“10”,或者 Boolean,而任何不能被转换为数值的值都会导致这个函数返回 TRUE;

console.log(isNaN("number")); // 是非数字 所以 TRUE
console.log(isNaN(10)); // 是数字 所以 false
console.log(isNaN("10")); // 能转换成数字 所以 false
console.log(isNaN(NaN)); // 不是数字 所以 true
console.log(isNaN(false));//  能转换成数字 所以 false

9,string 类型 一般使用单引号 如 userName=’aini’; 空格也是字符串;

10,转义字符 \n 换行 \t 制表符 \b 退格 \r 回车 \f 进纸 \ 斜杠 \’ 单引号 \” 双引号 \nn 以 16 进制代码表示的一个字符 \unnnn 以 16 进制代码 nnnn 表示的一个 Unicode;

var src = "zero\nnetwork " // 输出到页面时,不会换行;因为 HTML 代码换行时页面中不
document.write(src);//  换行,只是多了空格;

// 书写字符串直接量可以拆分多行,每行必须加上反斜杠\结束;

11,布尔类型

var a = 10, b = 15;
console.log(a == b);
if(a == b){
	console.log("相等");
}else{
	console.log("不相等");
}

12,Undefined 未定义的:此类型只有一个值,既特殊的 undefined;在使用 var 声明变量,但未对其初始化;

13,Null :此类型也只有一个值,既特殊的 null 值(空值);从逻辑上看。Null 值表示一个空对象指针,而这也正是使用 typeof 操作检测 null 时会返回 object 的原因;

var car = null;
console.log(car); // null
console.log(typeof car); // object

// 注:实际上,undefined 值是派生自 null 值得,因此 ECMA-262 规定对他们相等性测试要返回 TRUE;如
console.log(null == undefined); // TRUE

1-4 复杂数据类型

1,也称为复杂的数据类型;就是一组数据和功能的集合;

var person = {};
person.name="aini";
person.age=18;
person.say = function(){
console.log("my name is aini");

console.log(person); // {name: 'aini', age: 18, say: ƒ}

2,常见的引用数据类型;

var arr = [1, 3, 4, 5];
console.log(arr); // (4) [1, 3, 4, 5]
function show(){};
console.log(show); // ƒ show(){}
var date = new Date();//  Mon Sep 12 2022 14:04:42 GMT+0800 (中国标准时间)
console.log(date);

3,Object 对象

​ 1,Object 对象时多有对象的基础,因此多有对象都具有这些基本的属性和方法;

​ 2,toSrting(): 返回对象的字符串表示; valueOf():返回对象的字符串,数值或布尔值表示,通常与 toString()返回值相同;

var o = new Object();
var arr = new Array(1,2,3,4);
var fun = new Function("a","b");
console.log(o); // {}
console.log(arr); // (4) [1, 2, 3, 4]
console.log(fun); // ƒ anonymous(a) {b}
console.log(o.toString()); // [object Object]
console.log(arr.toString()); // 1,2,3,4
console.log(fun.toString()); // function anonymous(a) {b}
console.log(o.valueOf()); // {}
console.log(arr.valueOf()); // (4) [1, 2, 3, 4]
console.log(fun.valueOf());//  ƒ anonymous(a) {b}

通常这两个方法是可以改写的,如:

var o = new Object();
o.valueOf = function(){
	return "this is a object";
}
o.toString = function(){
	return "this is my name";
}
console.log(o.valueOf()); // this is a object
console.log(o.toString());//  this is my name

4,可变类型和不可变类型;

​ 1,原始值是不可变类型,引用数据(复杂数据)类型属于可变类型;

var arr = [1,2];
var arr1 = arr;
arr1.push(3);
console.log(arr);

// Arr 和 arr1 指向同一个堆,所以改变一个的值,另一个也随之改变;

image-20240130225435624

var arr = [1,2];
var arr1 = arr;
arr1 = [1,2,3]  // (重新创建了 arr1,并覆盖了之前的数据)
console.log(arr);//  [1,2]

1-5 数据类型转换

1,因为数据之间是同类型的运算,如果是不同类型的运算,需要将其转换为同一类型后再进行运算;

2,类型分两类:强制类型转换,自动类型转换;

3,自动类型转换:根据运算的语义环境,会自动进行类型转换;

var a = "2",b = "1";
console.log(a-b); // 1;
b = true;
console.log(a-b); // 1;
// 注:最经典的用法在 if 语句条件中;
var car = new Object();
if(car){ //加入为真(存在),执行花括号里的语句
	console.log("汽车"); //汽车
}
// 自动转换注意点:
	1,” “ 空字符串转成数字 0;
	2,[] 空数组转换数字为 0;
	3,Function() 能转换成字符串”function” 转换为数字为 NaN ;
1-5-1 强制类型转换
// 1,尽管 JavaScript 可以自动进行类型转换,但有时仍需要显式转换,或者有时为了使代码变得更加清晰易读;使用 string(),number(),Boolean();

// 2,转换为数值;Number(); parseInt();parseFloat(); number() 可以用于任何类型,另外两个则专门用于把字符串转成数值,这三个函数对于同样的输入有不同的返回结果;

// 3,number()函数,转换规则如下;
	1,Boolean 1,0;
	2,数字 直接返回该数字;
	3,undefind NaN;
	4null 0// 如果是字符串则遵循以下规则;
	1,”123123;
	2,“012351235; // (去掉最前面的 0)
	3,“1.11.1 //(浮点型);
	4,“01.11.1 // (浮点型);
	5,“0xff255; // (number()能识别出 16 进制);
	6,“” 0;
	7,“123abc” //  NaN;
var value = {};
console.log(Number(value)); // NaN

var value = {};
value = {valueOf:function(){return 18}};
console.log(Number(value)); // 18

var value = {};
value = {valueOf:function(){return true}};
console.log(Number(value));
// 4,有一些操作符(+,-)的操作与 Number()函数相同,会将操作数转换为数字;
var x = "w";
console.log(typeof +x); // number (加了正负号以后把数据类型自动改了)

var x = "w";
console.log(typeof +x); //number (加了正负号以后把数据类型自动改了)
console.log(x); // "w"
console.log(+x); // NaN
1-5-2 parseInt() 函数;
// 1,number 函数在转换字符串时比较复杂而且不够合理;并且只能基于十进制进行转换,且不能出现非法的尾随字符,因此在处理整数的时候通常用的是parseInt()和 parseFloat();

// 2,二者都是全局函数,不属于任何对象的方法;
console.log(parseInt(" 8a:")); // 8;
console.log(parseInt(22.5)); // 22;
console.log(parseInt("wang1")); // NaN;
console.log(parseInt("")); //NaN
var value = "";
console.log(Number(value)); //0
console.log(parseInt(value)); //NaN
// 3,parseInt() 能识别 16 进制,识别不了字符串的 8 进制;

console.log(parseInt("0xff")); //255;
console.log(parseInt("0xafwangwei")); // 175(自动忽略除了 16 进制以外的部分)
console.log(parseInt(070)); //56;(不用字符串表示,可以识别出 8 进制并转化)
console.log(parseInt("075")); //75 (字符串里的八进制无法识别,会忽略 0 在转换为整数)
// 4,parseInt()识别八进制时,第 3 版可以识别,但第 5 版识别不了,为解决这个问题,可以为 parseInt()指定第二个参数:转换基数;

console.log(parseInt("0xff",16)); // 255 把 16 进制转换为 10 进制
console.log(parseInt("077",8)); // 63 把 8 进制转换为 10 进制
console.log(parseInt("100101",2)); // 37 把 2 进制转换为 10 进制
console.log(parseInt("054",6)); // 34 把 6 进制转换为 10 进制

// 在指定了进制以后可以把前面的 0X 或者 0 去掉了;

console.log(parseInt("ff",16));
console.log(parseInt("77",8));
console.log(parseInt("100101",2));
console.log(parseInt("54",6));
1-5-3 parseFloat() 函数;
// 1,parseFloat() 只能识别十进制,16 进制会始终转换成 0;

console.log(parseFloat("123abc")); // 123;
console.log(parseFloat("123.56abc")); // 123.56;
console.log(parseFloat("0xaa")); // 0;
console.log(parseFloat("22.5")); //22.5;
console.log(parseFloat("0909.5")); //909.5;
console.log(parseFloat("3.125e6")); //3125000;
console.log(parseFloat("0700")); //700;

7,转换为字符串

// 1,要把值转换成字符串有两种方式;第一种是使用几乎每个值都有的 toString()方法;该方法会返回相应值得字符串;

var age = 18;
var ageString = age.toString();
var found = true;
console.log(found); // ”True” 实际上调用了 found.toString()或 found.valueOf()

// 注:数值,布尔值,对象和字符串值都有 toString()方法,但 null 和 undefined值没有这个方法;
// 2. 可以为 toString()制定一个可选的基数(radix)的参数(前提是对象是数字类型),默认情况下,toString()方法以十进制格式返回数值的字符串表示;通过设置基数,可以输出其他进制的字符串;

var age = 18;
console.log(age.toString(2)); // "10010";
console.log(age.toString(8)); // "22"
console.log(age.toString(16)); // "12"
// 3,第二种方法是 String()函数,这个函数可以把任意类型的数值转换为字符串,包括 null 和 undefined;
console.log(String(null)); // "null"
console.log(String(undefined)); // "undefined"
// 4,如果要把某个值转换为字符串,可以使用加号操作符把他与一个空字符串拼接;
var str = 10 + "";
console.log(typeof str)
1-5-4 Boolean()类型;
// 1,使用 Boolean(),会返回一个 Boolean 值,但返回 TRUE 或者 false,取决于要转换值得数据类型及其实际值;
// 2,一元“!”运算符也将其操作数转换为布尔值并取反,这是在代码中进行这种类型转换的常用办法;

var str = "w";
console.log(!!str); //相当于 boolean(str)

1-6 检测类型

1-6-1 typeof
// 1,由于松散的数据类型,可以用 typeof 方法来检测数据类型(返回结果都是字符串);会返回一下值;
Undefined; boolean; string; number; object; null;

console.log(typeof null); // object 比较特殊,把他看成是空对象
1-6-2 instanceof
// Typeof 在检测引用类型的数据时用处不大;想要确认一个对象具体是什么类型的对象,就需要 instanceof 操作符;

var str = "w";
var o = new Object();
console.log(o instanceof Object);//  true
console.log(o instanceof String); // false
console.log(str instanceof Number);//  false

// 相当于判断,返回值布尔类型的;
console.log(typeof null); // object;
console.log(null instanceof Object); //false 注意区分

说明:根据规定所有引用类型的值都是 object 的实例;因此,在检测一个引用类型值和 Object 构造函数时,instanceof 操作符始终返回 TRUE;

var color = new Array();
console.log(typeof color); // object;
console.log(color instanceof Object); //true;
console.log(color instanceof Array); //true;

// 使用 instanceof 操作符在检测基本类型的值,会返回 false,因为基本类型不是对象

1-7 表达式

// 1,”wangwei”; “name”; 1.23; var; name; name; 都算一个表达式;
// 2,简单表达式(原始表达式,比如一些常量,变量) 复杂表达式(有简单表达式组合一起的表达式)

1-8 运算操作符

// 1,算数运算符:+ - * / %(取余数) ++ --;
// 2,比较运算符:== !=(不等) > >= < <= ===(全等) !==(不全等);
// 3,条件运算符:? : ;
// 4,逻辑运算符:&&(和) ||(或) ! (非);
// 5,位操作符:~ <<左移位 >>右移位 >>>无符号右移 &按位与 ^按位异或|按位或
// 6,字符元算符: + ;
// 7,赋值运算符: = += -+ *= /= %= &= ^= |= <<= >>= >>>=;
// 8,其他操作符:delete typeof void instanceof in(测试属性是否存在),逗号,忽略第一个操作数,返回第二个操作数。
1-8-1 运算符优先级

image-20240130231555090

1-8-2 操作数及个数
// 1,运算符所操作的数据就是操作数,可以位于运算的左右两边或者一遍;
// 2,运算符可以根据其操作数的个数进行分类:一元操作数,如:“+”,二元操作数,如:“*”,三元操作数,如:“?”;
1-8-3 操作数类型和结果类型

有一些运算符可以作用于任何数据类型,但在真是场景中,一般都是有确定的数据类型,并且大部分运算符都会计算并返回一个特定类型的值;

1-8-4 算数运算符
console.log("1"+2); //12 (加法当中字符串中的数字不会自动类型转换)
console.log(1+{}); //1Oobject
var sum = "1" + 2;
console.log(sum); // 12
console.log(+"2" + 3); // 5
console.log(Number("3")+2) // 5
console.log(true + true); // 2
console.log(1 + true); // 2
console.log(1 + null); // 1
console.log(1 + undefined); // NaN

1,考虑到加法的结合性,既运算结果依赖于运算的顺序;

console.log(1 + 2 +"wang"); //"3wang"
console.log(1 + "wang" + 2); //"1wang2"
console.log("wang" + 2 + 1); //"wang21" (都是从左右的顺序计算);

2,连接字符串,对于 null 和 undefined,则分别调用 String()函数并取得字符串”null”和”undefined”;

console.log("wang" + null); //"wangnull"
console.log("wang" + undefined); // "wangweiundefined"
console.log("wang" + String(null)); //"wangnull"
console.log("wang" + String(undefined)); // "wangweiundefined"

3,减法:如果一个操作符是字符串,布尔值,null 或 undefined,则先在后台调用 Number()进行转换,在进行转换运算;

console.log(8 - true); // 7
console.log(8 - "true"); // NaN (注意“TRUE”引号引起来就是字符串)
console.log(8 - Number(true));
console.log(8 - null); // 8
console.log(8 - {}); // NaN
console.log(8 - "3"); // 5
console.log(8 - undefined); // NaN
console.log(Number("true")); // NaN

4,如果一个操作数是对象,数值,布尔值,则调用他们的 toString()或 valueOf()方法取得相应的字符串值,在进行计算;

console.log(8 - {toString(){return 2}}); // 6

5,其他运算

console.log(5 - true); // 4
console.log(NaN - 1); // NaN
console.log((5 - "")); // 5
console.log(5 - "2"); // 3
console.log(5 - null); // 5
console.log(-0 + 0); // 0
console.log(-0 - 0); // -0
console.log(Infinity-Infinity); //NaN
console.log(-Infinity - Infinity); //-Infinity

6,乘除操作符:如果操作数是非数值,会执行自动类型转换(既后台用 Number()函数转换),空字符串被当做 0; 其余遵循以下规则

// 1,如果计算结果超出范围 返回 Infinity -Infinity;
// 2,有一个操作数是 NaN 返回 NaN;
// 3,Infinity 与 0 相乘 返回 NaN;
// 4,Infinity 与非零相乘 返回 Infinity -Infinity;
// 5,Infinity 与 Infinity 相乘 返回 Infinity;
// 6,0 被 0 除 返回 NaN;
// 7,非零的有限数被 0 除 返回 Infinity -Infinity;
// 8,如果 Infinity 被任何非 0 除 返回 Infinity -Infinity;
console.log(5 / 0); //Infinity
console.log(-5 / 0); // -Infinity
console.log(0 / 0); //Nan

7,取模

console.log(10 % 3); // 1
console.log(6.3 % 2.1); //2.09999999999999996
// 注:小数取模会出问题;

8,一元操作符 ++ 自增 – 自减

// 1,分为前置 ++a 后置 a++
// 2,前置操作时,变量的值都是语句被求值之前完成的(先完成变量本身自增,自减,在计算整个语句);

var age = 18;
var anotherage = --age + 2;
console.log(age); //先算 --age 再算 anotherage 17;
console.log(anotherage); // 19
// 3,后置操作时,先算整个语句再考虑变量本身的自增,自减

var age = 18;
var anotherage = age-- + 2;
console.log(age); //先算 another 再算 age-- 17;
console.log(anotherage); // 20
var num1 = 2;
var num2 = 20;
var num3 = num1-- + num2;
console.log(num3) //先算 num3 再算 num1-- 22;
console.log(num1); // 1
// 4,包含数字的字符串会进行自动转换; ++不会进行字符串的拼接;

var x = "1";
console.log(++x); //2 也把 x 的数据类型改变了 x=2
console.log(x + 5); // x=x+5 7
var x = "1";
console.log(x + 5); // 进行字符串拼接 “15”;
console.log(++x); //2 也把 x 的数据类型改变了 x=2

5,在应用于对象时,先调用对象的 valueof 方法;如果为 NaN,则再调用toString 方法再应用;

var s1 = "2", s2 = "z" , b = false , f = 1.1 ;
o ={
valueOf:function(){
return -1;
}
}
console.log(s1++); // 2 同时 s1 变成 3;
console.log(s1); // 3
console.log(s2++); //NaN 同时把数据类型也改了;
console.log(s2); //NaN
console.log(b++); //0 同时 b 变成 1;
console.log(b);
console.log(f--); //1.1 同时 f = f-1;
console.log(f);
console.log(o--); // -1 同时 o 变成 -2;
console.log(o); //-2

6,一元加和减操作符;(可以把操作数转换成数字或 NaN)而对象先调用valueOf 和 toString 方法

var s1 = "2", s2 = "z" , b = false , f = 1.1 ;
o ={
    valueOf:function(){
    	return -1;
    }
}
console.log(+s1); // 2
console.log(+s2); //NaN
console.log(+b); //0
console.log(+f); //1.1
console.log(+o); -1

1-9 关系比较运算符

1-9-1 > < <= >=
// 1,关系运算符用于测试两个值的关系,根据他们的关系返回 TRUE 或 false;通常应用在 if,while 等控制语句中;
// 2,如果两个操作数都是字符串,则比较他们的大小;
// 3,如果一个是数值,则将另一个转换为数值,再比较;
// 4,如果一个操作数是对象,则调用 valueOf()或 toString()方法,得到结果再进行比较;
// 5,如果是布尔值,将其转换为数值再进行比较;
// 6,Infinity 比任何数字都大(除了其本身);-Infinity 比任何数都小
// 7,任何操作数与 NaN 进行比较,结果都是 false;
// 8,被认定为 false 的值:undefined,null,NaN,” ”,0,false
var o = {};
console.log(o);
o = {valueOf:function(){return 3}};
console.log(3 > 2);
console.log(3 >= 5);
console.log("a" > "b");
console.log("3" > 2);
console.log(o > 2);
console.log("a" > 5);
console.log("a" > "A");
console.log(NaN != "a"); //true
console.log(NaN > 3); //false
console.log( NaN == NaN); //false
// 9,大小写转换的方法;
console.log("abcdef".toUpperCase());
console.log("ABCDEF".toLowerCase());
1-9-2 相等和不相等 == !=

1,这两个操作数都会先转换操作数(通常称为强制转换),然后再比较;

console.log(2 == 2);
console.log(2 == 3);
console.log("w" == "w");
console.log("2" == 2); // true
console.log(true == 1);
console.log(2 != 3);

Null 和 undefined 是相等的;

console.log(null == undefined); //true
1-9-3 全等和不全等 === !==

(不进行类型转换)

console.log( "19" === 19); //false
console.log(+0 === -0); //true
var o1 = {};
var o2 = o1;
console.log(o1 === o2); //true

var x = NaN;
console.log(x !== x); //true 只有 x 等于 NaN 时才成立;

Null 和 undefined 是相等,但不是全等

console.log(null === undefined); //false
console.log(null == undefined); //true
1-9-4 条件运算符
var num1 = 10, num2 = 20
var max = num1>num2 ? num1: num2;
console.log(max); // 求绝对值;

基本语法:

// 表达式? 返回值 1:返回值 2;
// 如果表达式为真则返回 返回值 1 ,若表达式为假。则返回 返回值 2;

var x = -5;
console.log(x > 0 ? x : -x); //求绝对值;

var a = 10, b = 15;
console.log(a == b ? true:false);

判断某个变量有没有定义

var a,b;
a = "aini";
console.log(a? a:"未定义"); // "aini"
console.log(b? b:"未定义"); //"未定义"

1-10 位操作符

// 1,计算值负数:正数绝对值二进制取反 加一 如求 -18 先求 18 的二进制 10010取反以后是 01101 加一以后 01110 所以-18 二进制就等于 01110;再加一个符号位就是 101110
// 2,按位非 ~ 按位与& 按位或| 按位异或^ 
// 3,按位非 返回数字的反码;
var num = 25; // 25 = 11001 -25 = 00110 +00001 = 00111
var num1 = ~num; //反码 00110 所以反码是 00110 = 00111 - 00001 = -25 - 1
console.log(num1); // -26 所以反码是 00110 = 00111 - 00001 = -25 - 1 = -26

4,按位与,有两个操作符,相同位数对齐比较 全 1 出 1,有 0 出 0;

var sum = 25 & 3; // 11001
console.log(sum); // 00011 00001 = 1

5,按位或,有两个操作符,相同位数对齐比较 有 1 出 1,全 0 出 0;

var sum = 25 | 15; // 11001
console.log(sum); // 01111 11111 = 31

6,按位异或,相同返回 0,不同返回 1;

var sum = 25 ^ 15; // 11001
console.log(sum); // 01111 10110 = 22

7,c = a<<b 左移;c = a 的二进制数左移 b 位后的结果,也等于 c = a * 2^b;

var sum = 3 << 3; // 11 左移 3 位就是 11000 = 24
console.log(sum); // 24

8,c = a>>b 右移(有符号的右移>>,正数补 0,可以忽略,负数补 1,需要注意,无符号右移>>>,只会用 0 来填补);c = a 的二进制数右移移 b 位后的结果,也等于 c =a / 2^b;

var sum = 64 >> 3; // 1000000 右移 3 位就是 0001000 = 8
console.log(sum); // 8

1-11 赋值运算符

1,赋值运算符,有很低的优先级;

var a,b = 0;
console.log((a = b) == 10); //因为 = 优先级很低,一般需要用括号括起来,来提高计算的优先级

// 赋值运算符的结合性是从右到左;
var j = k = i = 0; // i=0,k=i, j=k 由于 k,i 不是用 var 声明,所以属于全局白变量;

2,如果等号前面添加了其他操作符就可以完成复合赋值操作;

+=,-=,*=,/+,%=,>>=,<<=,>>>=,&=,^=,|=

num = 10;
num+=10; //num = num + 10
console.log(num);

// += 可用于字符串
var str = "aini";
str += "&AINI";
console.log(str);

// 有时候两者有区别,指向的不是同一个元素;会出现问题
var date = [1,2,3,4];
var i = 1;
// date[i++]*=2; // date[1] = date[1]*2=4 [1,4,3,4]
date[i++] = fate[i++] *2 // [1,6,3,4] date[1] ->i=2 ->date[2] 3*2=6 最终
date[1]=date[2]*2=6
console.log(date);

1-12 其他运算符

1-12-1 typeof

两种语法 typeof value / typeof(value);

console.log(typeof 1);
console.log(typeof true);
console.log(typeof undefined);
console.log(typeof NaN);
console.log(typeof null);
console.log(typeof "aini");
console.log(typeof function(){});
1-12-2 instanceof

不能判断基本数据类型;

var num = 1;
console.log(num instanceof Number); // false 不能判断基本数据类型;
var num1 = new Number(1); // 基本包装类型
console.log(typeof num1); // object
console.log(num1 instanceof Number); //true
console.log(num1 instanceof Object);

var str = new String("aini");
console.log(typeof str); // object
console.log(str instanceof String); // true
console.log(str instanceof Object); // true
var colors = new Array();
console.log(typeof colors);
console.log(colors instanceof Array);
console.log(colors instanceof Object);
1-12-3 in 运算符

in 运算符希望它的操作数是一个字符或者可以转换为字符,右操作数是一个对象;如果该对象拥有一个名为左操作符的属性名,则表示返回 TRUE;

var point = {x:1,y:1};
console.log(1 in point); // false
console.log("x" in point); // true
console.log("z" in point); // false
console.log("toString" in point); //True 所有对象都继承了 Object 的 toString 方法
var date =[1,2,3];
console.log("0" in date); // 能自动转换
console.log(1 in date); // 判断索引
console.log(3 in date); // false
1-12-4 delete 运算符

能删除对象的方法和属性;

var point = {x:1,y:1};
delete point.x;
console.log(point); // {y: 1}
var date =[1,2,3];
delete date[1];
console.log(date); // (3) [1, 空, 3]
1-12-5 逗号运算符

1,一次性声明多个变量需要逗号隔开;

2,逗号运算符在一条语句中执行多个操作;在用于赋值的时候,逗号操作符总会返回表达式中的最后一项;

var a = (1,3,5,7,9);
console.log(a); // 9

3,其首先计算左操作数,再计算右操作数,最后返回右操作数的值;

var num = (i =2 , j =3, k =i+j);
console.log(num); //5

2,流程控制

2-1 前言

1,表达式语句

// 1,具有副作用的表达式是js中最简单的语句;如赋值语句str = “zero” + “network”;
// 2,递增递减运算也是一种表达式;
// 3,Delete 运算符非作用是删除一个对象的属性,所以他也是语句,而不是表达式;
// 4,函数调用是表达式语句中的另一个大类;
3 + 2; //表达式
a = 2; // 语句
alert("wangwei"); //语句
console.log(x = Math.cos(60)); //语句

2 复合语句和空语句

复合语句和空语句,可以把多条语句联合在一起,形成一条复合语句;使用花括号将多条语句括起来,也称为代码块;

{
var x = Math.PI;
var cs = Math.cos(x);
console.log("cos(PI) = " + cs);
}

// 注:语句块结尾不需要分号;

空语句:允许包含 0 条语句的语句,其实用分号;表示;Js 解释器在执行空语句时不会执行任何动作,但某些情况下,空语句还是很有用的;

var a =[];
for(var i = 0 ;i < 10;a[i++]=i);
console.log(a);

3,声明语句 var function
4,顺序结构 ES 程序就是一些列可执行语句的集合;默认情况下,JS 解析器依照语句的编写顺序依次执行,这是程序最基本的结构;
5,流程控制结构 : 流程就是代码执行顺序,流程控制就是改变语句的默认执行顺序,使其按一定的方式进行;
1,条件(condition) 语句
2,循环语句(loop)
3,跳转语句(jump) 如 break,return,throw 语句

2-2 条件语句

2-2-1 if 条件
2-1-1 形式一

if(condition) statement;

var a = 10;
if(a == 10) console.log("a is 10");
if("wangwei")  console.log("aini is a good person"); //只要是个真值就行

ES 语法规定,if 关键字和带圆括号的表达式之后必须跟随一条语句,但只能是一条语句,可以分行写,也可以写在一起;如果有多条,可以使用语句块;

var a = 10;
if(a != 10){
	console.log(a);
	console.log(a + 10);
}

注:一般使用 Xnull 或 Xundefined 或简写!X 来判断一个变量是否有效;

var x;
if(!x) x = 12; //如果 x 没有值的话
console.log(x);
2-1-2 x形式二

if else 语句 if(condition)statement1;else tatement2;

多条语句加花括号; 同?:一样

var x = 10;
var y = 15;
if(x == 10){
	console.log("x = 10")
}else{
	console.log("x != 10")
}

if(y==10) console.log("y = 10")
else console.log("y != 10");
2-1-3 形式三

if-else if 语句 多条件语句

var grade = 41;
if (grade>=80 & grade<=100)
console.log("良好");
else if(grade>=60)
console.log("及格");
else if(grade>=40)
console.log("不及格");
else
console.log("0");
2-1-4 if 语句嵌套
var a = 30;
if(a >= 0 & a<=10){
if(a>=0 & a<=5){
	console.log("a 在 5 之内");
}else{
	console.log("a 在 6 到 10 之内");
}
}else if(a >10 & a<=20){
    if(a>10 & a<=15){
    	console.log("a 在 11 到 15 之内");
    }else{
    	console.log("a 在 16 到 20 之内");
}
}else{
	console.log("a 大于 20");
}
2-2-2 Switch 语句
// 语法:
Switch(expression){
	Case value 1:statement ; 
    break; 
    case value 2: statement;
	break;
    default :statement;

}
var n = 2;
switch(n){
case 1:
console.log(1);
break;
case 2:
console.log(2);
break;
case 3:
console.log(3);
break;
case 4:
console.log(4);
break;
default:
console.log("other");
        
// 本质来讲,case只是指明了程序运行的起点,只要找到复合的case语句,就开始进行,直到程序结束
// 每一个case语句里加break;不然找到了第一个匹配的值以后就会继续往下算,直到程序结束
// Default 是当所有case不匹配时才运行,一般放到最后面
var n = 2;
    switch(true){
      case n > 0:
        console.log("大于0");
        break;
      case n = 0:
        console.log("等于0");
        break;
      case n < 0:
        console.log("小于0");
        break;
    }

// 如果把所有可能性已经囊括了,就不需要写default
// Case后:具体的一个值,运算,返回,常量,变量
// Case语句在比较值是用的是全等操作符,因此不会进行类型转换;所以字符串”10”不等
var n = 1;
      switch(n){
        case 1:
        case 2:
          console.log(2);
          break;
        case 3:
          console.log(3);
        case 4:
          console.log(4);
          break;
      }

image-20240131001119418

如果在函数中使用Switch,可以使用return代替break,两者都用于终止Switch语句,也会防止一个case语句块执行完后继续执行下一个case语句块;

function convert(x){
      switch(typeof x){
        case 'number':
          return x.toString(16);
        case 'string':
          return '"' + x +'"';
        default:
          return "other";      
      }
    }
    console.log(convert(18));
    console.log(convert("aini"));
    console.log(convert(true));

// Return会返回值并直接退出函数
var str = "world";
    switch("hello world"){
      case str:
        console.log(str);
        break;
      case "world":
        console.log("world");
        break;
      case "hello " + str:
        console.log("hello " + str);
        break;
      default:
        console.log("other");
    }

image-20240131001257224

 switch(true){
      case "wangwei":  //这是真值,但不是布尔的TRUE,也不会进行自动转换,所以不成立
        console.log("wangwei");
        break;
      case 1:
        console.log(1);
        break;
case 3 > 2:    //返回的结果是 TRUE ,所以会运行case 3 > 2的情况
        console.log(true);
        break;
      default:
        console.log("other");
    }
 var str = "10";
    switch(str){
      case 10:	
        console.log("这是数字10");
        braak;
      case "10":
        console.log("这是字符串10");
        break;
    }
    
// Case语句在比较值是用的是全等操作符,因此不会进行类型转换;所以字符串”10”不等于数字10

Case 后面建议使用常量,避免使用副作用的表达式

3,循环控制

3-1 while 循环

1,属于前测试循环语句,循环之前判断条件是否成立;

2,语法 while(expression){statement;}

var count = 0;
    while(count < 10){
      console.log(count);
      count++;
    };
 var count = 0;
    var sum = 0;
    while(count <= 100){
      sum += count
      count++;
    };
    console.log(sum);

// while语句里的分号建议加上
var i = 18;
    while(true){
      console.log(i)
      if(i == 18){
        break;
      }
    }
// break语句能结束while循环,跳出循环;有时候为了避免死循环,可以使用

3-2 Do while 循环

1,后测试循环,先执行一遍循环,再判断条件是否满足,所以循环至少执行一次;

var i = 0;
    do{
      i+=2;
    }while(i<=10)
    console.log(i);
// 也可以这样用,改变条件的表达式直接写在

var i = 0;
    do{
      console.log(i);
    }while((i+=2) < 10);
// 这样可以先改变条件,再进行循环;相当于前测试循环

function printArray(arr){
      var len = arr.length,i=0;
      if(len == 0){
        console.log("Emprty Array")
      }else{
        do{
          console.log(arr[i]);
        }while(++i < len);
      }
    }
    var arr=["beijing","nanjing","xinjiang","gaungdong"];
    printArray(arr);
<body>
  <div id="mydiv"></div>
  <script>
    var imgArr = ["xm/1.webp","xm/2.webp","xm/3.webp","xm/4.webp","xm/5.webp","xm/6.webp","xm/7.webp","xm/8.webp",];
    var mydiv = document.getElementById("mydiv");
    var i = 0;
    while(i < imgArr.length){
      var img = document.createElement("img");
      img.src = imgArr[i];
      mydiv.appendChild(img);
      i++;
    }
  </script> 
</body>

3-3 for 循环

1,前测试循环语句;已知循环次数情况下使用for循环比较合适;

2,语法 for(初始循环变量;test条件表达式 ;增量表达式){statement}

3,初始循环变量只是循环开始时执行一次

for(var i = 0; i < 10; i++){
      console.log(i);
    }
// 跟上面的for循环等价
 var i = 0;
    while(i < 10){
      console.log(i);
      i++;
    }
 var count = 0;
    while(count++,count < 10){
      console.log(count);
    }
    
// 注:不能把初始化变量放到while括号里面;但是可以放不影响增量的其他变量;
// 从左往右计算,返回逗号运算符最右侧的代码

image-20240131001921963

 var result = 1,i = 1;
    while(i <=8){
      result*=i;
      console.log(i + "的阶乘是:  " + result);
      i++;
    };

简化上面的while循环

 var result = 1,i = 1;
    while(result*=i,  i++,i <=8){
      console.log(i + "的阶乘是:  " + result);
    };
    var result = 1;
    for(var i = 1; i <= 8; i++){
      result*=i;
      console.log(i + "的阶乘是:  " + result);
    }
var count = 10,i;
    for(i=0;i<count;i++)
      console.log(i);
// i可以在外部定义,for循环里面就不用定
var count = 10,i=0;
    for(;i<count;i++)
      console.log(i);
// For循环中,初始化变量,条件表达式,增量表达式都是可选的,但是分号不能省略
var count = 10,i=0;
    for(;;i++)
      if(i<count){
        console.log(i);
      }else{
        break;
      }
 // 这是一个死循环
 var count = 10,i=0;
    for(;;)
      console.log(i);

把初始化放到外部,把条件表达式以及更新表达式放到内部,条件不满足使用break跳出循环

var count = 10,i=0;
    for(;i<count;){
      console.log(i);
      i++;
      }  
      
// 所以可以把初始化和增量放到外部

可以有多个初始化变量,条件表达式,增量表达式,需要用逗号运算符

var sum = 0;
    for(var i=0,j=1;i<10;i++,j++){
      sum+= i*j;
      console.log(i + "," + j);
    }
    console.log(sum);

For循环的执行顺序

for(var i=0,j=console.log("A");console.log("B"),i<10;console.log("C"), i++){
      console.log("aini");
    }

// 顺序:
i=0,j=console.log("A")        console.log("B"),i<10       console.log("aini")    console.log("C"), i++       console.log("B"),i<10      console.log("aini")     console.log("C"), i++        

总之:初始变量语句只运行一次,增量表达式在statement以后再执行

for(var i=0,j=1;i<10;i++,j++){
      document.write(i);
      if(j%4==0){
        document.write("</br>");
      }
    }

3-4 for-in 循环

1,for in 是精准的迭代语句;

2,语法 for(variable in object){statement}

for(var proname in window){
      console.log(proname);
    }
// window是一个内置对象,for in可以把内置对象的所有属性,方法,事件枚举出来
var arr=[];
   for(var i=0;i<10;i++){
    arr[i]=i*10;
  }
    for(var j=0;j<arr.length;j++){
      console.log(arr[j]);
   }
   for(var k in arr){
    console.log("数组中的第" + k + "个元素是: " + arr[k]);
   }

// 把数组中的元素枚举出来,可以用for循环,也可以用for-in语句
// For-in来枚举数组索引
 var str ="hello world";
   for(var a in str){
    console.log(a + "字符:" + str[a]);
   }
   
 // 字符串也可以像数组一样枚举出来
var person = {
      name:"aini",
      age:18,
      walk:function(){
        console.log("aini can walk") 
      }
    }
    for(var p in person){
      console.log(p + ": " +person[p]);
    }

// 可以把对象的属性和属性值枚举出来
// 注:P是属性,person[p]才是属性值数组里面也是同理,索引和值

ESMAScript对象的属性没有顺序,因此用for-in循环出来的属性名的顺序不可预测;如果迭代的对象时null或undefined,ES5之前会抛出错误,在ES5里面不会报错误,也不执行如果object表达式等于一个原始值,这个原始值会转换为与之对应的包装对象;

var num = 18;
    for(var n in num){
      console.log(n);
    }
var str ="hello world";
   for(var a in str){  //这里使用了new String(str) 把原始类型转换为基本包装类型;
    console.log(a);
   }
ar person = {
      name:"aini",
      age:18,
      walk:function(){
      console.log("aini can walk") 	
      }
    }
    var a =[],i=0;
    for(a[i++] in person);
    console.log(a);

// A[i++]在这里既可以当变量,也可以自增,同时对自己赋值

image-20240131004031326

For- in并不会遍历所有的属性和方法,只会枚举可枚举的属性和方法,由js核心语言内置的属性和方法就是不可枚举的,比如toString(),valueOf();

各种循环可以任意嵌套

3-5 跳转

1,可以从一个位置跳转到另一个位置;

2,标签语句:任何语句如果在前面加上标识符和冒号都可以构成标签语句,以便将来使用;

语法: label:statement

 var flag = true;
    one: 
    if(flag){
      console.log("flag");
    }
    start:
     for(var i=0; i<10;i++){
      console.log(i);
    }
    two:
    {
      var num =18;
      num++
      console.log(num);
    } 

// 一般与for,while语句配合使用;既在循环体内使用break和continue退出或跳转到标签外,二者是唯一可以使用标签的语句;
// 标签可以跟变量函数名同名;
// 标签可以嵌套
// 嵌套时标签名不允许跟标签名重复
one: for(var i = 0;i<5; i++){
    two: for(var j = 0; j<5;j++){
      console.log(i+j);
    }
  }

// 嵌套里的标签不允许
// 单独使用break语句可以立即退出最内层的循环或者Switch语句(break语句只能用在循环或者Switch中)
// reak与标签语句配合使用,退出这个循环的同时跳转到标签处结尾处,可以用于跳出多层循环的
var num = 0;
  outermost:
    for(var i = 0;i < 10;i++){
      for(var j = 0; j < 10;j++){
        if(j==5){
          break outermost;
        }
        console.log(i + "," +j);
        num++;
      }
    }
    console.log(num);

image-20240131004317058

var num = 0;
  outermost:
    for(var i = 0;i < 10;i++){
      inner: for(var j = 0; j < 10;j++){
        if(j==5){
          break;
        }
        console.log(i + "," +j);
        num++;
      }
      for(var k = 0;k<5;k++){
        if(k==2){
          break inner;
        }
      }
    }
    console.log(num);

image-20240131004344763

var sumarr = [[1,2,3],[2,"a",4],[4,5,6]],sum = 0;
    compute_sum:
    if(sumarr){
      for(var i=0;i<sumarr.length;i++){
        var row = sumarr[i];
        if(!Array.isArray(row)){
          break compute_sum;
        }
        for(var y = 0; y < row.length; y++){
          var cell = row[y]
          if(isNaN(cell)){
            break compute_sum;
          }
          sum+=cell;
        }
      }
      console.log("全部执行完毕");
    }
    console.log(sum);

3-6 continue 语句

// Continue语句与break语句非常相似,区别在于他不在退出循环,而是结束本次循环,继续下次循环,其与break用法基本一致
// Continue只能用在循环体当中
for(var i = 0;i<5; i++){
      if(i == 3) continue;
      console.log(i);
    }
// 遇到3结束本次循环,开始下次
var date = [1,2,undefined,4,NaN,8,9,null]; sum = 0,error = 0;
    for(var i = 0; i<date.length;i++){
      if(!date[i]){
        error+=1;
        continue;
      } 
      sum+=date[i];
    }
    console.log(sum);
    console.log(error);
// Continue可以与标签配合使用,就是跳出外侧循环,继续进行外侧循环的下一次循环

3-7 return 语句

// Return语句只能在函数中使用,既函数调用后的返回值;
// 如果没有return语句,则程序返回的结果就是undefined
// Return默认返回值也是undefined
function display_object(o){
      if(!o) return ;
      for(p in o){
        console.log(p);
      }  
      return;
    }
    var person ={
    name:"aini",
    age:18,
    city:"xinjiang"
  }
  console.log( display_object(person)) ;

3-8 with 语句

// 为了简写代码
With(object){statement}
 var person = {
      name:"aini",
      age:18,
      city:"xinjiagn"
    }
    console.log(person.name);
    console.log(person.age);
    console.log(person.city);

// 可以这样简写代码
    with(person){
      console.log(name);
      console.log(age);
      console.log(city);
    }

3-9 Debugger语句

// 什么也不做,用于调试,不怎么重要,会在debugger语句的地方产生一个断点

3-10 “use strict” 语句

// 让js代码在严格模式下执行

4,函数

4-1 定义函数

4-1-1 第一种方式
// 第一种方法:函数声明式,关键字function定义;

image-20240131111239498

image-20240131111246269

4-1-2 第二种方式
// 第二种方法:function实际上也是对象,所以可以使用function对象的构造函数来创建一个函数;
// Var 变量名 = new Function(形参1,形参2,代码体);

image-20240131111352811

var date ="var num =prompt('input');console.log(num);"
var date1 ="alert('aini');"
var date2 ="var p=fname:'aini',age:19};console.log(p.name)";
var fun =new Function(date2);
fun();
var scope ="global";|
function func(){
	var scope ="local";
	var myFun =new Function("return scope;");
	console.log(myFun());
}
func();

// 注:只能认全局变量,在函数中的局部变量识别不了;所以上述例子返回“global”;内部的“local”识别不了;
// 注:1,参数,代码体必须使用引号引起来,代码体放在最后一个引号里,语句和语句之间分号引起来;
// 2,如果没有参数则省略,只有代码体的引号;
4-1-3 第三种方式

可以直接把定义函数保存在变量中,该变量名实际上就是函数

// Var 变量名 = function(参数1,参数2){表达式语句}
var mysum =function(num1,num2){return num1+num2;
console.log(mysum(10,20));

由于函数名仅仅是函数的指针,所以可以把函数名赋值给多个变量,这么说一个函数可以有多个名字

<script>
function sum(num1,num2){
	return num1 +num2;
}
mysum =sum;
anothersom =sum;
console.log(mysum(10,20));
console.log(anothersom(30,50));
</script>

把函数名赋值的时候不能带括号,带括号的话立马就执行了

<script>
var sayworld;
var condition =false;
if(condition){
	sayworld =function(){console.log("aini")};
}else{
	sayworld =Function()fconsole.1og("AINI")};
}
sayworld();
</script>

函数声明和函数表达式的区别

// 解释器在执行环境中加载数据时,会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于表达式,则必须等解析器执行到它所在的代码行,才会真正被解析;
sayHi();
function sayHi(){
	console.log("hi");
}
// Function函数声明的时候,访问可以放在函数之前,也可以放在函数后面
sayHi();
var sayhi =function(){console.log("Hi");

image-20240131111942304

// 通过表达式定义的函数,访问必须在定义之后,不然会出错
4-1-4 定义函数的三种方式
// 1,函数声明;
// 2,利用Function类构造函数;
// 3,通过函数表达式构造函数
// 函数的调用:可以在代码中调用,也可以在事件中调用,还可以通过链接调用;
<script>
function sum(a,b){
	return a+b;
}
console.log(sum(10,20));
var sum1 =function(a,b){
	return a +b;
}
console.log(sum1(20,30));
var fun =new Function("a","b","return a+b");
console.log(fun(50,100));
</script>
// 1,如果调用函数时输入的实参的个数大于形参的个数,则忽略多余的实参;
// 2,实参的个数小于形参,则没有接受传递值得参数赋予undefined;

// Return返回值:只要执行带return就会返回,并退出,后面的代码永远不会执行;return只能用在函数当中

// Return 也可以不带任何返回值,在这种情况下函数就会停止运行并返回undefined

// 函数中不用return也会返回undefined
4-1-5 一些例子

image-20240131114628074

image-20240131114634613

5,JavaScript 引用类型

// 本地对象:独立于宿主环境的由ECMAscript实现提供的对象;如object,Function,Array等;
// 内置对象:由ECMAscript实现提供的,独立于宿主环境的所有对象,如Global和Math(本质上是静态的,就不需要创建对象);
// 宿主对象:所有非本地对象都是宿主对象,既由scmascript实现的宿主黄江提供的对象,如多有BOM和DOM对象丢失宿主对象;

5-1 object 类型

5-1-1 创建
// 创建object实例:两种方法;

1,var person = new Object();
2,Var person = {};  (属性名可以是字符串,用引号引起来,属性名中的空格无法识别);
3,数值属性名会自动转换成字符串,但是用方法不同;

image-20240131115032239

ar myo = {};    // 是一个空对象,只包含默认的属性和方法;
Var myo = new Object(); // 两者可以等同;

image-20240131115103444

为对象添加方法

image-20240131115150089

// 注:字面量对象花括号里面用逗号
// 类创建对象里面的类里面使用分号
5-1-2 对象属性
// Object对象属性是动态的

// 两种访问方式:
    1,使用点访问如person.name;
    2,使用方括号表示法,属性应以字符串的方式放在括号中;

image-20240131115406231

// 注:如果属性名中包含会导致语法错误的字符,或者属性名使用的是关键字或者保留字,就可以使用方括号访问法;

image-20240131115437683

5-1-3 动态添加属性和方法

image-20240131115540256

5-1-4 Object类及对象的属性和方法
4-1 constructor 属性

对创建对象构造函数的引用;如:

image-20240131115621167

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4-2 prototype 属性

对对象的原型的引用

image-20240131115647697

4-3 obj.hasOwnProperty(property)

判断该对象是否具有某个属性

image-20240131115833889

4-4 Object.prototype.isPrototypeOf(object)

判断该对象是否为另一个对象的原型;

image-20240131115931698

// 就是判断obj是不是由object创建出来的

image-20240131115947308

// 结果是TRUE,因为p就是由person创造出来的
4-5 Obj.propertyIsEnumerable(property)

判断给定的属性是否可以用for in 枚举;

image-20240131120036827

4-6 obj.toString()

返回对象的原始字符串表示

4-7 obj.valueOf() :

返回最适合该对象的原始值

5-2 其他引用类型

1,数组  var arr = new Array(1,2,3,4,5);
2,字符串 var str = new String(“aini”);
3,真则表达式 var pattern = new RegExp(“w” , “i”);

5-3 对象的废除

// 一个对象不使用,应该废除,释放内存;JS自带垃圾回收程序;
// 强制销毁:object = null;这个对象就不存在了;
// 必须把所有对象的所有引用都设为null,对象才会被清除;
// Delete操作符删除不掉对象,但是可以删除对象的属性,对象的引用

image-20240131120320024

5-4 基本引用类型和引用类型的值

// 对象的比较并非值得比较,即使两个对象包含相同的属性和相同的值也并不相等

image-20240131120404340

// 对象的比较是引用的比较,当且劲当他们引用同一个对象时,他们才相等;

image-20240131120429650

5-5 引用类型的复制

image-20240131120454118

判断两个对象是否有相同的属性值

image-20240131120526048

image-20240131120537937

6,JS基本包装类型

// ES提供了三个基本的引用类型:Booleanj ,Number , String;
// 但是同时还具有与各自的基本类型相应的特殊行为;
// 实际上,每当读取一个基本类型的值得时候,后台就会创建一个对应的基本包装类型的对象,让我们用一些方法来操作这些数据

image-20240131120631951

// 后台默认给s1创建了一个String对象,是临时的一个对象,我们是看不见的再调用了他的substring()方法,调用完了以后再把这个临时对象销毁
// 后台默认给s1创建了一个引用类型,并调用了他的substring()方法赋给s2;

基本包装类型的实例调用typeof 会返回object,而且所有包装类型的对象转换为布尔值类型都是TRUE

image-20240131120842968

Object构造函数,仅接受一个参数;其会根据传入的值得类型返回相应包装类型的实例;

image-20240131120949400

ES在必要时会将包装类型转换成原数值;如:

image-20240131121013287

实际上比较的是他们的值,基本包装类型用valueOf方法返回他的值再进行比较,所以两者相等;

注:null和undefined没有包装对象,访问他们的属性会造成一个类型错误;

6-1 Boolean 类型

// 1,Boolean类型是与布尔值对应的引用类型,Boolean 对象表示两个值:"true" 或 "false"。
// 2,创建Boolean对象:var oBool=new Boolean(true); // 传入true或false值
// 注:如果逻辑对象无初始值或者其值为 0、-0、null、""、false、undefined 或者 NaN,那么对象的值为 false,否则,其值为 true;

image-20240131121148554

// 不判断对象的值,而是判断这个对象存不存在,所以返回TRUE;

image-20240131121216488

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

// 总结:理解基本类型的布尔值与Boolean对象之间的区别非常重要,建议不要使用,最好使用Boolean原始值;
// 可以使用Boolean(参数)进行数据类型转换;

6-2 Number 类型

// 1,Number 对象,是原始数值的包装对象。在必要时,JavaScript 会自动地在原始数据和对象之间转换;
// 2,可以用构造函数 Number() 明确地创建一个 Number 对象:

image-20240131121350159

6-2-1 对象属性
// 对象属性:

1MAX_VALUE: // 表示最大的数,静态属性;
2MIN_VALUE: // 表示最小的数,静态属性;
3NaN: // 非数字值;静态属性;
4NEGITAVE_INFINITY:// 负无穷大,溢出时返回该值,静态属性;
5POSITIVE_INFINITY:// 正无穷大,溢出时返回该值,静态属性;

image-20240131121438242

一般用于判断表达式
// 注:Number.NaN 是一个特殊值,说明某些算术运算(如求负数的平方根)的结果不是数字。
// 方法 parseInt() 和 parseFloat() 在不能解析指定的字符串时就返回这个值。
6-2-2 对象方法
Number类或对象方法:
1,parseInt()parseFloat()方法,静态方法;
2,isNaN()//  来判断是否为数字,静态方法;
3,isFinite:// 是否为有限,静态方法;
4,isInteger:// 是否为整形,静态方法;
5,toString()方法: // 如:NumberObject.toString(radix),radix可选,规定表示数字的基数,使 2 ~ 36 之间的整数,若省略该参数,则使用基数 10。

image-20240131121606771

6,tolocaleString() :// 把一个Number对象转换为本地格式的字符串
7,toFixed() 方法:// 把 Number 四舍五入为指定小数位数的数字,类型是字符串;参数规定了小数的位数,是 0 ~ 20 之间的值;有些实现可以支持更大的数值范围。如果省略了该参数,将用 0 代替

image-20240131121949098

8,toExponential()方法 // 可把对象的值转换成指数计数法。如:NumberObject.toExponential(num) ,num规定指数计数法中的小数位数,是 0 ~ 20 之间的值;

image-20240131122020511

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

// 用指数计数法表示,而且还要保留指定的小数位数
9,toPrecision() // 可在对象的值超出指定位数时将其转换为指数计数法;其可能会返回固定大小格式,也可能返回指数格式,具体规则是看哪种格式最合适;如:NumberObject.toPrecision(num) ,num规定必须被转换为指数计数法的最小位数,该参数是 1 ~ 21;

image-20240131122335501

// 就是小数点后或者整数位加起来就等于指定的位数,若不满足可以用科学计数法来凑

6-3 String 对象

String类型是字符串对象包装类型,用于处理文本(字符串)。语法:var str = new String(str); 如

image-20240131122427735

// 上面两个是对象
// 下面是强制类型转换的字符串;
6-3-1 字符串属性
1-1 length属性

返回字符串中的字符个数 如: str.length (汉字也是一个字符)

6-3-2 字符串操作方法
2-1 charAt() charCodeAt()

都接受一个参数,是基于0的字符位置;其中charAt() 方法以单字符串的形式返回给定位置的那个字符;

image-20240131122629891

如果想得到字符编码而不是字符时,使用charCodeAt(),如:

image-20240131122704822

也可以用方括号来访问字符串,向数组一样

image-20240131122724061

2-2 concat()

用于将一或多个字符串拼接起来,返回拼接得到的新字符串,如:

image-20240131123223317

concat()方法可以接受多个任意参数,即可以通过它可以拼接任意多个字符串,如:

image-20240131123241882

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2-3 slice(x,y)
// x位置开始截取,到y结束(不包含y位置) 

image-20240131123621086

2-4 substr(x,y)
// x是截取开始的位置,y是截取长度

substr()将负的第一个参数加上字符串的长度,而将负数的 第二个参数转换为0

image-20240131123702352

2-5 substring(x,y)
// x位置开始截取,到y结束(不包含y位置)
// 如果只设置一个参数,则从开始位置截取到末尾

substring()方法会把所有负值参数都转换为0

image-20240131123749534

image-20240131123519832

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

// 同时这些方法的值可以是负值;其中,slice()会将传入的负值与字符串的长度相加,substr()将负的第一个参数加上字符串的长度,而将负数的第二个参数转换为0,substring()方法会把所有负值参数都转换为0,如:
2-5-1 文字滚动效果

image-20240131123950644

6-3-3 字符串位置方法
3-1 indexOf() 和 lastIndexOf()
// 这两个方法都是从一个字符串中搜索给定的子字符串,然后返回子字符串的位置,如果没有找到子字符串,则返回-1

image-20240131124236826

// lastIndexOf从后面往前找,两个方法只返回第一个出现的位置
// 可以设置第二个参数,表示从什么位置开始查找;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在使用第二个参数的情况下,可以循环调用两个办法找到所有匹配的字符串

image-20240131124335762

3-2 trim()
trim()方法:.// 删除前置及后缀的所有空格,然后返回结果;

还有两个非标准的trimLeft()和trimRight()方法, // 分别用于删除字符串开头和末尾的空格

image-20240131124503565

3-3 大小写转换
toLocaleLowerCase() // 把字符串转换为小写;
toLocaleUpperCase() // 把字符串转换为大写;
toLowerCase() // 把字符串转换为小写;
toUpperCase() // 把字符串转换为大写;

// 其中toLowerCase()和toUpperCase()是最常用的方法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

6-3-4 字符串匹配方法
4-1 match()

本质上与RegExp的exec()方法相同;其只接受一个参数,要么是一个正则表达式,要么是一个RegExp对象,如

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

// 说明:match()返回了一个数组,其第一项是与整个模式匹配的字符串,之后的每一项(如果有)保存着与正则表达式中的捕获组匹配的字符串
4-2 search()

接受的参数与match()一样;该方法返回字符串中第一个匹配项的索引;如果没有找到匹配项,则返回-1;如

image-20240131124823113

4-3 replace()
replace()方法:// 替换字符串,接受两个参数,第一个参数是一个查找的字符串或者是正则,第二个参数是要替换的字符串或者函数;
// 注意:如果第一个参数是字符串,则只会替换第一个找到的字符串,想替换所有的话第一个参数可以写个正则;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

// 想要替换全部就加上g(全局标志),不然只会替换第一个如果没匹配上替换不了,就会输出原有的值

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4-4 split
split()方法:// 基于指定的分隔符将一个字符串分割成多个字符串,并把结果放在一个数组中;分隔符可以是字符串,也可以是正则;其可以接受可选的第二个参数,用于指定数组的大小;

image-20240131125033269

image-20240131125038349

image-20240131125045545

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第二个参数可以指定长度

image-20240131125112901

也可以是正则表达式

6-2-5 其他方法
5-1 ocaleCompare()
localeCompare() // 用本地特定的顺序来比较两个字符串,默认返回下列值中的一个:-1、0、1,如

image-20240131125250511

5-2 fromCharCode()
fromCharCode()方法: // String构造函数本身还有一个静态方法:fromCharCode(),这个方法的任务是接受一或多个字符编码,然后将它们转换成一个字符串;从本质上看,这个方法与实例方法charCodeAt()执行的是相反的操作

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

把asccII码转换成对应的字母或数字

5-3 HTML方法
// 早期的Web浏览器可以使用Javascript动态格式化HTML,其扩展了字符串的标准,实现了一些专门用于简化常见HTML格式化任务的方法;但是,尽量不要使用这些方法,因为它们创建的标记通常无法表达语义
anchor(name) // 创建 HTML 锚,输出如:<a name=”name”>string</a>
big() // 用大号字体显示字符串,如:<big>string</big>
small() // 使用小字号来显示字符串,如:<small>string</small>
blink() // 显示闪动字符串;
bold() // 使用粗体显示字符串,如:<b>string></b>
fontcolor(color) // 使用指定的颜色来显示字符串,如:<font color=”color>string</font>
fontsize(size) // 使用指定的尺寸来显示字符串,如:<font size=”size”>string</font>
italics() // 使用斜体显示字符串,如:<i>string</i>
link(url) // 将字符串显示为链接,如:<a href=”url”>string</a>
fixed() // 以打字机文本显示字符串,如:<tt>string></tt>
sup() // 把字符串显示为上标,如:<sup>string</sup>
sub() // 把字符串显示为下标,如:<sub>string</sub>
strike() // 使用删除线来显示字符串,如:<strike>string</strike>

7,Global 对象

// 全局对象及其属性在任何地方都可以直接使用;
// Global对象是对象,不是类,所以没有构造函数,不能new实例化
// 注:所有全局变量也都是全局对象的属性;

这个对象是不存在的,换句话说,不属于任何其他对象的属性和方法,最终都是它的属性和方法; 因此它没有名字(Global不是全局对象的名字,只是我们这样称呼它)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

// isNaN就是全局对象的函数

7-1 全局属性

// 如undefined、NaN以及Infinity都是Global属性;
// ECMAScript5禁止给undefined、NaN和Infinity赋值,这样做即使在非严格模式下也会导致错误。

7-2 全局对象

Math,json

image-20240131130034372

7-3 全局函数

// isNaN(),  isFinite(),  parseInt(),  parseFloat()等
// isNaN()用来确定一个值是否为NaN,而Number.isNaN()确定传递的值是否为NaN和其类型是Number;它是原始的全局isNaN的强大的版本

image-20240131130118557

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

// Number.isFinite()和全局的isFinite()相比,不会强制将一个非数值的参数转换成数值,这就意味着,只有数值类型的值,且是有穷的,才返回true

// 只有为Number的值且是有限的时候Numebr.isFinite()会返回true。而isFinite()在参数经过Number转换后再判断是否是有限的。正无穷、负无穷和NaN都不是有限数字。

// Number.isInteger()用来判断给定的参数是否为整数;只有是Number类型的值并且是整数才会返回true,Infinity和NaN都不是整数。

7-4 全局构造函数

// 全局对象还定义了一些属性,用来引用Javascript预定义的所有其他对象,这些对象大部分是原生引用类型的构造函数:

Object、Number、String、Boolean、Array、Date、Function、RegExp、Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError

// 根据JavaScript的运行环境,在JS中存在两种全局对象:JS全局对象和window全局对象
// 当Javascript放在特定宿主环境时,全局对象通常具有与该特定环境相关的额外属性;这些额外属性并不是ES标准规定的,而是由宿主环境实现的;如在客户端Javascript中,全局对象是Window对象,表示运行JS的Web浏览器窗口;或在nodejs中,Global指的就是global对象;

image-20240131130335852

// 全局中的this:由于全局对象位于作用域链的最顶部,所以在全局环境中,关键字this指的就是全局对象;如果在是浏览器中,this指的就是window对象;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

This 就相当于全局对象的代理人,在浏览器环境中就是window;

在ES中,全局对象的预定义属性都是不可枚举的,因此使用for/in来只能列出所有隐式或显式声明的全局变量,如

image-20240131130442912

7-5 全局变量

所有的全局变量都全局对象的属性

声明的4种方法:
// 1.直接在全局作用域中用var 声明的变量就是全局变量,此种方式声明的变量具有不可配置的属  性,不能使用delete操作符把变量删除。

// 2.window.变量,这种声明的变量也是全局变量,但这种变量跟上面用var 声明的变量有点不一样,这种方式声明的全局变量是可配置的,因此能用delete操作符把变量删除。

// 3.隐式声明全局变量,就是不使用var声明,直接进行赋值的变量,在不严格模式中,相当于window.变量这种方式,但在严格模式下,会报错。

// 4.在html中给标签指定一个id属性,也相当于给Window对象添加了一个id的属性,在javascript中可直接通过标签的id访问该标签(或者window['id'])。

7-6 Global对象的方法

7-6-1 字符编码方法
1-1 Escape() 函数
// escape() 函数可对字符串进行编码,这样就可以在所有的计算机上读取该字符串(其中某些字符被替换成了十六进制的转义序列 )

// 注:该方法不会对 ASCII 字母和数字进行编码,也不会对下面这些 ASCII 标点符号进行编码:- + _ . *
var str ="aini is a good boy"
var a="_,.艾尼!-+";
console.log(escape(str));
console.log(escape(a));

// 转换成以%开头的16进制码
// 如 空格→%20    艾尼 → %u827e  %u5c3c

image-20240131130722023

1-2 unescape() 函数
unescape() 函数
// unescape() 函数可对通过 escape() 编码的字符串进行解码。

// 注:通过找到形式为 %xx 和 %uxxxx 的字符序列(x 表示十六进制的数字),用 Unicode 字符 \u00xx 和 \uxxxx 替换这样的字符序列进行解码。
console.log(unescape("aini%20is%20a%20good%20boy"));

解码方式 :
// 把% 抓换成 \ ,这样以后浏览器就能识别\uxxxx的Unicode编码了
console.log("\u827e"+"\u5c3c");  // 艾尼
7-6-2 URL编码方法
// Global对象的encodeURI()和encodeURIComponent()可把字符串作为 URI(Uniform Resource Identifiers,通用资源标识符)进行编码,以便发送给浏览器;

// 有效的URI中不能包含某些字符,如空格;这两个编码方法,用特殊的UTF-8编码替换所有无效的字符,从而让浏览器能够接受和理解。

// 注:两个方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII 标点符号进行编码: - _ . ! ~ * ‘ ( )
encodeURI()也不会对在URI中具有特殊含义的符号;/?:@&=+$,#进行转义,但encodeURIComponent()// 其中,encodeURI()主要用于整个URI,如:
http://www.zeronetwork.cn/my case.html,而encodeURIComponent()主要用于对URI中的某一段,如my case.html进行编码;

// 它们的主要区别在于,encodeURI()不会对本身属于URI的特殊字符进行编码,如:冒号、正斜杠、问号和井号;而encodeURIComponent()则会对它发现的任何非标准字符进行编码
2-1 encodeURI() 和 encodeURIComponent()
encodeURI()函数 和 encodeURIComponent() 函数
var str="zero!ne work零-_!~'(*&#?";
console.log(encodeURI(str));

image-20240131131103503

var str="zero!ne work零-_!~'(*&#?";
console.log(encodeURIComponent(str));

image-20240131131121850

// encodeURIComponent() 函数 编码范围广一点(用的时候仅仅对URL里面某一部分进行编码)
var url1="https://www.zeronetwork.cn/case.html?user%20name&age=18#one";
console.log(encodeURI(url1));
console.log(encodeURIComponent(url1));

image-20240131131155140

// 所以encodeURICompenent不适合对整个URL进行编码
// 注:一般来说,使用encodeURIComponent()的场景比使用encodeURI()要多,因为在实践中更常见的是对查询字符串参数而不是对基础URI进行编码
2-2 decodeURI() 和 decodeURIComponent()
decodeURI()decodeURIComponent()
// 其中decodeURI() 只能对 encodeURI() 编码过的 URI 进行解码;
// 如:可将%20替换成一个空格,但不会对%23作任何处理,因为%23代表井号,而井号不是使用encodeURI()替换的;
// 同样的,decodeURIComponent()能够解码encodeuricomponent()编码的所有字符,即它可以解
var url1="https://www.zeronetwork.cn/case.html?user name&age=18#one";
console.log(encodeURI(url1));
console.log(encodeURIComponent(url1));

console.log(decodeURI("https://www.zeronetwork.cn/case.html?user%20name&age=18#one"));
console.log(decodeURIComponent("https%3A%2F%2Fwww.zeronetwork.cn%2Fcase.html%3Fuser%20name%26age%3D18%23one"));

image-20240131131323548

// 注:URI方法代替了已经被ECMA-262第3版废弃的escape和unescape()方法,因为URI方法能对所有Unicode编码,而原来的方法只能对ASCII符号正确编码;因此在实际场景中,一定要使用URI方便,不要再使用escape()和unescape()方法;
onsole.log(escape("http://www.zeronetwork.cn/index.html?id=101&username=艾尼"));
console.log(escape("?!=()#%&"));
console.log(encodeURI("http://www.zeronetwork.cn/index.html?id=101&username=艾尼"));
console.log(encodeURI("?!=()#%&"));
console.log(encodeURIComponent("http://www.zeronetwork.cn/index.html?id=101&username=艾尼"));
console.log(encodeURIComponent("?!=()#%&"));

image-20240131131426361

7-7 eval 函数

//只接受一个字符串参数,是正常的JS或者ES代码
// 可以动态执行JS代码
function swapImg(){
  var img = eval("document.getElementById(myimg)")
  myimg.src="images/02.jpg";
}
var myImg=document.getElementById("myimg");
myImg.onmouseover=swapImg;

同样的,也可以在eval()中定义一个函数,在该调用的外部代码中引用这个函数,如:

eval("var msg='hello world';");alert(msg);
// 注:在eval()中创建的任何变量或函数都不会被提升,因为在解析代码的时候,它们被包含在一个字符串中,它们只在eval()执行的时候被创建;
console.log(str);     // undefined
var str = "aini like dilnur";
// 虽然变量声明在后,访问在前;但是str会被提升,但是他的值不会被提升,所以str存在,返回undefined;
show();

function show(){
  console.log("show");
}

// 这里函数也提升了
eval("console.log(str)");
str="aini like dilnur";

// 会报错,str未定义,说明str没有被提升
var s="global";
function func(){
  eval("var s = 'local——';")  // 局部的  在外边不能够访问
  window.eval("var s = 'window.local';")  //全局
}
func();
console.log(s);  // window.local
var x = y ="global";
function f(){
  var x = "local";
  eval("x+='changed';")  //改变的是局部x的值
  console.log(x);
}

function g(){
  var y = "local";
  window.eval("y+='changed';");  //改变的是全局x的值
  console.log(y)
}
f();  //返回函数局部变量 x 的值
g();  //返回局部变量 y 的值,由于没改变 所以返回 local
console.log(x);  //返回全局变量  x 的值
console.log(y);  //返回全局变量 y 的 值

image-20240131131800596

所以说window.eval在函数内能改变全局变量的值;

// 函数内可以访问到全局变量,函数外不能访问函数内的局部变量;当函数内,函数外声明同一个变量时,函数内优先访问函数内的局部变量;

Window.eval()在函数内优先访问全局变量

eval() 当在函数内部作用域中,eval执行的是局部,如果调用window.eval则在全局空间执行

// 注:在严格模式下,ES5对eval()的行为施加了更多的限制,在外部访问不到eval()中创建的任何变量或函数,同时,为eval()赋值也会导致错误

不是严格模式下:

var x = 18;
eval("var y=20;console.log(y)");
console.log(y);   // 外部也能访问

在严格模式下:

"use strict"
var x = 18;
eval("var y=20;console.log(y)");
console.log(y);   // 外部不能访问

7-8 window对象

// ECMAScript虽然没有指出如何直接访问Global对象,但Web浏览器都是将这个全局对象作为window对象的一部分加以实现的;因此,在全局作用域中声明的所有变量和函数,就都成为了window对象的属性
var color="red";
function sayColor(){
  console.log(window.color);   //就可以当window的属性用
}
sayColor();
// 注:JavaScript中的window对象除了扮演ECMAScript规定的Global对象的角色外,还承担了很多别的任务(即window对象下的一些方法和属性都是js原生提供的,但在浏览器环境中,大量的功能都是由宿主对象完成的);另外一种取得Global对象的方法
var global=function(){
  return this;
}();   // 相当于global();

// 定义的同时函数也运行了
// 说明分析:以上代码创建了一个立即调用的函数表达式,返回this;在没有给函数明确指定this值的情况下,this值等于Global对象
var global=function(){
  return this;
}();   // 相当于global();

 var str="aini like dilnur";
console.log(str);
console.log(window.str);
console.log(this.str);
console.log(global.str);

image-20240131132523906

在函数内访问外部变量的方法

var global=function(){
  return this;
}();   // 相当于global();

 var str="aini like dilnur";
 function show(){
  var str="aini";
  console.log(window.str);
  window.eval("str");
  console.log(global.str);
  console.log(this.str);  //容易产生歧义
}
show();

7-9 Math 对象

// Math对象是一个静态对象,或者称为工具类,不能使用new关键字创建对象,应直接使用”对象名.成员”的格式来访问其属性和方法,如: 
var num=Math.random();
7-9-1 math对象的属性
E// 自然对数的底数,即常数e.约等于2.718;
LN10// 代表10的自然对数,约等于2.302;
LN2// 代表2的自然对象,约等于0.693;
LOG2E// 代表以2为底E的对数;
LOG10E// 代表以10为底E的对数;
PI// 代表II的值,约等于3.1415926;
SQRT1_2// 代表2的平方根分之一,约等于0.707;
SQRT2// 代表2的平方根,约等于1.414;
console.log(Math.PI);
console.log(Math.E);
console.log(Math.SQRT1_2);
console.log(Math.abs(-5));
7-9-2 math对象的方法
2-1 min()和max()
// 用于确定一组数值中的最小值和最大值;这两个方法都可以接收任意多个数值参数

var max =Math.max(3,25,45,78,96,54,100);
var min =Math.min(87,45,12,59,87);
console.log(max);
console.log(min);

注:数组中不能用,所以需要借助apply()方法,如:

var arr=[1,25,84,75,95,42,35,68];
var max=Math.max.apply(Math,arr);
console.log(max);
2-2 ceil()
// 向上取整

console.log(Math.ceil(1.1));  // 2
  console.log(Math.ceil(-1.5)); // -1
2-3 floor()
// 向下取整
console.log(Math.floor(1.1));  // 1
2-4 round()
// .5的时候round返回比它大的整数,所以负数有点不一样
// 注:在round里面使用负数时注意:
console.log(Math.round(-1.1));
  console.log(Math.round(-1.5));   //注意中间的
  console.log(Math.round(-1.6));

image-20240131133115476

2-5 Random()

Random()方法:// 随机数;
// 产生[0,1)之间的随机数
console.log(Math.random());

image-20240131133455119

般与其他方法配合使用,以求得一个在一个范围内的随机数

console.log(Math.floor(Math.random()*10+1)); // [1,10]
总结公式:
Math.floor(Math.random()*(max-min+1)+min)

得到一个区间的整数随机数

function intRandom(min,max){
  return Math.floor(Math.random()*(max-min+1)+min);
}
console.log(intRandom(10,100));

数组当中随机拿出一项

var colors=["red","blue","green","black","purple"];
var color=colors[Math.floor(Math.random()*(colors.length))];
console.log(color);

随机返回字符

  var str="ABCDEFGHIJKLNOPQURSTUVWXYZabcdefghijklnopqurstuvwxyz0123456789";
  var a="";
function getRandomStr(x){
    for(var i=0;i<x;i++){
      var random=Math.floor(Math.random()*(str.length-1));
      a+= str.substring(random,random+1);
    }
    return a;
}
console.log(getRandomStr(4));

从中文里面随机取x个字符

// 首先确定汉字编码的范围
4E00 ----------------9FA5
// 16进制转换成10进制
19968 -------------40869

image-20240131134131735

	function getRandomHan(len){
  var str="";
  for(var i=0;i<len;i++){
      c=(Math.floor(Math.random()*(40869-19968+1)+19968)).toString(16);
      c = "\\u" +c;
      c=c.replace(/\\/g,"%");
      c= unescape(c);
      c.replace(/%/g,"\\") //还存在%的用\代替回去;
      str+=c;
  }
  return str;
}
console.log(getRandomHan(4

继续封装,继续简化

	function getRandomHan(len){
  var str="";
  for(var i=0;i<len;i++){
      c=getRandom(19968,40869).toString(16);
      c = decodeUnicode(c);
      str+=c;
     
  }
  return str;
}
console.log(getRandomHan(4))

function getRandom(min,max){
  return Math.floor(Math.random()*(max-min+1)+min);
};
function decodeUnicode(str){
  str="\\u"+str;
  str=str.replace(/\\/g,"%");
  str=unescape(str);
  str=str.replace(/%/g,"\\");
  return str
}

老师课件上的封装

function getRandomHan(len){
    var str = '';
    for(var i=0;i<len; i++){
        var c = (Math.floor(Math.random() * (40869 - 19968) + 19968)).toString(16);
        c = "\\u" + c;
        c = c.replace(/\\/g,"%");
        c = unescape(c);
        c = c.replace(/%/g,"\\");
        str += c;
    }
    return str;
}
console.log(getRandomHan(6));
// 拆分整合
function getRandom(min,max){ // 获取指定范围内的随机数
    return Math.floor(Math.random() * (min - max) + max);
}
function decodeUnicode(str) {  // 解码
   str = "\\u"+str; //Unicode显示方式是\u4e00
   str = str.replace(/\\/g, "%");
   str = unescape(str);     //转换中文
   str = str.replace(/%/g, "\\");   //将其他受影响的转换回原来
   return str;
}
function getRandomChinese(len){
    var str = "";
    for(var i = 0;i<len;i++){
        str += decodeUnicode(getRandom(0x4e00,0x9fa5).toString(16));
    }
    return str;
}
console.log(getRandomChinese(2));
7-9-3 其他函数
abs(num):// 绝对值、
asin(x):// 反正弦值、
exp(num):// Math.E的num次幂、
atan(x):// 反正切值、
log(num):// num的自然对数、
atan2(y,x):// y/x的反正切值、
pow(num,power):// num的power次幂、
cos(x):// 余弦值、
sqrt(num)://平方根、
sin(x):// 正弦值、
acos(x):// 反余弦值、
tan(x):// 正切值

8,函数的形参与实参

( 形参 parameter 实参 argument )

8-1 形参

8-1-1 可选参数
可选参数:
// 当实参比形参的个数要少时,剩下的形参都将设置为undefined值;为了让实参与形参保持较好的适应性,有必要为可选形参设置一个合理的默认值;如:
function show(name,msg){
  if(name==undefined) name="aini";
  if(msg==undefined) msg="18 years old";
  console.log("hello,"+name+",message:"+msg);
}
show();
show("aili");
show("dilnur","22 years old");

不用 if 可以使用||,也是惯用手法

function show(name,msg){
  name=name || "aini";
  msg= msg || "18 years old";
  console.log("hello,"+name+",message:"+msg);
}
show();
show("aili");
show("dilnur","22 years old");
// 将对象o中可枚举的属性追加到数组a中,并返回数组a ,如果省略a,则创建一个新数组并返回这个新数组;

image-20240131134817480

function getProprtyNames(o,/* optional */a){ // 一般可以加个注释来指定一下不定参数
  if(a == undefined) a=[];
  // a= a || [];
  for(var property in o){
    a.push(property);
  }
  return a;
}
var o={name:"aini",age:18,sex:"nan",friend:"dilnur"};
var result=getProprtyNames(o);
console.log(result);
var arr=[1,2,3];
console.log(getProprtyNames(o,arr));
// 说明:当设置可选参数时,一定要把可选参数放到形参列表的最后,因为不能省略第一个参数(定参)并传入第二个实参;如果第一个参数(定参)不存在或不使用,也需要显式的传入undefined,真实的场景是传入一个无意义的占位符null
	function show(a,b,/* optional*/c,d){

}
show(1,2,3,4);      //正常使用
show(1,null,2,3);   // 使用null占位
8-1-2 形参个数
//函数本身的length属性,只读,它代表函数的形参个数,即函数期望传入的实参个数,如:
// 函数名.length   可以返回传入的形参的个数
function show(a,b,/* optional*/c,d){
    console.log(show.length);
}
show(1,2,3,4);      
show(1,null,2,3); 

8-2 实参

8-2-1 实参对象(arguments)
// 传入的实参个数超过函数定义的形参个数时,没有办法直接获得超出的实参的值,但可以使用实参对象arguments来获取
    // 在函数体内,arguments是指向实参对象有的引用,其是一个类数组对象;
    // 函数接收的始终都是这个数组,而不关心数组中包含哪些参数;
    // 即使没有传递参数,这个数组也存在,只不过是包含0个元素的数组;
function show(a,b){
    console.log(arguments);
}
show();
show(1,2,3,4); 

image-20240131135150131

Arguments.length 可以返回实参个数

function sayHi(){
  console.log("传入了,"+arguments.length+"个实参");
  console.log("hello,"+arguments[0]+","+arguments[1]);
}   
sayHi();
sayHi("aini","huan ying ni lai xue xi");

可以使用arguments对象来验证所传递的参数是否符合函数要求;如

//实际场景中,如果对参数个数有要求需要验证;
function func(x,y,z){
  //首先验证传入的个数是否正确
  if(arguments.length!== func.length) throw new Error("需要"+func.length+"三个函数");  //抛出异常错误;
 //在判断类型;
 if(typeof arguments[0] != "number") 
 console.log("实参类型是数字");
 z=+arguments[2]?y:0;  
 return x+y+z;
}
console.log(func(1,2,"w"));
// 在某些时候,没有必要检查实参的个数,因为JS的默认行为是可以满足需要的,如省略的实参都是undefined,多出的参数自动省略
function check(args){
  var actual=args.length; //实参的真实个数;
  var expected=args.callee.length;  //形参个数;
  if(actual != expected) throw new Error("参数个数不对");
}
function f(x,y,z){
  try{
    check(arguments);
    return x+y+z;
  }
  catch(e){
    console.log(e);
  }
};
console.log(f(1,2,3));
console.log(f(2,3));

image-20240131135417724

image-20240131135423389

可以利用arguments对象,让函数能够接收任意个数并分别实现适当的功能,如

function doAdd(){
  if(arguments.length==1) console.log(arguments[0]);
  else if(arguments.length==2) console.log(arguments[0]+arguments[1]);
  else if(arguments.length==3) console.log(arguments[0]+ arguments[1]+arguments[2]);
}
doAdd(10,20);
doAdd(5,10,20);
doAdd(10,35,12,45);

// 根据实参的个数进行处理

优化代码(利用循环

function doAdd(){
  var sum = 0;
  for(var i=0;i<arguments.length;i++){
    sum+=arguments[i];
  }
  console.log(sum);
}
doAdd(10,20);
doAdd(1,2,3,4,5,6,7,8,9);

继续优化

function doAdd(){
  var sum = 0;
  for(var i=0;i<arguments.length;i++){
    sum+= (+arguments[i]?arguments[i]:0);
  }
  console.log(sum);
}
doAdd(10,20);
doAdd(1,2,3,4,5,"aini",NaN,8,9);

根据实参返回最大值

function max(){
  var max=Number.NEGATIVE_INFINITY;
  for(var i=0;i<arguments.length;i++){
      (arguments[i]>max)?(max=arguments[i]):max;
  }
  return max;
}
var largest=max(1,45,32,189,245,12,36,985,421,65);
console.log(largest);
// 类似于这种可以接收任意个数的实参,这种函数也被称为“不定实参函数”,这个术语来自C语言

// 但真正的不定实参的个数不能为零,其应用的场景是:该函数包含固定个数的命名和必须的参数,以及随后个数不定的可选实参,即arguments对象可以与命名参数一起使用
function doAdd(num1,num2){
  var result=parseInt(num1)+parseInt(num2);
  if(arguments.length > 2){
    for(var i=2;i<arguments.length;i++){
      result+= parseInt(arguments[i]);
    }
  }
return result;
}
console.log(doAdd(20,30,40,50,60,30));

arguments并不是真正的数组,它是一个对象,其数组元素是函数实参的别名;arguments的值永远与对应命名参数的值保持同步

function func(num1){
  console.log(num1);   // 10
  console.log(arguments[0]); //10
  arguments[0]=1;
  console.log(num1);   // 1
  console.log(arguments[0]) //1 ;
}
func(10);

image-20240131135806474

// 注:在严格模式下,重写arguments的值会失效,但不会导致语法错误
// Javascript是一门弱类型语言,所以函数中的形参并没有类型声明,并且在传入参数前也未做任何类型检查,虽然JS可以进行数据类型的自动转换,但在某些时候(类型不同,不会导致语法错误,但在程序运行时有可能导致错误),函数还是希望得到一个类型明确的参数值,此时,应当对所传入的参数进行类型的检查,如:
function sum(arr){
    if(Array.isArray(arr)){
      var total=0;
      for(var i=0;i<arr.length;i++){
        var element=arr[i]
        if(element == null) continue;
        if(isFinite(element)) total+=element;
        else throw new Error("数组元素必须是有限数");
      }
      return total;
    }else throw new Error("函数的参数必须是数组");
}
console.log(sum([1,2,3,4,5,6]));

扩大累加的数据类型

function flexsum(a){
  var total=0;
  for(var i=0;i<arguments.length;i++){
    var element=arguments[i],n;
    if(element == null) continue;
    if(Array.isArray(element)){
      n=flexsum.apply(this.element);
    }else if(typeof element ==="function"){
      n=Number(element());
    }else n=Number(element);
    if(isNaN(n)) throw new Error("无法把:"+element+"转换为数字");
    total+=n;
  }
return total;
}
console.log(flexsum(1,2,3));
console.log(flexsum(1,"",3));
console.log(flexsum(1,[7,8],3));
console.log(flexsum(function(){return 18;}));

没有重载

unction func(){
  console.log("func函数");
}
function func(num){
  console.log("年龄为:"+num);
}
function func(name,age){
  console.log("名字为:"+name+",年龄为:"+age);
  }
func();
func(18);
func("aini",18);

image-20240131135937956

// 虽然定义了三个一样的函数,但是最后定义的把前面定义的两个覆盖掉

// ECMAScript函数不能像传统意义上那样实现重载;而在其他语言中,可以为一个函数编写两个定义,只要这两个定义的签名(参数的类型和数量)不同即可;ECMAScript函数没有签名,因为其参数是由包含零或多个值的数组来表示的;而没有函数签名,真正的重载是不可能做到的;
var addNum = function(num){return num+100};
var addNum = function(num){return num+300};

函数表达式定义同名的函数时,下面的变量就把前面的变量覆盖了,所以也不存在重载;

8-2-2 模拟重载

可以通过检查传入函数中参数的类型和数量并作出不同的反应,可以模拟方法的重载;

function overrideFunc(){
  var arglen=arguments.length;
  if(arglen==0) console.log("wu canshu de fangfa");
  else if(arglen==1) console.log("传入的参数为:"+arguments[0]);
  else if(arglen==2) {console.log("传入了2个参数");
      if(!isNaN(arguments[0]&& !isNaN(arguments[1]))){
        console.log("和为:"+(arguments[0]+arguments[1]));
      }else console.log("不是数字:"+arguments[0]+arguments[1]);
  }else console.log("未知,其他处理");
}
overrideFunc();
overrideFunc("零点程序员");
overrideFunc(3,4);
overrideFunc(3,"a",33);

image-20240131140125692

8-2-3 callee和caller属性
// callee属性指向当前正在执行的函数,在某些时候非常有用,如在一个匿名函数中使用递归;但在严格模式下会抛出错误,所以,尽量避免使用callee属性,可以为函数明确定义一个名称;
//数的阶乘
function factorial(x){
  if(x<=1) return 1;
  return x* factorial(x-1);
}
console.log(factorial(10));
//数的阶乘
function factorial(x){
  if(x<=1) return 1;
  // return x* factorial(x-1);
  return x*arguments.callee(x-1)  //这样就不会出错了;
}
var myFactorial = factorial;
factorial = null;
console.log(myFactorial(10));
8-2-4 匿名函数中使用
//数的阶乘
function create(){
  return function(n){
    if(n<=1) return 1;
    return n*arguments.callee(n-1);
  };
}
console.log(create()(5));
// 由于内函数没有名字,所以只能用arguments.callee来指向他;

caller是非标准的,但大多数浏览器都实现了这个属性,它指向调用当前函数的函数,在ES5中被移弃了,其始终会返回undefined,不会报错

8-3 参数的传递方式

// 基本数据类型:值传递
// 引用数据类型:引用传递,地址传递(本质上还是按值传递)
function setname(obj){
  obj.name="aini";
}
var person = new Object();
setname(person);
console.log(person.name);   //aini

// obj和person内存对应同一个地址
function setname(obj){
  obj.name="aini";
  obj = new Object();
  obj.name = "dilnur";
  console.log(obj.name);
}
var person = new Object();
setname(person);     //dilnur
console.log(person.name);   //aini

8-4 函数回调和匿名函数

8-4-1 函数回调
// 回调 :将一个函数对象a 传递给另一个函数对象 b,让后者在适当的时候执行 a。这就是所谓的“回调”

// 适当的时候:当这个外部函数在一定条件下就会调用参数指定的函数,此函数就是回调函数。如:
function funcA(){
  console.log("this is func A");
}
function funcB(func){
  func();
}
funcB(funcA);  // this is a func A 
  <input type="text" id="score">
  <input type="button" value="检测" οnclick="test()"><br>
  <p id="myp"></p>
<script>
function test(){
  var myp=document.getElementById("myp");
  var str;
  var score = document.getElementById("score").value;
  if(score<0) str="分数不能为负"
  else if(score == 0) str="此考生没有参加考试"
  else if(score>0 && score<60) str="不及格"
  else if(score>=60 && score <70) str="及格"
  else if(score>=70 && score<90) str="良好"
  else if(score>=90 && score<=100) str="优秀"
  else str="无效分数";
  myp.innerHTML=str;
}

也可以这么样写

	  var str;
  function fun(score){
    if(score<0) str="分数不能为负"
  else if(score == 0) str="此考生没有参加考试"
  else if(score>0 && score<60) str="不及格"
  else if(score>=60 && score <70) str="及格"
  else if(score>=70 && score<90) str="良好"
  else if(score>=90 && score<=100) str="优秀"
  else str="无效分数";
  }
function test(){
  var myp=document.getElementById("myp");
  var score = document.getElementById("score").value;
  fun(score);
  myp.innerHTML=str;}

继续改进,用回调函数

<body>
  <input type="text" id="score">
  <input type="button" value="检测" οnclick="test()"><br>
  <p id="myp"></p>
<script>
  var str;
  function fun(score,callback){
    if(score<0 || score>100) str="分数无效"
  else if(score == 0) str="此考生没有参加考试"
  else callback();
  }
function test(){
  var myp=document.getElementById("myp");
  var score = document.getElementById("score").value;
  fun(score,function(){  //匿名函数
  if(score>0 && score<60) str="不及格"
  else if(score>=60 && score <70) str="及格"
  else if(score>=70 && score<90) str="良好"
  else if(score>=90 && score<=100) str="优秀"
  });
  myp.innerHTML=str;
}

数组排序

  function sortArr(arr,fun){
    if(!Array.isArray(arr) || !(fun instanceof Function)){
      throw new Error("参数类型不准确");
    }else{
      for(n in arr){
        for(m in arr){
          if(fun(arr[n],arr[m])){
            var tmp=arr[n];
            arr[n]=arr[m];
            arr[m]=tmp;
          }
        }
      }
    }
  }
  function compare(num1,num2){
    return num1>num2;   
    }
  try{
    var arr=[45,12,68,95,115,65,32,25,12,78,35];
    sortArr(arr,compare);
    console.log(arr)
  }catch(e){
    console.log(e);
  }
8-4-2 匿名函数
// 匿名函数即为没有名字的函数,也称为拉姆达函数;匿名函数功能强大,用途很广

// 一般将匿名函数作为参数传入另一个函数,或者从一个函数中返回另一个函数,这是一种极为有用的技术

// 函数也可以作为值来使用;也就是说,可以将函数赋值给变量、存储在对象的属性或数组元素中,也可以当作一个参数进行传递
function func(x){return x*x}
var s = func;
console.log(func(4));
console.log(s(4));

同样可以将函数赋值给对象的属性,此时应该称为方法

	var o = {
  name:"aini",
  say:function(x){
    return x*x;
  }
}
console.log(o.say(5));

或者

var o = {}
function say(x){
  return x*x;
};
o.say=say;
console.log(o.say(5));

或者

这里用的就是匿名函数

var o = {}
o.say=function(x){return x*x;}
console.log(o.say(5));

赋值给数组元素,此时的函数可以不需要名字,即是匿名函数

var arr = [function(x){return x*x},18];
console.log(arr[0](arr[1]));

函数作为参数传递

	  function myFun(someFun,someArg){
    return someFun(someArg)
  }
  function add(num){
    return num+=10;
  }
  var result = myFun(add,20);
  console.log(result);

还可以这样用

//定义一些简单函数
function add(x,y){return x+y};
function subtract(x,y){return x-y};
function multiply(x,y){return x*y};
function devide(x,y){return x/y};
function operate(operator,num1,num2){
  return operator(num1,num2);
}
var result = operate(add,operate(multiply,4,5),operate(add,2,3));
console.log(result);

第二种用法

// 使用函数直接量,定义在一个对象直接量中
var operators = {
    add: function(x,y){return x + y;},
    subtract: function(x,y){return x - y;},
    multiply: function(x,y){return x * y;},
    divide: function(x,y){return x / y;},
    pow: Math.pow  // 使用内置的Math对象的pow方法
};
// operate函数接受一个名字作为运算符,在operators对象中查找这个运算符
// 然后将它作用于所提供的操作数
function operate(operation, num1, num2){
    if(typeof operators[operation] === "function")
        return operators[operation](num1, num2);
    else throw "unknown operator";
}
// 调用并计算
var result = operate("add", "hello", operate("add", " ", "wangwei"));
console.log(result);  // hello wangwei
result = operate("pow", 10,2);
console.log(result); // 100
// 当函数作为值的一个典型的应用,就是在Array.sort()方法中使用,该方法用来对数组元素进行排序;因为排序的规则有很多,如:基于数值大小、字母表顺序、日期大小、从小到大等,所以sort()方法接受一个自定义函数作为参数,该自定义函数用来处理具体的排序操作,其原理是:对于任意两个值都返回一个值,以指定它们在在排序后的数组中的先后顺序;如:
function compare(x,y){
    return x - y;
}
var arr = [1,88,3,5,12,18,67];
console.log(arr.sort(compare));

9,数组

// 数组是值的有序集合;每个值叫做一个元素,而每个元素在数组中都有一个位置,以数字表示,称为索引或下标;

// JavaScript数组是无类型的,数组元素可以是任意类型;即使同一个数组内,不同的元素也可以有不同的类型
// JS数组的索引是基于零的32位整数,第一个元素的索引为0;
// ES数组是动态的,即它的大小是可以动态调整的,可以随着数据的添加与删除会自动增长或缩减

9-1 创建数组

9-1-1 第一种

使用构造函数Array()创建

var arr = new Array();  // 空数组
var arr = new Array(3);   // 指定长度
var arr = new Array("wangwei","wujing","jingguo");  //显示指定元素
// 1.如果预先知道数组要保存的项目数量,可以为构造函数传递该数量,这个数量实际上就是该数组length属性(实际上是预分配了一个数组空间,但数组中没有存储值,甚至数组的索引都未定义)
// 2.直接传递项,即参数就是数组的元素
// 3.如果需要创建只有一个值的数组,并且这个值是数字,只能把该数字使用字符串的形式;当这一个值如果是非整数时,会抛出异常;
var arr = new Array(18.5);   //抛出异常

也可以省略new操作符,实际上是进行了数据类型转换,如

var arr = Array();  // 空数组
console.log(arr);
var colors = Array(3);   // 指定长度
console.log(colors);
var names = Array("wangwei","wujing","jingguo");  //显示指定元素
console.log(names);
9-1-2 第二种

使用数组字面量(直接量)创建

var empty = [];
var num = [2,3,4,5];
// 这是创建数组最简单的方法,即使用方括号[ ]将数组元素用逗号分隔开
// 当为[ ]空数组时,与new Array()等价
// 数组直接量中的值不一定是常量,也可以是任意的表达式
var year = 2022;
var years = [year,year+1,year+2];
console.log(years);

还可以包含对象或其他数组

var objArr = [{name:"wangwei",age:18},[1,2],[3,{x:4,y:5}]];

如果省略数组中某个值,省略的元素的值都是undefined值

var count = [1,,3];
console.log(count[1]);  // undefined;

允许有可选的结尾逗号,但会省略最后的元素

count = [1,2,];  // 2
console.log(count);
count = [,,,];   // 3
console.log(count);

image-20240131141838495

// 不建议使用,某些浏览器会解析为3项数组,因为它们对数组的实现不一致
// 总结:在实际场景中,使用数组字面量要比构造函数简单、灵活

9-2 数组的内存分布

// 数组在内存中用一串连续的区域来存放一些值;在 JavaScript 中,数组是哈希映射,在内存中不是连续的,它可以通过多种数据结构实现,其中一种是链表,因此,如果使用大数组,需要的时间很长;

// 在ES6中引入了类型化数组,JavaScript 引擎已经在为同种数据类型的数组分配连续的存储空间了,如ArrayBuffer,其使用的就是一块连续的内存空间

image-20240131142026981

9-3 数组的读写

要读取或设置数组元素的值时,使用“[ ]”运算符,并提供相应的基于0的数字索引下标

var citys = new Array("蚌埠","宿州","南京");
var c1 = citys[0];  // 读取第一个元素
citys[0] = "怀远";  // 写第一个元素

在越界访问不存在的数据元素时,会返回undefined

如果设置某个值的索引超过了数组现有的项数,数组会自动增加到该索引值加1的长度

	var colors=["red","blue"];
colors[2]="green";   // 明确添加了一个新元素
var i = 3;
colors[i] = "purple";
colors[i+1] = "yellow";

注意:跳过某个索引添加值时,跳过的自动变成empty

var colors=["red","blue"];
colors[3] = "purple";
console.log(colors);
console.log(colors.length);  // 4

image-20240131142211018

// 数组的最大索引为4294967294(2的32次方-2)

// 数组是对象的特殊形式;使用方括号访问数组元素就像用方括号访问对象的属性一样;
// 其会将指定的数字索引值转换成字符串,并将其作为属性名来使用
var n = [];
n[0] = "one";  n[0]=”one”
n[1] = "two";   n[1]=”two”    等同
console.log(n[1]);
var p = {};
p[0] = "one";  // 将0转换为”0”
p[1] = "two";
console.log(p[1]);
var o = {0:"wangwei",1:"wujing",2:"huiyuan"};
o[1] = "lili";
console.log(o[1]);  // lili
// 所有的数组都是对象,所以可为其创建任意名字的属性,此时这个任意名字如果不是非负整数,它就只能当作常规的对象属性,而非数组的索引;
var arr = [1,2];
arr[-2] = "-2的值";
arr["name"] = "aini";
console.log(arr);

image-20240131142338648

// 如果凑巧使用了是非负整数的字符串,它就当作数组索引,而非对象属性;当使用一个浮点数和一个整数相等时情况也是一样的
var arr = [1,2];
arr["2"] = "333";    把二当做数组的索引,因为字符串2能转换成数字
var arr = [1,2];
arr[-1.23] = true;  // 创建了一个名为“-1.23”的属性
arr["1000"] = 18;   // 数组的第1001个元素
arr[1.000] = 10;    // 等同 arr[1] 
console.log(arr)
// 事实上,数组索引仅仅是对象属性名的一个特殊类型,这意味着ES数组没有“越界”错误的概念,即试图查询任何对象中不存在的属性时,不会报错,只是得到undefined值 

// 所有的索引都是属性名,但只有在2的32次方之内的整数属性名才是索引;超出的索引只能是属性名(所以超出4294967295的都属属性,数组索引最大长度为4294967295)
arr[4294967294] = "a";
arr[4294967294] = "a";
console.log(arr);
console.log(arr.length);
arr[4294967295] = "b";
console.log(arr);
console.log(arr.length);
arr[4294967296] = "c";
console.log(arr);
console.log(arr.length);

image-20240131142500805

通常,数组的实现是经过优化的,用数字索引来访问数组元素一般来说比访问常规的对象属性要快很多,所以尽量使用数组传递数据

9-4 length 属性

数组对象的length(长度)属性返回或指定数组元素的个数

var colors=["red","blue","green","skey","gray"];
console.log(colors.length);
colors.length = 3;
console.log(colors.length);
console.log(colors);
// 说明:即多于length的元素被删除了
var names=["wangwei"];
names.length = 4;
console.log(names);    // 添加了三个空的向;
// length属性设置为大于数组项数的值,则新增的每一项都会取得undefined值;(从本质上讲,并不会向数组中添加新的元素,它只是在数组尾部创建一个空的区域,其实就是后面要讲的稀疏数组)

利用length属性可以方便的在数组末尾添加新项

var colors=["red","blue","green"];
colors[colors.length]="black";
colors[colors.length]="brown";
console.log(colors);
// 数组的最大索引为4294967294(2的32次方-2),即可以包含4292967295个元素,当索引小于此值并且不为非负时,数组会自动更新其length属性;超出这个范围,length不会更新

在ES中,可以用Object.defineProperty()让数组的length属性变成只读的

9-5 稀疏数组

Sparse arrays 稀疏数组:ES中的数组是稀疏的,即数组的元素的索引不一定要连续,它们之间可以有空缺

var arr = new Array(5);   // 数组没有元素,但length是5
arr = [];   // 空数组,length为0
arr = [1,,4];   // 
arr[1000] = 0;  // 赋值添加了一个元素,length为1001
arr = [1,2,3,4,5];
delete arr[2];  // 使用delete也能生成稀疏数组
console.log(arr);

如果数组是稀疏数组,length值大于元素的个数,否则,该属性就是数组元素的个数;(即稀疏数组的length长度与个数是不一致的)

遍历时会跳过这些空隙

var a1 = [1,,,4];
for(i in a1)
    console.log(i);

循环遍历时都能打印出来,空数组就打印undefined;

9-6 稠密(密集)数组

与稀疏相对应,则为密集数组,即元素中不存在空隙,其实密集数组基本就是平时常见的正常数组

var spare =  new Array(3);
var dense= Array.apply(null,spare);  可以产生值为undefined
console.log(spare);                    的密集数组,跟稀疏数组     
console.log(dense);                    有区别
console.log(spare[0]);
console.log(dense[0]);

区别

var arr = new Array(3);
arr[2] = "aini";
for (index in arr){
  console.log(index,arr[index]);  //  2 aini
}

var dense = Array.apply(null,Array(3));
dense[2] = "dilnur";
for (index in dense){
  console.log(index,dense[index]);  //都能打印
}

image-20240131143104495

// 说明稀疏数字里面的undefined和密集数组里面的undefined是两回事儿
// 密集数组
console.time('one');
Array.apply(null,Array(1e5)).forEach(function(){});
console.timeEnd('one');
// 稀疏数组
console.time('two');
Array(1e5).forEach(function(){});
console.timeEnd('two');
// one: 8.589111328125ms
// two: 2.122314453125ms

 // 稀疏数组速度比密集数组处理速度快
// 在实际应用中,绝大部分的es数组都不是稀疏数组;如果使用了稀疏数组,可以像处理非稀疏数组一样处理,只不过它们包含一些undefined值

// 稀疏的数组通常在实现上比稠密的数组更慢、内存利用率更高,在这样的数组中查找元素的时间与常规元素属性的查找时间一样长;另外,在通常的情况下,我们想要直接声明一个数组并赋予其一些特定的初始值,并且为了避免问题,通常是希望申明为密集数组的

压缩稀疏数组:可以通过filter()方法压缩其中的空隙,因为filter会跳过空隙,返回密集的数组

	var a1 = [1,,,4];
var a2 = a1.filter(function(x){return true;});
for(i in a2)
    console.log(i);

9-7 数组方法

ES在Array.prototype中定义了很多操作数组的方法

console.log(Array.prototype);

image-20240131143312300

9-7-1 数组转字符串
1-1 toString(),valueOf()

所有对象都具有toLocaleString()、toString()及valueOf()方法

1,valueOf() // 返回的是数组本身
2,toString() // 方法将数组表示为字符串,各个元素按顺序排列组合以逗号分隔的字符串返回。(通过对每项调用toString()方法,然后用逗号把它们连接在一起构成的)
var arr = [1,2,3,5];
console.log(arr);
console.log(arr.valueOf());
console.log(arr.toString());

image-20240131143434889

var colors=["red","blue",["green","black"]];
console.log(colors.valueOf());  // ["red", "blue", Array(2)]
console.log(colors.toString()); // red,blue,green,blank
console.log(colors);        // ["red", "blue", Array(2)]
// 以上console.log()换成alert,由于alert要接收字符串参数,所以它会在后台调用toString(),由此会得到与直接调用 toString()方法相同的结果

// 这里的toString()方法与不使用参数的join()方法返回的字符串是一样的
// toLocaleString()是toString()方法的本地化版本,一般情况下会返回与toString()和valueOf()方法相同的值,但也不总是如此(其会使用本地化的分隔符);当调用数组的toLocaleString()方法时,后台会为每一项调用 toLocaleString()方法,而不是tostring()方法
var p1 = {
  toLocaleString:function(){return "aini"},
  toString:function(){return "ai"}
}
p2 = {
  toLocaleString:function(){return "dilnur"},
  toString:function(){return "norah"}
}

image-20240131143541909

var person = [p1,p2];
console.log(person);
console.log(person.toString());
console.log(person.toLocaleString());
1-2 join()
2,join()方法
// 将数组中所有元素使用不同的分隔符转化为字符串,分隔符号由用户指定
var colors = ["red","green","blue","balck"];
console.log(colors.join());
console.log(colors.join(","));
console.log(colors.join("-"));
console.log(colors.join("|"));
console.log(colors.join(" "));
var arrStr = new Array(10);
console.log(arrStr.join("-"));

image-20240131143711245

如果不指定分隔符,或者传入undefined,则使用默认的逗号分隔

var colors = ["red","green","blue","balck"];
console.log(colors.join(undefined));
console.log(colors.join("undefined"));
console.log(colors.join(null));
console.log(colors.join("null"));

image-20240131143745854

// 注:”undefined”,”null”是字符串;如果直接是undefined则会用逗号分割;null的话转换成”null”,再用”null”分割
// join()方法是split()方法的逆向操作,后者是将字符串分割成若干块来创建一个数组。
// 注:如果数组中的某一项的值是null或者undefined,那么该值在以上所有方法中返回空字符串。

字符串split()方法:把字符串转换为数组

var str = "red,blue,green";     // 字符串
var aStr=str.split(",");        // 数组
var aStr=str.split("");     // 单个字符数组

关于字符串split方法的几个注意点:

// (1)如果指定错误的分隔符,则把整个字符串当做数组的一个  元素返回
var str = "red,blue,green";     
var aStr=str.split("-");     
console.log(aStr);

image-20240131143949461

// (2)如果什么也不传,就会把字符串里面的每一个字符拿出来,当做数组的一个元素
var str = "red,blue,green";     
var aStr=str.split("");     
console.log(aStr);

image-20240131144023647

// (3)如果把空字符串声明为分隔符,split会返回字符串的每个字条符;一般用于需要逐个处理字符.
9-7-2 队列方法
//(1)栈和队列是一种限制了插入和删除数据项操作的数据结构;
//(2)栈数据结构的访问规则是LIFO(先进后出)
//(3)而队列数据结构的访问规则是FIFO(First-In-First-Out,先进先出)
//(4)栈最近添加的项是最先删除的项;栈中的插入和删除操作都只发生在一个位置,即栈顶部;把一个项添加到栈中,叫做推入栈;从栈中删除一项,叫做从栈中弹出;
//(5)队列在列表的末端添加项,从列表的前端移除项;可以使用push()方法向数组末尾添加项,使用shift()方法从数组前端移除项,这两个方法模拟队列;
2-1 push()

在数组尾部添加一个或多个元素;并返回更新后数组长度会修改原数组

var colors = new Array();
var count = colors.push("red","green");
console.log(colors);
console.log(count);  // 2
count = colors.push("black","blue");
console.log(colors);
console.log(count);  //4

// push()方法实际上同手工添加数据一样.
// 注:pop()不带值也不会报错,只返回数组长度
2-2 pop()

与push()方法相反,其会删除数组末尾的一个元素,并将其返回;会修改原数组

var colors=new Array();
colors.push("red","green","blue");
var item = colors.pop();
console.log(item);      // blue
console.log(colors);
2-3 unshift()
// (1) 在数组顶端插入元素,一次可以插入单个或多个元素,所有元素按顺序插入,操作完成后返回新数组的长度;会修改原数组;其与push()类似
// (2)unshift()方法将引发所有下标的改动
// (3)在一次性插入多个参数时,插入的元素的顺序和它们在参数列表中的顺序一致
// (4)如果不计较元素插入的位置,则推荐使用push方法,因为,unshift()方法可能会影响依靠下标才能准确进行的计算。
var colors = new Array();
colors.push("red","green","blue");
var newLen = colors.unshift("black","gray");
newLen = colors.unshift("r",["g","b"]);
console.log(newLen);
console.log(colors);

image-20240131144420538

2-4 shift()
// shift()移除数组的第一个元素并将其返回;该方法执行后数组剩下的元素向前移动,下标索引号重新调整从0开始按顺序赋予所有元素;会修改原数据;其与pop方法类似;
var colors = new Array();
colors.push("red","green","blue");
var shiftItem = colors.shift();
console.log(shiftItem);
var item = colors.shift();
console.log(item);
console.log(colors);

image-20240131144505477

调用push()方法和shift(),可以使Array对象具有队列一样的行为(先进先出)

var colors=new Array();
var count = colors.push("red","green","blue");
count = colors.push("black");
var item = colors.shift();
console.log(colors);

使用unshift()和pop()方法,可以从相反的方向来模拟队列,即在数组的前端添加项,在末端移除项;

var colors=new Array();
var count = colors.push("red","green","blue");
count = colors.unshift("black");
var item = colors.pop();
console.log(colors);
9-7-3 数组排序

排序算法:选择排序和冒泡排序;

3-1 选择排序
var  arr = [78,15,69,3,45,78,95,62,35,98,789,125,98,5,58];
for (var i=0; i<arr.length;i++){
    var element 
    for(var j=i+1; j<arr.length;j++){
      if(arr[j]>arr[i]){
        element = arr[i];
        arr[i] = arr[j];
        arr[j]=element;
      }
    }
};
console.log(arr);
3-2 冒泡排序
var  arr = [78,15,69,3,45,74,785,125,654,12,35,64,05,45,78,126];
function compare(num1,num2){
  return num1<num2?true:false;
}
function sort(arr){
  var len = arr.length;
  for (var i=0; i<len;i++){
    var element 
    for(var j=0; j<len-i;j++){
      if(compare(arr[j],arr[j+1])){
        element = arr[j];
        arr[j] = arr[j+1];
        arr[j+1]=element;
      }
    }
}
  return arr;
};
console.log(sort(arr));
3-3 reverse()

reverse()方法将一个Array对象中所有元素的次序反转,会修改原数组

var arr = ["aini","dilnur","xinjiang","shanghai"];
console.log(arr.reverse());

image-20240131144758867

3-4 sort()
sort()方法:
(1)sort()方法:// 对数组元素进行排序后并返回;默认将一个数组中的所有元素进行升序排序:会改变原数组2// 如果数组元素中undefined或null,它们会被排列在数组的尾部;
var values = [0,1,5,10,15];
values.sort();
console.log(values);
var arr = new Array("banana",undefined,"cherry",null,"apple");   按照Unicode编码大小
arr.sort();	进行排序;
console.log(arr);

image-20240131144846249

实际上,sort()方法是把元素转换成字符串进行排序的

// (1)sort()方法可以接收一个比较函数,以确定一个排序的规则
// (2)比较函数接收两个参数,如果第一个参数位于第二个之前则返回一个负数
// (3)如果两个参数相等则返回0
// (4)如果第一个参数位于第二个之后则返回一个正数
function compare(value1,value2){
    if(value1<value2){
        return -1;
    }else if(value1>value2){
        return 1;
            }else{
        return 0;
    }
}
var values = [0,1,5,10,15];
values.sort(compare);

image-20240131144937808

// 对于数值类型或者valueOf()方法会返回数值类型的对象类型,可以使用一个更简单的比较函数,这个函数只要用两个值互减即可
function compare(value1,value2){
    return value1 - value2;
}

注:参数必须是数字才能这样用;

// 说明:由于该函数通过返回一个小于零、等于零或大于零的值来影响排序结果,因此减法操作就可以适当地处理所有这些情况。
3-5 随机排序
var arr = [1,2,3,4,5,6,8,9];
var randomArr = arr.sort(
  function(){
  return Math.random()-0.5;
}
);
console.log(arr);

对于字符串排序,主要是大小写的问题

var str = ["dilnur","AINI","aini","DILNUR"];
console.log(str.sort());
str.sort(function(s,t){
    var s = s.toLowerCase();
    var t = t.toLowerCase();
    if(s < t) return -1;
        if(s > t) return 1;
    return 0;
});
console.log(str);

image-20240131145130547

另外,有时需要按字符串长度进行排序,此时还应该同时判断字符是否为双节字

var arr = ["阿卜杜艾尼a","aini-艾尼","dilnur_地","aini艾尼","dilnur地理"];
function getDword(str){
  var len = str.length;
  for(var i=0;i<len;i++){
    if(str.charCodeAt(i)>255){
      len++;
    }
  }
  return len;
}
arr.sort(function(s1,s2){
  return getDword(s1)-getDword(s2);
})
console.log(arr);

// 汉字的长度就按双字节算;
3-6 对象排序,

比较的主要是对象的valueof()值:

var arr = [o,
    {valueOf:function(){return 15}},
    {valueOf:function(){return 18}},
    {valueOf:function(){return 4}}
];
arr.sort(function(o1,o2){return o1 - o2});
console.log(arr);
for(var i in arr)
    console.log(arr[i].valueOf());

image-20240131145324644

或者按指定的对象属性进行排序

var wang = {name:"wangwei",sex:"男",age:18};
var wu = {name:"wujing",sex:"女",age:28};
var guo = {name:"jianguo",sex:"男",age: 25};
var arr = [wang,wu,guo];
var pArr = arr.sort(function(p1,p2){
    return p1.age - p2.age;
});
console.log(pArr);
9-7-4 数组操作
4-1 concat()
concat()方法:
// (1)基于当前数组,将多个元素连接一起成为新的数组并返回
// (2)新数组中的元素按连接时的顺序排列

// (具体来说,这个方法会先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。当需要合并多个数组时,此方法比较方便)
var arr = [1,2];
arr1 = arr.concat();  
arr2 = arr.concat(3,4);
arr3 = arr.concat([3,4,],["aini"]);  // 3,4,aini拿出来放到源数组里面;  扁平化
arr4 = arr.concat([3,4,[5,6,7]]);
console.log(arr);
console.log(arr1);
console.log(arr2);
console.log(arr3);
console.log(arr4);
// (3)concat()不会递归扁平化数组的数组,也不会修改原来的数组
// (4)当没有给concat()方法传递参数的情况下,它只是复制当前数组并返回副本
4-2 slice()
2,slice()方法:
// (1)能够基于当前数组中的一个或多个项创建一个新数组
// (2)其接受一或两个参数,即要返回项的起始和结束位置;
// (3)在只有start一个参数的情况下,slice()方法返回从该位置开始到当前数组末尾的所有项;
// (4)如果两个参数,该方法返回起始和结束位置之间的项,但不包括结束位置的项;
// (5)其不会影响原有数组;
var colors = ["red","green","blue","yellow","purple"];
var a = colors.slice(2);
var b = colors.slice(1,4);
console.log(a);
console.log(b);
var c = colors.slice(1,-2); //从索引1 取到 倒数第二个;
var d = colors.slice(-4,-2); // 倒四  取到 倒二;
console.log(c);
console.log(d);

只能从左往右截,像colors.slice(3,1) 就会返回空数组;

4-3 splice()
3,splice()方法:(改变原数组)
// (1)可以删除、替换或插入数组元素,是最强大的数组方法,有很多种用法;
// (2)具体作用是,从一个数组中移除一个或多个元素;剩下的元素组成一个数组,移除的元素组成另一个新数组并返回;
// (3)同时,原数组可以在移除的开始位置处顺带插入一个或多个新元素,达到修改替换数组元素的目的;

    start:// 必选项,表示从数组中剪切的起始位置下标索引号。
    deleteCount:// 必选项,表示将从数组中切取的元素的个数。
    item:可选项,// 表示切取时插入原数组切入点开始处的一个或多个元素;
3-1 删除
var colors = ["red","green","blue","yellow","purple"];
var a = colors.splice(1,3);
console.log(colors);  // 返回原数组删除某些元素以后的数组
console.log(a);  // 返回删除的项
1)删除:// 可以删除任意数量的项,只需要指定2个参数,如:splice(0,2);// 删除数组中前两项;如果只指定1个参数,则删除从该位置到结尾的元素;
2)插入:// 可以向指定位置插入任意数量的项,需要指定3个参数,其中第二个参数为0;如果要插入多个项,可再传入第4第5等任意多个项,如:splice(2,0,”red”,”green”);
3)替换:// 可以向指定位置删除任意数量的项,同时插入任意数量的项,需要指定至少3个参数;插入的项数不必与删除的项数相等,如:splice(2,1,”red”,”green”);

splice()方法始终会返回一个数组,该数组中包含从原始数组中删除的项(如果没有删除任何项,则返回一个空数组)

3-2 插入

没必要把返回值赋给新变量

var colors = ["red","green","blue","yellow","purple"];
var a = colors.splice(1,0,"grey","skey");
console.log(colors); // 返回插入以后的新的数组;
console.log(a);  // 返回空数组
3-3 替换

替换的数量跟删除的数量不一定一致

var colors = ["red","green","blue","yellow","purple"];
var a = colors.splice(1,2,"grey","skey");
console.log(colors); // 返回替换以后的新的数组;
console.log(a);  // 返回数组删除的项

注:splice会改变源数组,但是splice本身只返回删除的项

var colors = ["red","green","blue","yellow","purple"];
var a = colors.splice(1,2,["wangwei"],["aini"]);
console.log(colors); 
console.log(a); 

image-20240131150627107

不会扁平化,区别于concat()方法

第一个参数可以接受负数,如果为负数,则指定的位置是数组长度加上该负值,或者从后向前定位

var colors = ["red","green","blue","yellow","purple"];
var a = colors.splice(-3,2,"aini","dilnur");
console.log(colors); 
console.log(a);

image-20240131150719795

// (倒数第三个位置往后数两个)
// 在实际的场景中,主要还是向数组的中部插入项;
// delete运算符删除数组元素:
// 通常使用delete运算符删除一个指定的元素(与删除对象属性一样)
var colors=["red","green","blue","yellow","purple"];
console.log(colors.length);
delete colors[1];
console.log(colors);
console.log(colors.length);

image-20240131150757687

// delete删除不会修改数组的length属性;如果从数组中删除一个元素,该数组就变成稀疏数组;
// 此删除并没有真正删除数组元素,仅删除元素内容;(此删除与为其赋undefined值是类似的,但有一些微妙的区别)
// 删除数组元素有多种方式,如设置length属性小于数据长度,会删除数组尾部的元素;
9-7-5 数组位置操作
5-1 ndexOf(),lastIndexOf()
// 1.两者会搜索整个数组中具有给定值的元素,并返回要查找的元素在数组中的第一个索引位置,或者在没找到返回-1;
// 2.这两个方法都是接收两个参数:要查找的项和(可选的)表示查找起点位置的索引;其中,indexOf()方法从数组的开头(位置为0)开始向后查找,lastIndexOf方向则从数组的末尾开始向前查找。

// 注:在查找时,使用是全等操作符
var numbers = [1,2,3,4,5,4,3,2,1];
console.log(numbers.indexOf(4));  // 3
console.log(numbers.lastIndexOf(4));  // 5
console.log(numbers.indexOf(4,4));   // 5   从索引4位置往后找“4”这个元素;
console.log(numbers.lastIndexOf(4,4)) // 3  从索引4位置往前找|“4”这个元素;
var p ={name:"wangwei"};
var p1 = p;
var people = [{name:"wangwei"},{name:"aini"}];
var people = [p1,{name:"aini"}];
var morePeople = [p];
console.log(people.indexOf(p));  //  0
console.log(morePeople.indexOf(p)); // 0
// 4.第二个参数也可以是负数,即为数组长度加上该负数的值为查找的索引开始位置,或者从后往前定位
var numbers = [1,2,3,4,5,4,3,2,1];
console.log(numbers.indexOf(4,-5));  // 5

封装一个函数,查找所有给定值的索引,并返回一个包含匹配索引的数组

var numbers = [1,2,3,4,5,4,3,2,1,4,2,7,9,4,2,3,5];
function findIndex(arr,index){
  var len = arr.length;
  var result = [];
  var pos = 0;
  while(pos<len){
    pos = arr.indexOf(index,pos);
    if(pos == -1) break;
    result.push(pos);
          pos++;
  }
return result;
}
console.log(findIndex(numbers,4));

字符串也有indexOf()和lastIndexOf()方法,它们和数组的方法功能类似

对于数组这些方法,也可以重写,本质上就是修改其prototype对象的属性方法,如

var arr = [1,2,3];
arr.push(4,5);
console.log(arr);
Array.prototype.push = function(){
    for(var i=0; i<arguments.length; i++)
        this[this.length] = arguments[i] * arguments[i];
}
arr.push(6,7);
console.log(arr);

image-20240131151144845

9-7-6 遍历数组
// 所谓的数组的遍历就是通过索引挨个取出数组元素;遍历目的是为了读取所有元素或者处理所有元素;使用for循环是遍历数组最常见的方法
var cities = ["beijing","nanjing","bengbu"];
for(var i=0;i<cities.length;i++) console.log(cities[i]);
for(var a in cities) console.log(a,cities[a]);
var o ={name:"aini",age:18,sex:true,hobby:"reading"};
var keys = Object.keys(o); // 把对象的属性放到数组里返回
console.log(keys);  
var  values = [];
for(var i=0,len=keys.length;i<len;i++){
  // values[i]=o[keys[i]]
  values.push(o[keys[i]]);
}
console.log(values);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

// 在遍历稀疏数组,或者需要排除null、undefined和不存在的元素时,需要先检测:
var arr = [1,,3,null,5,undefined,7];
for(var i=0,len=arr.length;i<len;i++){
  console.log(arr[i]);  // 可以把所有元素都打印出来;
} 
console.log("----------------");
for(var i in arr){
  console.log(arr[i]);  //会过滤掉empty元素
};
console.log("----------------");
for(var i in arr){
  if(!arr[i]) continue; //过滤掉null和undefined
  console.log(arr[i]);  
};
console.log("----------------");
for(var i in arr){
  if(arr[i] === undefined) continue; //过滤掉undefined
  console.log(arr[i]);  
};
console.log("----------------");
for(var i in arr){
  if(arr[i] === null) continue; //过滤掉null
  console.log(arr[i]);  
};
console.log("----------------");
for(var i=0,len=arr.length;i<len;i++){
  if(!(i in arr)){
    console.log(arr[i]);  // 可以用for循环过滤空元素;
  }
} ;

// (注:空元素的索引用in操作符判断时返回false;用for循环时可以用来过滤掉稀疏数组里的空元素;)
// 但是,for/in能够枚举继承的属性名,如添加到Array.prototype中的方法;由于这个原因,在数组上不应该使用for/in循环,除非使用额外的检测方法来过滤不用的属性,如:
Array.prototype.company ="zeronetwork";
var arr = [1,,3,null,undefined,6];
arr.custom = "wangwei";
arr[-18.1]=18;
for(var i =0;i<arr.length;i++){
  console.log(i,arr[i]);
}  //for循环只遍历元素;

//用 for in把所有可枚举的属性枚举出来;
for(var i in arr){
  console.log(i,arr[i]);
} //custom ,company,-18.1都打印出来;
//hasOwnProperty()方法过滤;
for(var i in arr){
  if(!arr.hasOwnProperty(i)){
    console.log(i,arr[i]); //只有 company zeronetwork;
  }
};
//跳过不是非负整数的i;
for(var i in arr){
  if(String(Math.floor(Math.abs(Number(i))))!== i) continue;
  console.log(i,arr[i]);
}
// (1)ES允许for/in循环以不同的顺序遍历对象的属性;
// (2)通常数组元素的遍历实现是升序的,但不能保证一定是这样的;特别地,如果数组同时拥有对象属性和数组元素,返回的属性名很可能是按照创建的顺序而非数值的大小顺序;
// (3)如何处理这个问题的实现各不相同,如果算法依赖于遍历的顺序,那么最好不要使用for/in而使用常规的for循环。
9-7-7 遍历数组的迭代方法
 (1// ECMAScript5为数组定义了5个迭代方法;2// 这些方法的第一个参数接收一个函数,并且对数组的每个元素调用一次该函数;3// 第二个参数是可选的:其是运行该函数的作用域对象(影响this的值);4// 或者说:调用的函数被当作是第二个参数的方法;5// 如果是稀疏数组,对不存在的元素不调用传递的函数6// 这些方法不会修改原始数组;7// 参数函数使用三个参数:数组元素、元素索引和数组对象本身;通常,只需要第一个参数,可以忽略后两个参数;7// 参数函数可以修改原始数组;8// 根据使用的方法不同,这个函数执行后的返回值可能也会不同;
7-1 forEach()

遍历数组,为每个元素调用指定的函数;该函数使用三个参数:数组元素、元素索引和数组本身;此方法没有返回值,本质上与使用for循环迭代数组一样

var numbers = [1,2,3,4,5,4,3,2,1];
numbers.forEach(function(item,index,array){
    console.log(item,index,array[index]);
});
第一个参数:// 数组元素;
第二个参数:// 数组索引;
第三个元素:// 数组对象本身
// 为每个元素加1
var data = [1,2,3,4];
data.forEach(function(v,i,a){
    a[i] = v + 1; //会改变原数组元素
});
console.log(data);

如果只关心数组元素的值,可以只使用一个参数,额外的参数将被忽略

// 求和
var data = [1,2,3,4];
var sum = 0;
data.forEach(function(value){  //第一个参数数组元素,相当于遍历多有元素;
    sum += value;   //不会改变原数组元素;
});
console.log(sum);
1// forEach()方法无法在所有元素都被传递给调用的函数之前终止遍历,即没有像for循环中使用的break语句;2// 如果要提前终止,必须把forEach()方法放在try块中,并能抛出一个异常:
	function foreach(a,f,t){
    try{a.forEach(f, t);}
    catch(e){
        if(e === foreach.break) return;
        else throw e;
    }
}
foreach.break = new Error("StopIteration");
7-2 every(),some()
1// 最相似的是every()和some(),它们都是数组的逻辑判定:用于判定数组元素是否满足某个条件;2// 对every(),传入的函数必须对每一项都返回true,这个方法才返回true;3// 而some()是只要传入的函数对数组中的某一项返回true,就会返回true,否则返回false
var numbers = [1,2,3,4,5,4,3,2,1];
var everyResult = numbers.every(function(item,index,array){
    return (item > 2);
});
console.log(everyResult);  // false
var someResult = numbers.some(function(item,index,array){
    return (item > 2);
});
console.log(someResult);    // true
4// 一旦every()和some()确定该返回什么值时,它们就会停止遍历数组元素;5// (some()在判定函数第一次返回true后就返回true,但如果判定函数一直返回false,它将会遍历整个数组;every()恰好相反,它在判定函数第一次返回false后就返回false,但如果判定函数一直返回true,它将会遍历整个数组)。6// 根据数学上的惯例,在空数组上调用时,every()返回true,some()返回false;
7-3 filter()方法
1// 返回的是数组元素是调用的数组的一个子集;2// 回调函数是用来逻辑判定的,该函数返回true或false;如果返回的是true或真值,则该函数处理的数组元素就被添加到返回的子集数组中;
(3// filter()方法会跳过稀疏数组中缺少的元素,它的返回数组总是密集的
var numbers = [1,2,3,4,5,4,3,2,1];
var filterResult = numbers.filter(function(item,index,array){
    return item>2;
});
console.log(filterResult);
var evenResult = numbers.filter(function(value,index){
    return index % 2 == 0;  // [1, 3, 5, 3, 1] index是索引
});
console.log(evenResult);
// 压缩稀疏数组
var sparse = [1,,,4];
var dense = sparse.filter(function(){
    return true;  //过滤稀疏元素
});
console.log(dense);
// 压缩并删除undefined和null元素
var sparse = [1,null,3,undefined,,6];
var dense = sparse.filter(function(v){
    return v !== undefined && v != null;
});
console.log(dense);

image-20240131152046419

(注:该方法返回的是符合条件的数组元素;)

7-4 map()
(1)// 将调用的数组的每个元素传递给回调函数,并将调用的结果组成一个新数组返回;
(2)// 其不会修改原始数组,如果是稀疏数组,返回的也是相同方式的稀疏数组,即具有相同的长度、

相同的缺失元素;

var numbers = [1,2,3,4,5,4,3,2,1];
var mapResult = numbers.map(function(item,index,array){
    return item*2;
});
console.log(mapResult);
var a = [1,null,3,undefined,5];
var b = a.map(function(v){
    return v * v;
});
console.log(b);

image-20240131152226167

不做任何处理

var spare = [1,,4,null,undefined,NaN,6];
var dense = spare.map(function(v,i,a){
 
});
console.log(dense);

image-20240131152259293

返回所有数组元素

var dense = spare.map(function(v,i,a){
 return v;
});
console.log(dense);

image-20240131152327943

能处理的元素进项处理,空元素返回空元素

var spare = [1,,4,null,undefined,NaN,6];
var dense = spare.map(function(v,i,a){
 return v*v;  //进行数据类型转换,由于null和undefined不能进行数据类型转换});
console.log(dense)     //,所以都返回NaN

image-20240131152359150

7-5 reduce()和reduceRight()
1// reduce()和reduceRight();这两个方法都会迭代数组的所有项,然后构建一个最终返回的值;2// 其中,reduce()方法从数组的第一项开始,逐个遍历到最后;而reduceRight则从数组的最后一项开始,向前遍历到第一项;3// 这两个方法都接收两个参数:调用的函数callbackfn和作为归并基础的初始值initialValue(可选的);4// 这个函数接收4个参数:前一个值、当前值、项的索引和数组对象;其返回的任何值都会作为第一个参数自动传给下一项;第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项,第二个参数就是数组的第二项

使用reduce()方法可以执行求数组中所有值之和的操作,如

// 求和
var values = [1,2,3,4];
var sum = values.reduce(function(prev,cur,index,array){
    return prev + cur;
});
console.log(sum);   // 10

image-20240131152527761

reduce()参数函数的参数也可以只使用两个参数,如:

// 求和
var values = [1,2,3,4];
var sum = values.reduce(function(prev,cur){
    return prev + cur;
});
console.log(sum);   // 10
// 求积
var product = values.reduce(function(prev,cur){
    return prev * cur;
});
console.log(product);  // 24
// 求最大值
var max = values.reduce(function(prev,cur){
    return prev > cur ? prev : cur;
});
console.log(max);  // 4

reduce()方法的第二个参数initialValue是回调函数的第一个参数previousValue的初始值;如:

// 把以上所有示例添加第二个参数,如:
var values = [1,2,3,4];
var sum = values.reduce(function(prev,cur){
    return prev + cur;
},2);  // 12

意思就是pre不是第一项,而是给pre赋一个值,cur从第一项开始迭代

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

var values = [1,2,3,4,5];
var sum = values.reduceRight(function(prev,cur,index,array){
    console.log(prev,cur);
    return prev + cur; //21
},6); //pre的初始值为6;
console.log(sum);  // 15
// 求2^(3^4),乘方操作的优先顺序是从右到左
var a = [2,3,4];
var big = a.reduceRight(function(accu, value){
    console.log(value);
    return Math.pow(value, accu);
});
console.log(big);
// 在空数组上,不带初始值参数的reduce()将导致类型异常;如果数组只有一个元素并且没有指定初始值,或者一个空数组并且指定了一个初始值,该方法只是简单的返回那个值而不会调用回调函数;
var a = [];
// var b1 = a.reduce(function(x,y){return x + y});  //no initial value  会报错
// var b2 = a.reduce(function(x,y){return x + x});  //也会报错
var b3 = a.reduce(function(x,y){return x + y},3); //只返回初始值

console.log(b3);  // 3
1// 使用reduce()还是reduceRight(),主要取决于要从哪头开始遍历数组;除此之外,它们完全相同。2// reduce()和reduceRight()是典型的函数式编程;有些地方,会把其回调函数称为化简函数,这个化简函数的作用就是用某种方法把两个值组合或化简为一个值,并返回化简后的值;如以上的示例,化简函数通过各种方法,组合了两个值

9-8 检测数组及类数组

9-8-1 检测数组
1.// 数组是具有自己特殊行为的对象,因此,确定某个对象是不是数组就很重要;
2.// typeof只能检测其是object的类型,而不能确定是否为Array类型;
3// 对于一个页面或者一个全局作用域而言,可以使用instanceof操作就可以得到检测结果,如:
var arr = [];
console.log(arr instanceof Array);
console.log(arr instanceof Object);
console.log(arr.constructor);
console.log(arr.constructor === Array);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注:实际上就是判断它的原型;

4. // 但instanceof与constructor有问题:如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,分别具有自己的全局对象,每个全局对象都有自己的Array构造函数,因此一个框架窗口中的对象不可能是另一个框架窗口的实例;
5.// 比如:如果从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中创建的数组分别具有各自不同的构造函数,它们就不是同一类对象
var frame = document.createElement('iframe');
document.body.appendChild(frame);
var FrameArray = window.frames[0].Array;  // 获取框架全局环境中的Array构造函数
var fa = new FrameArray(1,2,3);  // 在框架全局环境中创建数组
console.log(fa instanceof Array);   // false,在当前环境中判断fa是不是数组
console.log(Array.isArray(fa));  // true
console.log(fa.constructor === Array);  // false
6. // 解决方案:使用Object.prototype上的原生toString()方法判断;如:
Object.prototype.toString.call(arr)7.// 该方法返回一个[object NativeConstructorName]格式的字符串;但该方法不能检测非原生构造函数的函数名,因此自定义的构造函数都将返回[object Object],如:
var frame = document.createElement('iframe');
document.body.appendChild(frame);
var FrameArray = window.frames[0].Array;  // 获取框架全局环境中的Array构造函数
var fa = new FrameArray(1,2,3);  // 在框架全局环境中创建数组
console.log(fa instanceof Array);   // false,在当前环境中判断fa是不是数组
console.log(Array.isArray(fa));  // true
console.log(fa.constructor === Array);  // false
console.log(Object.prototype.toString.call(fa));
console.log(Object.prototype.toString.call(fa) === "[object Array]");

image-20240131154355685

var d = new Date();
console.log(Object.prototype.toString.call(d));

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

console.log(Object.prototype.toString.call(p));

image-20240131154436879

// 所以说如果不是系统自己定义的,而是我们自己定义的独享无法用
Object.prototype.toString.call();这个方法来判断,因为都会返回[object object];
<body>
  <iframe src="a.html" name="myFrame"></iframe>
<script>
// 修改上例
console.log(Object.prototype.toString.call(fa) === "[object Array]");   // true
 
function Person(){}
var p = new Person();
console.log(Object.prototype.toString.call(p));  // [object Object]
// 或者:

<script>
// a.html 中定义了 var aArr = [1,2];
window.onload = function(){
    var myFrame = window.frames["myFrame"];
    console.log(myFrame.aArr);
    console.log(myFrame.aArr instanceof Array);  // false
    console.log(Object.prototype.toString.call(myFrame.aArr) === "[object Array]"); // true
}
</script>

根据封装一个判断类型的函数:

function getType(target){
  var types = {
    "[object Object]" : "Object",
    "[object Array]" : "数组",
    "[object Number]" : "Number",
    "[object Boolean]" : "Boolean",
      "[object String]" : "String",
    "[object Date]" : "日期",
  };
  if(target == null) return null;
  if(typeof target == "object"){
    var toStr = Object.prototype.toString.call(target);
    return types[toStr];
  }else{
    return typeof target;
  }
}

console.log(getType(18));
console.log(getType({}));
console.log(getType([]));
console.log(getType(new Boolean()));
console.log(getType(new Date));
console.log(getType([1,2,"aini"]));
// 在ES5中,新增了Array.isArray()静态方法,该方法的作用是确定某个值是不是数组,而不管它是在哪个全局环境中创建的;如:
var colors = ["red","blue","yellow"];
console.log(Array.isArray(colors));

(万能的静态方法,都能判断)

9-8-2 数组去重
1// 使用一个空对象作为临时数组不重复元素值的载体,即把数组不重复元素值保存为空对象属性,再把该属性名添加到一个返回的数组中;
2// 最主要的原理:对象的属性名不可能重复,会覆盖,如
var arr = [1,3,2,3,4,2,3,2,1,1,2,3,4];
function getUnique(arr){
  var obj=[];
  var a = [];
  for(var i=0;i<arr.length;i++){
    if(!obj[arr[i]]){
      console.log(arr[i]);
      obj[arr[i]]="aini";
            a.push(arr[i]);
    }
  }
  return a;
}
console.log(getUnique(arr));

image-20240131154715745

9-8-3 类数组对象
ES数组的有一些特性是其他对象所没有的:
(1// 当有新的元素添加到列表中时,自动更新length属性;2// 设置length为一个较小值时将截断数组;3// 从Array.prototype中继承一些方法;4// 其类属性为Array;5// 这些特点让数组和常规的对象有明显的区别;6// 通常来说,把一个拥有数值length属性和对应非负整数属性的对象看作一种“类数组”对象;7// 这些“类数组”对象不能直接调用数组方法或者其length属性也没有特殊的行为(如字符串的length属性就是只读的),但可以使用数据遍历的方法来遍历它们,就像操作真正的数组一样;
// 字符串是一个类数组对象:
// Arguments对象是一个类数组对象:
str = "zeronetwork";
str.length = 5;
console.log(str.length); // 11

字符串Length属性只读,不可写

str = "zeronetwork";
console.log(str[0],str[1],str[2]); //  z e t

在客户端JS中,一些DOM方法,如
document.getElementsByTagName()也返回类数组对象,如:

var  lis = document.getElementsByTagName("li");
console.log(lis);
for(var i=0;i<lis.length;i++){
  var li = lis[i];
  li.onclick = function(e){
    alert(this.innerText); //在对应的li标签鼠标单击时弹出里面内容
  }
}

自定义一个类数组对象

如果让一个对象成为一个类数组对象,主要的做法是:利用属性名模拟数组元素索引;动态的length属性;甚至可以调用push等数组方法;

添加length属性

var obj = {0:"aini",1:"dinur",2:"dil"};
console.log(obj[0],obj[1],obj[2]);

for(p in obj){
  console.log(p,obj[p]);
};
//添加length属性
var obj = {0:"aini",1:"dinur",2:"dil",length:3};
console.log(obj[0],obj[1],obj[2]);
console.log(obj.length);
//动态添加length属性
var o = {};
var i = 0;
while(i<10){
  o[i]= i*i;
  i++;
};
o.length=i;
//把o当做数组一样遍历
var sum = 0;
for(var j=0;j<o.length;j++){
  sum+=o[j];
}
console.log(sum);

自定义一个类数组对象

//自定义类数组类及对象
function MyArray(){
  this.length = arguments.length;
  for(var i=0;i<this.length;i++){
    this[i]=arguments[i]
  }
};
var arr = new MyArray(4,3,5,"wangwei");
for (var i=0; i<arr.length;i++){
  console.log(arr[i]);
};

function YourSize(size){
  this.length=size;
  for(var i=0;i<size;i++){
    this[i]=[i];
  }
};
var a = new YourSize(3);
for(var x in a){
  this[i]=i;
  console.log(x,a[x]);
};
console.log(a);

添加push方法

//添加push方法,同时更新length属性;
var obj = {'0':'a','1':'b','2':'c',length:3,push:function(){
  this[this.length]=arguments[0];
  this.length+=arguments.length;
  return this.length;
}}
console.log(obj.length);
console.log(obj.push("aini"));

添加push方法;添加多个元素

//添加push方法,同时更新length属性;
var obj = {'0':'a','1':'b','2':'c',length:3,push:function(){
  for(var i=0;i<arguments.length;i++){
    this[this.length]=arguments[i];
    this.length++
  }
  return this;
}}
console.log(obj.length);
console.log(obj.push("aini","dilnur","diana","norah"));

可间接应用Array的方法:

//间接使用array的push方法;
var obj = {
  length:0,
  push:Array.prototype.push,
  splice:Array.prototype.splice
  };
console.log(obj.push("c","d"));  //2  
console.log(obj.length);   // 2
9-8-4 检测类数组对象

封装一个函数,用来检测类数组对象,如

var o = new String("aini");
// 判定 O 是否是一个类数组对象
// 字符串和函数都具有length属性,但可以用typeof进行排除
// 客户端,DOM文件节点也具有length属性,需要用额外判断o.nodeType!=3将其排除

function isArrayLike(o){
  if(o &&                  // o 是非null或undefined
  typeof o =="object" &&   // 是对象
  isFinite(o.length) &&   // 有限数
  o.length>0 &&            // 大于0
  o.length == Math.floor(o.length) &&  //length 属性是整数
  o.length < 2^32    // 
  )
  return true 
  else 
  return false;               
};
console.log(isArrayLike(o));

ES的数组方法为了能应用在类数组对象上而特意定义为通用的,但是,由于类数组没有继承自Array.prototype,就不能直接调用某些数组方法,只有间接的使用Function.call方法调用,如

var s1 = "aini like ";
console.log(s1.concat(" dilnur"));

var o = {0:"a",1:"b",2:"c",length:3};
o.length=3;
console.log(Array.prototype.join.call(o,"+"));
console.log(Array.prototype.slice.call(o,0));
console.log(Array.prototype.map.call(o,function(x){
  return x.toUpperCase();
}));

image-20240131155426285

注:concat方法是特例,可以直接用在字符串上面

作为数组的字符串

// 字符串的行为类似于只读的数组;除了用charAt()方法来访问单个字符外,还可以使用方括号;ES为可索引的字符串定义这些特性,主要为了更加简洁、可读和更高效的操作;
var s = "aini";
console.log(s.charAt(0)); // a
console.log(s[0]);   // a

字符串的这些类数组的行为,可以使用数组方法

var str = "ainilikedilnur";
console.log(Array.prototype.join.call(str,","));
console.log(Array.prototype.filter.call(str,function(s){
  return s
}).join(""));

image-20240131155641510

字符串是不可变值,所以当把它当作数组看待时,它们是只读的;故如push、sort、reverse、splice等数组方法会修改数组,但在字符串上是无效的;

9-8-5 二位数组和多维数组
// 二维或多维数组就是以数组作为数组元素的数组;多维数组的应用场景还是比较多的,比如一个班的学生成绩表;

但从本质上说,ES并不支持真正的二维或多维数组;

var a1 = [1,2,3];
var a2 = [4,5,6];
var a3 = [7,8,9];
var arr = [a1,a2,a3];
// 或者
var arr = [
    [1,2,3],
    [4,5,6],
    [7,8,9,10,12]
];
var arr = [
    [1,2,3],
    ['wangwei','wujing'],
    ['零点','网络']
];
var arr = new Array(
    new Array(1,2,3),
    ['wangwei','wujing'],
    new Array('零点','网络')
);
    

如果把二维数组当作另外一个数组的元素,那就是三维数组了,以此类推,就形成多维数组;

访问二维或多维数组,只要多次使用[ ]操作符即可,如:

var arr = [
    [1,2,3],
    ['wangwei','wujing'],
    ['零点','网络']
];
console.log(arr[1][1]); // 访问
arr[3] = new Array('one','two','three');  // 添加
arr[2][2] = "程序员";
console.log(arr);

二维或多维数组的遍历同一维数组一致,只不过使用了嵌套遍历

求最大值

var arr = [
  [88,67,44,98],
  [56,78,99,56],
  [79,95,78,92]
];
var max = arr[0][0];
for(var i=0;i<arr.length;i++){
  for (var j=0;j<arr[i].length;j++){
    arr[i][j]>max?(max=arr[i][j]):max;
  }
};
console.log(max);

九九乘法表

var table = new Array(9);
for(var i=0;i<table.length;i++) table[i]=new Array(9);
for(var row=0;row<table.length;row++){
  for(var col=0;col<table[row].length;col++){
    table[row][col]=row*col;
  }
}
console.log(table[5][9]);

function getTable(arr){
  var table = document.createElement("table");
  for(var i=1; i<arr.length;i++){
    var tr = document.createElement("tr");
    for(var j=1;j<arr[i].length;j++){
      var td = document.createElement("td");
      td.innerText=i+"*"+j+"="+arr[i][j];
      tr.appendChild(td);
    }
    table.appendChild(tr);
  }
  document.body.appendChild(table);
}
getTable(table);

10,Date日期对象

GMT// 格林尼标准时;
UTC// 格林尼治标准时间;
时区:// 共有24区,东12区,西12区,一个时区一小时;
计算机元年:// 1970年1月1日 0:00:00 用于计时的开始

Date使用的是UTC// 是所有时区的基准标准时间,是1970年1月1日 00:00:00 开始经过的毫秒数保存日期;

10-1 Date对象的创建

d = new Data()  // 以当前日期和时间创建date对象;
d = new Date(0) // 以1970-1-1 00:00:00 的毫秒数创建date对象;
d = new Date(2020,7,18) // 就表示创建了一个2020年8月18号的日期对象;

image-20240131160607166

image-20240131160614482

new Date()里面直接传年份注意:
// JS里的月份是 0~11 分别表示1~12月;所以计算机里 0 表示1月,1表示2月,11就是12月;
d = new Date(2020,7,18) //得到的是2020年8月18日;

// ECMAScript提供了两个静态方法:
Date.parse()和Date.UTC()

10-2 Date.parse()

// 跟时区无关,月份基于1;
Date.parse()方法接受一个表示日期的字符串参数,返回一个时间戳(毫秒数)1// 日期字符串应该符合 RFC 2822 和 ISO 8061 这两个标准:
2// ISO 8601扩展格式 YYYY-MM-DDTHH:mm:ss:ssssZ,如2020-05-25T00:00:00;(yyyy4位年份、MM月份、DD天、HH时、mm分、ss秒、ssss毫秒)

通常见的日期格式

1// mm/dd/yyyy 如: 3/21/2009,即月/日/年
2// yyyy/mm/dd 如: 2009/3/21
3// mmmm dd,yyyy 如: Apr 21,2009,即英文月名 日,年,即January 12,2010
console.log(Date.parse("May 25,2020"));
console.log(Date.parse('2018-07-22'))
console.log(Date.parse('2018-07'))
console.log(Date.parse('2018'))
console.log(Date.parse('07/22/2018'))
console.log(Date.parse('2018/07/22'))
console.log(Date.parse('2018/7/22'))
console.log(Date.parse('July 22, 2018'))
console.log(Date.parse('July 22, 2018 07:22:13'))
console.log(Date.parse('2018-07-22 07:22:13'))
console.log(Date.parse('2018-07-22T07:22:13'))
// 注:如果传入Date.parse()方法的字符串不能表示日期,那么它会返回NaN;

根据parse()返回值创建Date对象;

var d = new Date(Date.parse("May 25, 2020"));
console.log(d)

// Mon May 25 2020 00:00:00 GMT+0800 (中国标准时间)

实际上,如果直接将表示日期的字符串传递给Date构造函数,也会在后台调用Date.parse(),两者是等价的,如:

var d = new Date("May 25, 2020");

注意:月份前面有0和没有0是不一样的(中间连接符是‘-’的时候才会有区别)其他都是GTM时间

var time = Date.parse('2019-04-03'); // +8区时间
var time1 = Date.parse('2019-4-03'); // 标准UTC时间
var d = new Date(time);
var d1 = new Date(time1);
console.log(d);
console.log(d1);

image-20240131161108431

注:日期对象在不同浏览器实现的并不统一,比如,传入了超出范围的值:

(负数可以直接取正,前面的0可以省略)

var d = new Date("January 33,2020");
console.log(d)  // Invalid Date
// 在解析January 33,2020,有些浏览器返回:Invalid Date;IE返回:Sun Feb 02 2020(把超出的时间往后自动推算);

// UNIX 时间戳的原因以秒(seconds)为单位。JavaScript 以毫秒(milliseconds)为单位记录时间。

可在使用UNIX 时间戳去实例化Date 对象;

var timestamp = 1591866649;
var d = new Date(timestamp * 1000);
console.log(d);

10-3 Date.UTC()

Date.UTC方法://(参数不用引号,而且需要逗号隔开)月份是基于0的
语法:
// date.UTC(year,month,[date,hrs,min,sec,ms])
必需参数:// year,month

// 其参数为日期中的年,月(基于0),日,小时(0到23),分,秒,毫秒,其中年月必选;如果没有提供日,默认为1,如果省略其他参数,则统统默认为0;

// 至少应该是3个参数,但是大多数 JavaScript 引擎都能解析 2 个或 1 个参数;
var d = Date.UTC(2020);
var d1 = Date.UTC(2020,6);  // 毫秒数1593561600000
var d2 = new Date(Date.UTC(2020,6)); 
var d3 = new Date(Date.UTC(2020,6,6,17,55,55)); // 自动添加时区,返回当地日期和时间
var d4 = new Date(2020,6,10); //月份从0开始,6即是7月

image-20240131161405540

// 如果没有任何关于时区的信息,会将日期视为 UTC ,并自动执行到当前计算机时区的转换;
// 可以直接把UTC参数传递给Date()构造函数,如:
var d=new Date(2020,6); // Wed Jul 01 2020 00:00:00 GMT+0800 
var d = new Date(2020,6,6,17,55,55); // 即为GMT时间
console.log(d);
// 当初始化一个 Date 对象时可以选择时区,可以通过添加 +HOURS 的格式,或者通过一个被圆括号包裹的时区名来描述一个时区:

注:兼容性有点问题

console.log(new Date('Jun 7,2020 13:51:01 +0700'));
console.log(new Date('Jun 7,2020 13:51:01 (CET)'));   // CET欧洲中部时间

补充:

var time2 = Date.parse('2022-4-03')
console.log(new Date(time2))
var time3 = Date.parse('2022-04-03')
console.log(new Date(time3))

var time2 = Date.parse('2022/4/03')
console.log(new Date(time2))
var time3 = Date.parse('2022/04/03')
console.log(new Date(time3))
var time2 = Date.parse('2022,4,03')
console.log(new Date(time2))
var time3 = Date.parse('2022,04,03')
console.log(new Date(time3))
var time2 = Date.parse('2022-4-03 10:56:36')
console.log(new Date(time2))
var time3 = Date.parse('2022-04-03T10:56:36')
console.log(new Date(time3))

image-20240131161609614

10-3 总结

(1)// Date.parse() 当只有年月日时,且连接符是 - 的时候月份前面有0和没有0是有区别的,没有0则是UTC时间,有0的话加8小时;
var time2 = Date.parse('2022-4-03')
console.log(new Date(time2))
var time3 = Date.parse('2022-04-03')
console.log(new Date(time3))

image-20240131161714933

2// 当连接符不是 - 的时候,月份面前加不加0都一样,都返回UTC时间3// 当年月份后面加上 时间以后,月份前面有没有有没有0也都一样,都返回正确的  时间4// 只有当 - 充当连接符,而且月份前面有0是,年月份跟时间之间才可以加T,当使用其他连接符或者用 - 当连接符,但是月份前面没有0 时就会报错
var time2 = Date.parse('2022-4-03 10:56:36')
var time3 = Date.parse('2022-04-03T10:56:36')
var time2 = Date.parse('2022,4,03 10:35:56')
var time3 = Date.parse('2022,04,03 10:35:56')

image-20240131161830826

10-4 Date 对象的方法

1.Date():// 返回当日的日期和时间。
2.getDate():// 从 Date 对象返回一个月中的某一天 (1 ~ 31)。
3.getDay():// 从 Date 对象返回一周中的某一天 (0 ~ 6)。
4.getMonth():// 从 Date 对象返回月份 (0 ~ 11)。
5.getFullYear():// 从 Date 对象以四位数字返回年份。
6.getYear():// 请使用 getFullYear() 方法代替。
7.getHours():// 返回 Date 对象的小时 (0 ~ 23)。
8.getMinutes():// 返回 Date 对象的分钟 (0 ~ 59)。
9.getSeconds():// 返回 Date 对象的秒数 (0 ~ 59)。
10.getMilliseconds():// 返回 Date 对象的毫秒(0 ~ 999)。
11.getTime():// 返回 1970 年 1 月 1 日至今的毫秒数,与valueOf()返回值相同。
12.getTimezoneOffset():// 返回本地时间与格林威治标准时间 (GMT) 的分钟差。
13.getUTCDate():// 根据世界时从 Date 对象返回月中的一天 (1 ~ 31)。
14.getUTCDay():// 根据世界时从 Date 对象返回周中的一天 (0 ~ 6)。
15.getUTCMonth():// 根据世界时从 Date 对象返回月份 (0 ~ 11)。
16.getUTCFullYear():// 根据世界时从 Date 对象返回四位数的年份。
17.getUTCHours()://  根据世界时返回 Date 对象的小时 (0 ~ 23)。
18.getUTCMinutes():// 根据世界时返回 Date 对象的分钟 (0 ~ 59)。
19.getUTCSeconds():// 根据世界时返回 Date 对象的秒钟 (0 ~ 59)。
20.getUTCMilliseconds():// 根据世界时返回 Date 对象的毫秒(0 ~ 999)。
21.parse():// 返回1970年1月1日午夜到指定日期(字符串)的毫秒数。
22.setDate():// 设置 Date 对象中月的某一天 (1 ~ 31)。
23.setMonth():// 设置 Date 对象中月份 (0 ~ 11)。
24.setFullYear():// 设置 Date 对象中的年份(四位数字)。
25.setYear():// 请使用 setFullYear() 方法代替。
26.setHours():// 设置 Date 对象中的小时 (0 ~ 23)。
27.setMinutes():// 设置 Date 对象中的分钟 (0 ~ 59)。
28.setSeconds():// 设置 Date 对象中的秒钟 (0 ~ 59)。
29.setMilliseconds():// 设置 Date 对象中的毫秒 (0 ~ 999)。
30.setTime():// 以毫秒设置 Date 对象。
31.setUTCDate():// 根据世界时设置 Date 对象中月份的一天 (1 ~ 31)。
32.setUTCMonth():// 根据世界时设置 Date 对象中的月份 (0 ~ 11)。
33.setUTCFullYear():// 根据世界时设置 Date 对象中的年份(四位数字)。
34.setUTCHours():// 根据世界时设置 Date 对象中的小时 (0 ~ 23)。
35.setUTCMinutes():// 根据世界时设置 Date 对象中的分钟 (0 ~ 59)。
36.setUTCSeconds():// 根据世界时设置 Date 对象中的秒钟 (0 ~ 59)。
37.setUTCMilliseconds():// 根据世界时设置 Date 对象中的毫秒 (0 ~ 999)。
toSource():// 返回该对象的源代码。
toString():// 把 Date 对象转换为字符串。
toTimeString():// 把 Date 对象的时间部分转换为字符串。
toDateString():// 把 Date 对象的日期部分转换为字符串。
toGMTString():// 请使用 toUTCString() 方法代替。
toUTCString():// 根据世界时,把 Date 对象转换为字符串。
toLocaleString():// 根据本地时间格式,把 Date 对象转换为字符串。
toLocaleTimeString():// 根据本地时间格式,把 Date 对象的时间部分转换为字符串。
toLocaleDateString():// 根据本地时间格式,把 Date 对象的日期部分转换为字符串。
toISOString():// 返回对应的UTC时间的 ISO8601 写法,如2012-12-31T16:00:00.000Z,
toJSON():// 返回值同toISOString()
UTC():// 根据世界时返回 1970 年 1 月 1 日 到指定日期的毫秒数。
valueOf():// 返回 Date 对象的原始值。

以上方法大概分为三种:to方法、get方法和set方法。

10-4-1 to方法
1)to方法-日期格式化方法:
// date()类型还有一些专门用于将日期格式化为字符串的方法,如:
toString()toDateString()// 以特定于实现的格式显示星期几、月、日和年;
toTimeString()// 以特定于实现的格式显示时、分、秒和时区;
toLocaleDateString()// 以特定于地区的格式显示星期几、月、日和年;
toLocaleTimeString()// 在特定于地区的格式显示 时、分、秒;
toUTCString()// 以特定于实现的格式显示UTC日期;
toISOString()// 返回ISO表示的日期;
toGMTString()方法,// 这是一个与toUTCString()等价的方法,其存在的目的在于确保向后兼容;不过ECMAScript推荐使用  toUTCString()方法;

继承的方法

与其他引用类型一样,Date类型也重写了toLocaleString()、toString()和valueOf()方法;但这些方法的返回值与其他类型中的方法不同。

valueOf()方法:// 返回日期的毫秒数;
toString()方法:// 通常返回带有时区信息的日期和时间;其中时间一般以军用时间(即小时从0到23);
	var d = new Date();
	console.log(d); // 默认就是调用toString()方法
console.log(d.valueOf()); // 返回毫秒数
console.log(Date.parse(new Date())); //返回毫秒数
console.log(d.toString()); 

image-20240131170501327

toLocaleString()//会按照与浏览器设置的地区相适应的格式返回日期和时间;即时间格式中会包含AM或PM,但不会包含时区信息;
var d = new Date();
console.log(d.toLocaleString()); // 2022/12/17 13:16:37

注:真实场景中,toString()和toLocaleString()没有什么用,仅在调试代码时使用;

至于valueOf()方法,返回的是毫秒数,因此,可以方便的使用比较操作来比较日期,如:

var d1 = new Date(2019,0,1);
var d2 = new Date(2020,2,1);
console.log(d1);
console.log(d2);

image-20240131170621676

var d1 = new Date(2019,0,1);
var d2 = new Date(2020,2,1);
console.log(d2-d1); // 毫秒数相减
console.log(d2.valueOf()-d1.valueOf());

image-20240131170637903

相减默认调用的就是valueOf方法

+默认调用toString()方法,进行字符串拼接

var d1 = new Date(2019,0,1);
var d2 = new Date(2020,2,1);
console.log(d2+d1); // 字符串拼接,默认调用toString()方法

image-20240131170745089

注意日期比较的惯性思维,如2019.1.1早于2020.2.1日,但后者返回的毫秒数大。

10-4-2 get 方法
var d = new Date();
console.log(d.getDate()); //18
console.log(d.getDay());  //4
console.log(d.getFullYear()); //2020
console.log(d.getMonth()); //5 (starts from 0)
console.log(d.getHours()); //17
console.log(d.getMinutes()); //30
console.log(d.getSeconds()) //13
console.log(d.getMilliseconds()); //765
console.log(d.getTime()) //1591868420160
10-4-3 set 方法
var d = new Date();
d.setDate(6);
d.setFullYear(2022);
d.setMonth(4);
d.setHours(4);
d.setMinutes(4);
d.setSeconds(4);
d.setMilliseconds(123);
d.setTime(1598765678999);
console.log(d);
// 注:setDate 和 setMonth 从 0 开始编号;
// 这些方法基本是跟getter方法一一对应的,但是没有setDay方法,因为星期几是计算出来的,而不是设置的;
set方法中的参数如果超出它的范围,会进位,称为冒泡,如:date.setHours(48),这也会将日期数变大;
var date = new Date();
date.setFullYear(2022,1,18);
// date.setMonth(24);
date.setMonth(2,8);
date.setHours(16,18,28,208);
console.log(date.toLocaleString());
// 如果参数是负数,表示从上个月的最后一天开始减去相应的单位数
// 以上的方法都有一个相对应的 UTC set方法:
获取当前时间戳:

获取当前时间戳:

console.log(new Date().getTime());

console.log(Date.now());

Date.now()方法返回表示调用这个方法时的日期和时间的毫秒数;其简化了Date.getTime()方法,如:

如果有些浏览器不支持Date.now(),可以使用+操作符获取Date对象的时间戳,如:

var start = +Date.now();
num = 0;
for(var i=0;i<100000000;i++){
num+=1;
num+=2;
} // 模拟其他处理代码
var stop = +Date.now();
var result = stop-start;
console.log(result)
console.log(num)

10-5 日期计算

var d1 = new Date("2020-06-18");
var d2 = new Date("2020-06-19");
console.log(d1 - d2);  // -86400000
console.log(d1 + d2);  // 返回两个日期的字符串拼接
// 或
var d1 = new Date('July 18,2020 14:10:18');
var d2 = new Date('July 19,2020 14:10:18');
var diff = d2.getTime() - d1.getTime();
console.log(diff);

image-20240131171148228

// getTime() 方法返回以毫秒计的数字,所以需要将当日时刻计入;如:July 18, 2020 14:14:14 不等于July 18, 2020。在这种情况下,可以使用 setHours(0, 0, 0, 0) 来重置当日时刻;
10-5-1 计算本年度还剩下多少天
function leftDays() {
  var today = new Date();
  var endYear = new Date(today.getFullYear(), 11, 31, 23, 59, 59, 999);
  var msPerDay = 24 * 60 * 60 * 1000;
  return Math.round((endYear.getTime() - today.getTime()) / msPerDay);
}
console.log(leftDays());
10-5-2 中文月份和日期
var d = new Date();
var month = d.getMonth();
var week = d.getDay();
var monthArr = ['一月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月'];
var weekArr = ['星期一','星期二','星期三','星期四','星期五','星期六','星期天']
console.log(monthArr[month],weekArr[week-1])
10-5-3 获取日期部分信息
var d = new Date()
Date.prototype.dataPart = function(part){
  if(!part) part = 'd';
  else part = part.toLowerCase();
  var monthArr = ['一月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月'];
  var weekArr = ['星期一','星期二','星期三','星期四','星期五','星期六','星期天'];
  
  switch(part){
    case 'y':
      return this.getFullYear();
      break;
    case 'm':
      return monthArr[this.getMonth()];
      break;
    case 'd':
      return this.getDate();
      break;
          case 'h':
      return this.getHours();
      break;
    case 'm':
      return this.getMinutes();
      break;
    case 's':
      return this.getSeconds();
      break;
    case 'w':
      return weekArr[this.getDay()];
      break;
    default:
      return this.getDate();
  }
  return this.getDate();
}
console.log(d.dataPart('d'))
console.log(d.dataPart('s'))
console.log(d.dataPart('h'))
console.log(d.dataPart('y'))

// 在date对象原型中定义方法,只要是用date对象创建的日期都能用这个方法
10-5-4 还有多长时间退休
function retireDays(birthday,age){
    var d1 = new Date(birthday).getFullYear();
    var d2 = new Date().getFullYear();
    var old = d2 - d1;
    console.log("现在你的年龄是:" + old,",将于" + (d1 + age) + "退休");
    if(age - old > 0){
        console.log("还差"+(age - old)+"年退休")
    }else{
        console.log("你已经退休啦,好好享受老年生活吧");
    }
}
retireDays('2020.6.6',60);
10-5-5 网页时钟
function checkTime(time){
  if (time < 10){
    time = '0'+time
  }
  return time
}
function showTime(){
    var d = new Date();
    var h = checkTime(d.getHours());
    var m = checkTime(d.getMinutes());
    var s = checkTime(d.getSeconds());
    myP = document.getElementById("myP");
    myP.innerHTML = h +':'+ m +':'+s;
    timer = setTimeout('showTime()',1000);
}
showTime()
10-5-6 倒计时
function getCountDown(d){
    var d1 = new Date();
    var d2 = new Date(d);  // 
    var diff = d2 - d1;     // 相差毫秒数
    var o = {};
    if(diff >= 0){
        var day = Math.floor(diff / 1000 / 60 / 60 / 24);  // 剩下多少天
        var hour = Math.floor(diff / 1000 / 60 / 60 % 24);   // 剩下多少小时
        var minute = Math.floor(diff / 1000 / 60 % 60);     // 剩下多少分
        var second = Math.floor(diff / 1000 % 60);  // 剩下多少秒
        o.stop = false;
        o.str = "距离"+d+"     还剩下"+day+"天"+hour+"小时"+minute+"分"+second+"秒";
    }else{
        o.stop = true;
        o.str = "已时结束";
    }
    return o;
}
var timer = setInterval(function(){
    var mydate = document.getElementById('mydate');
    mydate.innerHTML = getCountDown('2022.12.31 23:59:59').str;
    if(getCountDown('2022.12.31 23:59:59').stop) clearInterval(timer);
},1000);
10-5-7 计算某个日期加上天数
function addDate(date,days){
    var d = new Date(date);
    d.setDate(d.getDay() + days);
    var month = d.getMonth() + 1;
    var day = d.getDate();
    if(month < 10)
        month = "0" + month;
    if(day < 10)
           day = "0" + day;
    var value = d.getFullYear() + "-" + month + "-" + day;
    return value;
}
console.log(addDate('2020-6-6',50));
console.log(addDate('2020-6-6',-6));
10-5-8 判断闰年

四年一闰,百年不闰,四百年再闰

Date.prototype.isLeapYear = function(){
    return (this.getFullYear() % 4 == 0 && ((this.getFullYear() % 100 !=0) || (this.getFullYear() % 400 == 0)));
}
var d = new Date();
console.log(d.isLeapYear());
d.setFullYear(2019);
console.log(d.isLeapYear());
10-5-9 计算两个日期相差的天数
function daysDiff(dateOne,dateTwo){
    var oneMonth = dateOne.substring(5, dateOne.lastIndexOf('-'));
    var oneDay = dateOne.substring(dateOne.length,dateOne.lastIndexOf('-') + 1);
    var oneYear = dateOne.substring(0, dateOne.indexOf('-'));
    var twoMonth = dateTwo.substring(5, dateTwo.lastIndexOf('-'));
    var twoDay = dateTwo.substring(dateTwo.length, dateTwo.lastIndexOf('-') + 1);
    var twoYear = dateTwo.substring(0, dateTwo.indexOf('-'));
    var diff = ((Date.parse(oneMonth+'/'+oneDay+'/'+oneYear) - Date.parse(twoMonth+'/'+twoDay+'/'+twoYear)) / 86400000);
    return diff;
}
console.log(daysDiff('2020-6-6','2020-5-30'));
10-5-10 格式化输出
Date.prototype.format = function(fmt){
    var o = {
        "M+" : this.getMonth() + 1,
        "d+" : this.getDate(),
        "h+" : this.getHours(),
        "m+" : this.getMinutes(),
        "s+" : this.getSeconds(),
        "q+" : Math.floor((this.getMonth() + 3) / 3),
        "S"  : this.getMilliseconds()
    };
    if(/(y+)/.test(fmt)){
        fmt = fmt.replace(RegExp.$1,
            (this.getFullYear() + "").substr(4 - RegExp.$1.length));
                }
    for(var k in o){
        if(new RegExp("(" + k + ")").test(fmt)){
            fmt = fmt.replace(RegExp.$1,
                RegExp.$1.length ===1
                    ? o[k]
                    : ("00" + o[k]).substr(("" + o[k]).length));
        }
    }
    return fmt;
};
var d = new Date(2020,6,6,0,0,0);
console.log(d);
console.log(d.format('yyyy年MM月dd日'));  // 2020年07月06日
console.log(d.format('yyyy年MM月d日 hh:mm:ss'));  // 2020年07月6日 00:00:00
10-5-11 时间控件
<body>
<input type="date" id="mydate">
<input type="datetime" id="mydatetime">
<input type="datetime-local" id="mylocaldate">
<input type="time" id="mytime">
<input type="button" value="提交" οnclick="show()">
<script>
function show(){
    var mydate = document.getElementById('mydate');
    var mydatetime = document.getElementById('mydatetime');
    var mylocaldate = document.getElementById('mylocaldate');
    var mytime = document.getElementById('mytime');

    console.log(mydate.value);
    console.log(mydatetime.value);
    console.log(mylocaldate.value);
    console.log(mytime.value);
}
</script>
</body>

image-20240131171819661

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

10-5-12 制作日历
function getDays(y,m){
    var d = new Date(y,m);
    d.setMonth(m+1);
    d.setDate(0);
    return d.getDate();
}
function changeDay(target,d){
    var year = d.getFullYear();
    var month = d.getMonth();
    var date = d.getDate();
    var week = d.getDay();
    var days = getDays(year,month); //一个月内有多少天

    
    var current = new Date();
    currentyear = current.getFullYear();
    currentmonth = current.getMonth();
    currentday = current.getDate();
    currentweek = current.getDay();
    var daylist = document.getElementById('daylist');
    for(var i=daylist.children.length-1;i>=0;i--){
        daylist.removeChild(daylist.childNodes[0]);
    }
    var d1 = d
    d1.setDate(1);
    var firstweek = d1.getDay(); //获取当月1号对应星期几
    for(var i=0;i<firstweek%7;i++){
        var li = document.createElement('li');
        daylist.appendChild(li)
    }
    
    for(var i=1;i<=days;i++){
        var li = document.createElement('li');
        li.innerHTML = i;
        if((i<currentday && month == currentmonth && year == currentyear) || (month<currentmonth && year==currentyear) || (year < currentyear)){
            li.className = "lightgray";
        }else if(i== currentday && month == currentmonth && year == currentyear){
            li.className = 'currentbox'
        }else{
            li.className = 'darkgray'
        }
                daylist.appendChild(li)
    }
    document.getElementById(target+'-month').innerHTML = month + 1 + '月';
    document.getElementById(target+'-year').innerHTML = year;
}
var d = new Date();
changeDay('calender',d)
var prev = document.getElementById('prev');
var next = document.getElementById('next');
prev.addEventListener('click',function(){
    d.setMonth(d.getMonth()-1);
    changeDay('calender',d)
},false);
next.addEventListener('click',function(){
    d.setMonth(d.getMonth()+1);
    changeDay('calender',d)
},false);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

10-6 Datejs 日期库

// 官网:www.datejs.com
10-6-1 返回特定的日期
console.log(Date.today());
console.log(Date.today().toString('yyyy-MM-d HH:m:s'))
console.log(Date.today().next().friday().toString('yyyy-MM-d HH:m:s'))
console.log(Date.today().last().friday().toString('yyyy-MM-d HH:m:s'))
console.log(Date.last().week().toString('yyyy-MM-d HH:m:s'))
10-6-2 判断
console.log(Date.today().is().sunday());
console.log(Date.today().is().saturday());
console.log(Date.today().is().dec());
console.log(Date.today().is().weekday()); //判断是不是工作日
10-6-3 返回加一天或减一天后的日期

可以是负数

console.log(Date.today().addDays(1));
console.log(Date.today().add(1).day());
console.log(Date.today().add(1).month());
console.log(Date.today().add(1).year());
console.log(Date.today().add(1).week());
10-6-4 返回某个月的某个日期
console.log(Date.monday().toString('yyyy-MM-d HH:m:s'));
console.log(Date.next().monday().toString('yyyy-MM-d HH:m:s'));
console.log(Date.april().toString('yyyy-MM-d HH:m:s'));
console.log(Date.today().first().monday().toString('yyyy-MM-d HH:m:s'));  //本月第一个星期一
console.log(Date.today().second().monday().toString('yyyy-MM-d HH:m:s'));  //本月第二个星期二
console.log(Date.today().final().sunday().toString('yyyy-MM-d HH:m:s')); //当前月的最后一个星期天
console.log(Date.april().final().monday().toString('yyyy-MM-d HH:m:s')); //返回四月的最后一个monday
10-6-5 返回今天的某个时刻
console.log(Date.today().at('4:18pm').toString('yyyy-MM-d HH:m:s'));
10-6-6 根据对象构件日期
var t = {hour:18,minute:30}
console.log(Date.today().at(t).toString('yyyy-MM-d HH:m:s'));
10-6-7 日期解析转换
console.log(Date.parse('t'));
console.log(Date.parse('tomorrow'));
console.log(Date.parse('next friday'));
console.log(Date.parse('yesterday'));
console.log(Date.parse('last monday'));
console.log(Date.parse('July 8th, 2020'))
console.log(Date.parse('July-08-2020'))
console.log(Date.parse('July/08/2020'))
console.log(Date.parse('2020 6 16'))
console.log(Date.parse('2020.6.16'))
console.log(Date.parse('6.16.2016'))
console.log(Date.parse('16:30:30'))
console.log(Date.parse('4:30:30 pm'))
console.log(Date.parse('t + 5d')); //今天加上5天
console.log(Date.parse('t + 5m'));//今天加上五个月 
console.log(Date.parse('t - 1m'));//今天减去5个月 
console.log(Date.parse('+'));    //今天加上一天
console.log(Date.parse('-y'));    //今天加上一天
10-6-8 链式操作
/添加1个月零5天,然后检查该日期是否为星期五
Date.today().add({ months: 1, days: 5 }).is().fri();
//输入日期,然后移至下一个星期五,减去一个月
Date.parse("10-July-2004").next().friday().add(-1).month();
10-6-9 日期比较
Date.today().equals( Date.parse("today"));  // true
Date.parse("last Tues").equals(Date.today());   // true|false
 
Date.equals(Date.today(), Date.parse("today")); // true|false
Date.compare(Date.today(), Date.parse("today"));    // 1 = greater, -1 = less than, 
 
Date.today().compareTo(Date.parse("yesterday"));    // 1 = greater, -1 = less than, 0 = equal
Date.today().between(startDate, endDate);   // true|false
10-6-10 转换为字符串

注意该format参数对于该.toString()功能是可选的。如果未提供format,.toString()则将调用本地JavaScript Date 函数。

s:// 分钟介于0到59之间的秒数,如:0 to 59
ss:// 如果需要,分钟的秒数,前导零,如:00 to 59
m:// 每小时的分钟数,介于0到59之间,如:0 or 59
mm:// 每小时的分钟,前导零(如果需要),如:00 to 59
h:// 1到12之间的一天中的小时,如:1 to 12
hh:// 如果需要,一天中的小时数,前导零,如:01 to 12
H// 0-23之间的一天中的小时,如:0 to 23
HH// 如果需要,一天中的小时数,前导零,如:00 to 23
d:// 每月的1到31之间的日期,如:1 to 31
dd:// 如果需要的话,该月的某天前导零。如:01 to 31
ddd:// 缩写的天名,如:Mon to Sun
dddd:// 全日名称,如:Monday to Sunday
M// 一年中的1-12点之间的月份,如:1 to 12
MM// 一年中的前导零(如果需要),如:01 to 12
MMM// 缩写的月份名称,如:Jan to Dec
MMMM// 完整的月份名称,如:January to December
yy:// 将年份显示为两位数,如:99 or 07
yyyy:// 显示完整的四位数年份,如:1999 or 2007
t:// 显示AM / PM指示符的第一个字符,如:A or P
tt:// 显示AM / PM指示符,如:AM or PM
S// 当日的序数后缀,如:st, nd, rd, or th

自定义日期和时间格式说明符

d:// shortDate格式模式,如:M/d/yyyy
D// longDate 格式模式,如:dddd, MMMM dd, yyyy
F// fullDateTime 格式模式,如:dddd, MMMM dd, yyyy h:mm:ss tt
m:// monthDay 格式模式,如:MMMM dd
r:// rfc1123 格式模式,如:ddd, dd MMM yyyy HH:mm:ss GMT
s:// sortableDateTime 格式模式,如:yyyy-MM-ddTHH:mm:ss
t:// shortTime 格式模式,如:h:mm tt
T// longTime 格式模式,如:h:mm:ss tt
u:// universalSortableDateTime 格式模式,如:yyyy-MM-dd HH:mm:ssZ
y:// yearMonth 格式模式,如:MMMM, yyyy

分隔符

正斜杠、空格、- 连字号、逗号

console.log(Date.today().toString());
console.log(Date.today().toString('M/d/yyyy'))
console.log(Date.today().toString('d'));
console.log(Date.today().toString('MMMM dS,yyyy'));
new Date().toString();  //星期三2007年10月31日格林尼治标准时间0700(太平洋夏令时间)
new Date().toString("M/d/yyyy");    //2007年10月31日
 
Date.today().toString("d-MMM-yyyy");    //2007年10月31日
new Date().toString("HH:mm");           // 16:18
 
Date.today().toString("MMMM dS, yyyy"); // April 12th, 2008
Date.today().toShortDateString();// "10/31/2007". 根据Date.CultureInfo.shortDatePattern特定于区域性
Date.today().toLongDateString();// "Wednesday, October 31, 2007". 根据Date.CultureInfo.longDatePattern特定于区域性
 
new Date().toShortTimeString();// "4:18 PM". 根据Date.CultureInfo.shortTimePattern特定于区域性
new Date().toLongTimeString();// "4:18:34 PM". 根据Date.CultureInfo.longTimePattern特定于区域性

核心用法

/将日期设置为当前月份和年份的15号;
//其他对象值包括year|month|day|hour|minute|second。
Date.today().set({ day: 15 });
Date.today().set({ year: 2007, month: 1, day: 20 });
//将Date添加2天。其他对象值包括 year|month|day|hour|minute|second.
Date.today().add({ days: 2 });
Date.today().add({ years: -1, months: 6, hours: 3 });
Date.today().addYears(1);   //增加1年
Date.today().addMonths(-2); //相减2个月
Date.today().addWeeks(1);   //增加1周
Date.today().addDays(4);    //增加4天
Date.today().addHours(6);   //增加6小时
Date.today().addMinutes(-30);   //相减30分钟
Date.today().addSeconds(15);    //增加15秒
Date.today().addMilliseconds(200);  //增加200毫秒
 
Date.today().moveToFirstDayOfMonth();//返回当前月份的第一天
Date.today().moveToLastDayOfMonth();//返回当前月份的最后一天
 
new Date().clearTime(); //将时间设置为00:00(一天的开始)
Date.today().setTimeToNow();//将时间重置为当前时间;与clearTime()的功能相反

其他用法

Date.getMonthNumberFromName("March");// 2-特定于CultureInfo。<static>
Date.getDayNumberFromName("sat");// 6-特定于CultureInfo。<静态>
Date.isLeapYear(2008)                  // true|false. <static>
Date.getDaysInMonth(2007, 9)           // 31 <static>
Date.today().getWeek();//返回一年中的第几周。根据年份Date 返回1到(52 | 53)
Date.today().setWeek(1);    //将一年中的星期几设置为星期几
var test = new Date(); // Do something... like run a test...
test.getElapsed();  //返回距现在的毫秒数
Date.today().isDaylightSavingTime();// true|false. 在夏令时之内
Date.today().hasDaylightSavingTime();// true|false. 是否遵守夏令时

10-7 Momentjs日期库

// 官网:https://momentjs.com/
10-7-1 获取当前的时间
console.log(moment()); //返回一个对象

image-20240131173445232

console.log(moment()._d); 
console.log(moment().format('yyyy-M-d'));  //2022-12-6
console.log(moment(undefined).format('yyyy-M-d'));  //2022-12-6
console.log(moment([]).format('yyyy-M-d'))
10-7-2 Format days
moment().format('MMMM Do YYYY, h:mm:ss a');
moment().format('dddd');
moment().format("MMM Do YY");
moment().format('YYYY [escaped] YYYY');
moment().format();
10-7-3 Relative Time
moment("20111031", "YYYYMMDD").fromNow();
moment("20120620", "YYYYMMDD").fromNow();
moment().startOf('day').fromNow();
moment().endOf('day').fromNow();
moment().startOf('hour').fromNow();
10-7-4 Calendar Time
console.log(moment().subtract(10, 'days').calendar());
console.log(moment().subtract(6, 'days').calendar());
console.log(moment().subtract(3, 'days').calendar());
console.log(moment().subtract(1, 'days').calendar());
console.log(moment().calendar());
console.log(moment().add(1, 'days').calendar());
console.log(moment().add(3, 'days').calendar());
console.log(moment().add(10, 'days').calendar());

image-20240131173641504

其余的去官网了解

11,RegExp 正则

11-1 正则创建

11-1-1 直接量
var pattern = /aini/;
var str = 'aini is a good boy'
console.log(pattern.test(str))
11-1-2 使用RegExp构造函数
var pattern = new RegExp('aini')
var str = 'aini is a good boy'
console.log(pattern.test(str))

11-2 修饰符

1,g :// 表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止;
2,i :// 表示不区分大小写(case-insensitive)模式;
3,M:// 表示多行(multiline),即在到达一行文本末尾时还会继续查找下一行中是否存在匹配项;

一个正则表达式就是一个模式与上述 3 个标志的结合体,不同组合产生不同结果,如:

var pattern1 = /at/g; // 匹配字符串中所有 at 的实例
var pattern2 = /[bc]at/i; // 匹配第一个 bat 或 cat,不区分大小写
var pattern3 = /.at/gi; // 匹配所有以 at 结尾的 3 个字符的组合,不区分大小写

另一种创建正则表达式的方式是使用 RegExp 构造函数;它接收两个参数:一个是要匹配的字符串模式,另一个是可选的修饰符字符串;如:

var pattern1 = new RegExp("[bc]at","i");
1// 也可以不使用 new 操作符,其等同于 new RegExp();
2// 但如果 pattern 参数是一个正则表达式时有所不同,它只是简单地返回 pattern,而不会创建一
个新的 RegExp 对象,但如果使用了不同的修饰符,就会返回新的 RegExp 对象:
var re = new RegExp(".at","g");
newre = RegExp(re); // true
newre = RegExp(re,"i"); // false (模式改变了就不是同一个正则了)
console.log(re === newre);

RegExp构造函数最大的特点是可以动态的创建正则表达式,这种情况往往用在:没办法通过写死在正则直接量中;

var arr = ['坏蛋','傻女人','妈的','脑子有病','神经病','疯子']
var arrStr = ['你真是个坏蛋','你是傻女人','他妈的,气死我了','你脑子有病','神经病啊你,干嘛打我','狗疯子,我要打死
你,你妈的,脑子真有病']
for(var i=0;i<arrStr.length;i++){
for(var j=0;j<arr.length;j++){
//var reg = /arr[j]/g; // 不能用直接量,这是错误的
var reg = new RegExp(arr[j],'img');
arrStr[i] = arrStr[i].replace(reg,'*');
}
}
console.log(arrStr)

11-3 转义符

1// 如果模式中用到特殊字符(元字符),包括非字母字符,必须使用转义 \ ,包括\自身:
2// 如果使用 RegExp 构造函数,转换符必须使用双 \ ,即 \\:
var str = 'aini is [aini] ia ainsii';
var pattern = /\[aini\]/g ;
var pattern1 = new RegExp('\\[aini\\]','g'); 双斜杠转义
console.log(str.match(pattern))
console.log(str.match(pattern1))

11-4 精确匹配

11-4-1 元字符
// 元字符是正则表达式拥有特殊含义的字符,即是正则表达式语法的一部分,其包括:
// ^ $ . * + ? = ! : | \ / ( ) [ ] { } (15 个)
// 这些元字符在正则表达式中都有一或多种特殊用途,某些符号只有在正则的某些上下文中才具有某种特殊含义,在其他
// 上下文中则被当作直接量处理;然而,任何时候,如果要使用这些特殊字符进行匹配,都必须进行转义。
var pattern1 = /[bc]at/i; // 匹配第一个 bat 或 cat,不区分大小写
var pattern2 = /\[bc\]at/i; // 匹配第一个[bc]at,不区分大小写
var pattern3 = /.at/gi; // 匹配所有以 at 结尾的 3 个字符的组合,不区分大小写
var pattern4 = /\.at/gi; // 匹配所有.at,不区分大小写
// 注:其他标点符号没有特殊含义,可以直接当作字面量进行匹配;如果记不住这些特殊符号,可以为
每个标点符号前都加上反斜杠;另外,许多字母和数字在有反斜杠做前缀时也有特殊含义
11-4-2 使用特殊字符
1// 可以直接使用字符表示它们本身,但也可以使用它们的 ASCII 或者 Unicode 代码来指定字符;要使用 ASCII 来表示一个字符,则必须指定一个两位的十六进制代码,并在前面加上\x
2// 也可以使用八进制代替十六进制来指定字符;
3// 如果要使用 Unicode 来表示,必须指定字符串的四位的十六进制\uxxxx
var sColor = "blue";// b 的 ASCII 为 98, 等于十六进制 62,因此 b 可以用\x62
var re = /\x62/; // 16 进制 相当于/b/
var re = /\142/; // 8 进制 相当于/b/
var re = /\u0062/; // Unicode 相当于/b/
console.log(re.test(sColor));
11-4-3 其他特殊字符
\o NULL 字符(\u0000)、
\t 制表符(\u0009)、 
\n 换行符(\u000A)、
\v 垂直制表符(\u000B)、 
\f 换页符(\u000C)、
\r 回车符(\u000D)、 
\b 回退字符、 
\a alert 字符、 
\e escape 字符、 
\xnn 由 16 进制数 nn 指定的拉丁字符,
如:\x0A 等价于\n、 
uxxxx 由 16 进制数 xxxx 指定的 Unicode 字符,
如:\u0009 等价于\t、 cX 与 X 相对应的控制字符,
如,\cJ 等价于换行符\n。
<body>
<textarea id="txt"></textarea>
<textarea id="result"></textarea>
<p id="myp"></p>
<input type="button" onclick="show()" value="提交" />
<script>
function show(){
var str = document.getElementById("txt").value;
var re = /\n/g;
str = str.replace(re, "\n"); // 保存在数据中
document.getElementById("result").value = str;
str = str.replace(re, "<br/>"); // 显示在页面中
document.getElementById("myp").innerHTML = str;
}
show()

image-20240131192729192

11-4-4 预定义字符

也称为字符类;一个字符类可以匹配它所包含的任意字符;由于某些模式会反复用到,所以提供了一些预定义字符来提高匹配的效率;

. // 等同于[^\n\r], 除了换行符和回车之外的任意字符
\d // 等同于[0-9], 数字(ASCII 数字)
\D // 等同于[^0-9], 非数字字符(除了 ASCII 数字之外的任何字符)
\s // 等同于[ \t\n\0B\f\r], 空白字符(任何 Unicode 空白符)
\S // 等同于[^ \t\n\0B\f\r], 非空白字符(任何非 Unicode 空白符的字符),注:和\w 不同
\w // 等同于[a-zA-Z0-9_], 单词字符(即 ASCII 字符,包括所有字母,所有数字和下划线)  \W 等同于[^a-zA-Z0-9_],非单词字符(任何不是 ASCII 字符)
[\b]// 退格直接量(特例)

注:以上的字符指的是 ASCII 字符,非 Unicode 字符

var str = "567 9838 zeronetwork 王唯";
var re = /./gi;
var re = /\d/gi;
var re = /\d\d\d/gi; // 匹配三个数字
var re = /\D/gi;
var re = /\s/gi;
var re = /\S/gi;
var re = /\w/gi;
var re = /\W/gi;

通过将一些字符放入方括号中,表示要匹配的范围;

简单范围:// 形如:/[acf]at/g
排除范围:// 使用^(脱字符号),用来定义否定字符类,必须出现在[ 之后,匹配所有不包含在方括号内的字符,形如:/[^acf]at/ 
连续范围:// 使用 – 连字符表示一个连续范围,如:[a-z], [0-9] ,[^1-4] 
组合范围:// 形如:[a-m1-4\n]
var str = "a bat, a Cat, a fAt baT, a faT, a faT cat";
var re = /[bcf]at/gi; //["bat", "Cat", "fAt", "baT", "faT", "faT", "cat"]
var re = /[\u0062cf]at/gi; //["bat", "Cat", "fAt", "baT", "faT", "faT", "cat"]
var re = /[^bc]at/gi; //["fAt", "faT", "faT"]
console.log(str.match(re));
var str = "num1, num2, num3, num4, num5, num6, num7, num8, num9";
var re = /num[1-4]/gi;
console.log(str.match(re)); // ["num1", "num2", "num3", "num4"]
var str = "567 9838 abc";
var re = /[0-9][0-9][0-9]/gi; // ["567", "983"]
var re = /[0-9]{3}/gi; // ["567", "983"]
console.log(str.match(re));
// 注:有些字符类转义字符只能匹配 ASCII 字符,还没有扩展到可以处理 Unicode 字符,但可以通过十六进制表示法来显式定义 Unicode 字符类,如:/[\u0400-\u04FF]/ 用以匹配所有 Cyrillic 字符(斯拉夫语)
11-4-5 量词

非贪婪模式放在量词后面

{n} :// 匹配 n 次
{n, m} :// 匹配至少 n 次,但不超过 m 次
{n, } :// 匹配至少 n 次
? :// 匹配 0 次或 1 次,等价于{0, 1} 
* :// 匹配 0 次或多次,等价于{0, } 
+ :// 匹配 1 次或多次,等价于{1, }
var str = "wangwei age is 18, (birthday) is 1998 year. jing123 age is";
var re = /\d{2,4}/g; // ["18", "1998",'123']
var re = /\w{4}\d?/g; ['wang', 'birt', 'hday', '1998', 'year', 'jing1']
var re = /\s+age\s+/g; // [" age ", " age "]
var re = /[^(|)]*/g; // 匹配非左括号或右括号的字符
11-4-6 贪婪与非贪婪
var str = "wwwwww";
var re = /w{2,4}/;
console.log(str.match(re)); // wwww
var str = "wangwei";
var re = /\w+/;
console.log(str.match(re)); // wangwei

以上匹配的特点是:尽可能多地匹配;这种匹配称为“贪婪”匹配;

贪婪量词的原理:// 先看整个字符串是否匹配,如果没有发现匹配,它去掉该字符串中的最后一个字符,并再次尝试,如果还没有发现匹配,那么再去掉最后一个字符,这个过程一直重复到发现一个匹配或者字符串不剩下任何字符;

// 与贪婪对应的就是非贪婪,称为惰性方式,只需要在量词的后面跟随一个?问号即可,如:??、+?、*?或{1,5}?;如:修改上例;
惰性量词的原理:先看字符串中的第一字母是否匹配,如果不匹配,再读出下一字符,一直继续,直到发现匹配或者整个字符串都检查过出没有匹配,与贪婪工作方式相反;

支配量词:只尝试匹配整个字符串;如果整个字符串不匹配,就不做进一步尝试;(已不被支持)

贪婪:// ? * + {n} {n,m} {n, } 
惰性:// ?? *? +? {n}? {n,m}? {n, }?
支配:// ?+ *+ ++ {n}+ {n,m}+ {n, }+

使用非贪婪模式所得到的结果可能和期望并不一致:

var str = "aaab";
var re = /a+b/; // aaab
var re = /a+?b/; // aaab
console.log(str.match(re));

由于惰性匹配是从左往右匹配;

11-4-7 复杂模式
7-1 候选
// 候选就是用“|”来表示的模式或关系,它表示的是在匹配时可以匹配“|”的左边或右边。这个“|”相当于“或”
var str = "i like colors:red black";
var re = /red|black|green/; // red
console.log(str.match(re));
var re = /jpg|png|gif/;
console.log(re.test("xxx.jpg"));
var str = "wang is 18 age."
var re = /\d{2}|[a-z]{4}/; // wang
console.log(str.match(re));

候选项的尝试匹配次序是从左到右,直到发现了匹配项;如果左边的选择项匹配,就忽略右边的匹配项,即使它会产生更好的匹配,如:

var str = "abcde";
var re = /a|ab/; // a 只会匹配一个 a
console.log(str.match(re));

候选结合 replace() 方法 主要用在从用户输入删除不合适的单词;

var str = "你妈的他妈的不是东西,都是坏蛋!";
var re = /坏蛋|你妈的|他妈的/gi;
var newStr = str.replace(re,"****");
console.log(newStr);
var newStr = str.replace(re,function(sMatch){
return sMatch.replace(/./g, "*");
});
7-2 分组

通过用一对圆括号,可以把单独的项组合成子表达式;

var str = "wangwei1998";
var re = /[a-z]+(\d+)/;
console.log(str.match(re));

image-20240131194244441

也会把圆括号里匹配的内容单独提取出来

为什么需要分组?:

1,// 它是一个组合项或子匹配,可作为一个单元,统一操作;如可以统一使用|、*、+等进行处理。
2,// 可以把分组匹配的结果单独抽取出来以备后用;
var str = "javascript";
var re = /java(script)?/; // true script 可有可无
console.log(re.test(str));
var str = "dogdogdog";
var re = /(dog){3}/;
console.log(str.match(re));
// 只关心匹配尾部的数字,把它单独提取出来
var str = "京 A88888";
var re = /.{2}(\d+)/;
var arr = re.exec(str);
console.log(arr);
console.log(arr[1]);

还可以嵌套分组:

var str = "zeronetwork";
var re = /(zero(net(work)))/;
console.log(str.match(re));

image-20240131194454053

反向引用:

// 每个分组都被存放在一个特殊的地方以备将来使用,这此分组也称为捕获组,这些存储在分组中的特殊值,称之为反向引用;即允许在同一正则表达式的后部引用前面的子表达式;其是通过在字符“\”后加一位或多数数字实现的,该数字指定了分组的子表达式的在正则中的位置:
var str = "god godod gododod godododod";
var re = /g(od)\1*/g;
console.log(str.match(re));

image-20240131194543186

由于分组可以嵌套,所以反向引用是按照从左到右遇到的左括号的顺序进行创建和编号的,如 (A?(B?(C?)))

1.(A?(B?(C?))) 2.(B?(C?)) 3. (C?):

var str = "aaabbccbb aaabbccbb";
var re = /(a+(b+))(c)\3\2\s\1/;
console.log(str.match(re));

image-20240131194641711

对分组的引用,并不是对分组表达式的引用,而是对分组模式相匹配的文本的引用;再如:

// 匹配单引号与双引号之间的字符
var str = 'wangwei \'is" "18 old", he is \'good\' man';
var re = /['"][^'"]*['"]/g; // 不要求引号的匹配
var re = /(['"])[^'"]*\1/g; // 不要求引号的匹配
console.log(str.match(re));

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

反向引用的情景:通常用来处理相同连续的内容,如:

// 匹配连续相同的三个数字
console.log("111a222b333c123d".match(/(\d)\1\1/ig));// ["111", "222", "333"]
console.log("111a222b333c123d".match(/(\d)\1{2}/ig));//["111", "222", "333"]
// 不同点,这是匹配 3 个数字,而不是相同的数字
console.log("111a222b333c123d".match(/(\d){3}/ig)); // ["111", "222", "333", "123"]
//匹配 ABAB 格式的数字,如:1212 或 3434
console.log("1212a3434b4545c123d".match(/(\d)(\d)\1\2/g)); // ["1212", "3434", "4545"]
// 匹配 ABBA 格式的数字,如:1221 或 3443
console.log("1221a3443b4554c123d".match(/(\d)(\d)\2\1/g)); //["1221", "3443", "4554"]
// 检索 html 标记及内容
var html = '请访问:<a href="https://www.zeronetwork.cn">zeronetwrok</a>网站';
var reg = /<(\w+)[\s]*.+?>(.*)<\/\1>/ig;
console.log(html.match(reg));

image-20240131194940162

反向引用几种使用方法:

// 使用正则表达对象的 test(), match(), search()方法后,反向引用的值可以从 RegExp 构造函数中获得;
var str = "#123456789";
var re = /#(\d+)/;
re.test(str);
console.log(RegExp.$1); // 123456789
// 去重
var str = "aaaabbbbbbbcccccc";
var re = /(\w)\1*/g;
console.log(str.replace(re, "$1")); // abc
// 格式化输出
var str = "1234 5678";
var re=/(\d{4}) (\d{4})/;
var newStr = str.replace(re, "$2 $1"); // 5678 1234
console.log(newStr);
7-3 非捕获性分组
1// 反向引用,称为捕获性分组;如果只需要组合,不需要反向引用,则可以使用非捕获性分组;
2// 在较长的正则表达式中,存储反向引用会降低匹配速度;
3// 非捕获性分组:在左括号的后面加一个问号和一个紧跟的冒号,如:(?: );此时,使用\n 就访问不了捕获组了。
var str = "zeronetwork";
var re = /(?:net)/;
console.log(str.match(re));
console.log(RegExp.$1); // 空
// 删除 HTML 标识
String.prototype.stripHTML = function(){
var re = /<(?:.|\s)*?>/g;
return this.replace(re,"");
};
var str="<a href=#><b>零点程序员</b></a>";
document.write(str + "<br>");
document.write(str.stripHTML());
7-4 边界

边界(bounday):用于正则表达式中表示模式的位置;也称为匹配表达式的锚

^// 匹配字符串的开头,在多行中,匹配行开头;
$ :// 匹配字符串的结尾,在多行中,匹配行结尾;
\b :// 匹配单词的边界,即位于字符\w 和\W 之间的位置,或位于字符\w 和字符串的开头或者结尾之间;
\B// 匹配非单词的边界的位置;
var str = "JavaScript"; // JavaScript
var str = "JavaScript Code"; // null
var re = /^JavaScript$/; // 如果不使用$,使用\s 也可以,但是包含了空格
console.log(str.match(re));
var str = "Study Javascript Code. JavaScripter is good";
var re = /Java[sS]cript/g; // ["Javascript", "JavaScript"]
var re = /\bJava[sS]cript\b/g; // ["Javascript"]
var re = /\bJava[sS]cript\B/g; // ["JavaScript"]
var re = /\B[sS]cript/g; // 不会匹配单独的 script 或 Script
console.log(str.match(re));
var str = "wangwei is good man";
var re = /(\w+)$/; // man
re.test(str);
console.log(RegExp.$1);
var re = /^(\w+)/; // wangwei
re.test(str);
console.log(RegExp.$1);
var re = /^(.+?)\b/; // wangwei
re.test(str);
console.log(RegExp.$1);
var str = "First second third fourth fifth sizth";
var re = /\b(\S+?)\b/g;
var re = /\b(\w+)\b/g;
var arr = str.match(re);
console.log(arr);
7-5 前瞻(先行断言)

()不是分组

前瞻(lookahead)// 是指检查接下来出现的是不是位于某个特定字符之前;分为正向和负向;
// 正向前瞻要将模式放在(?= 和 )之间,如:(?=p);要求接下来的字符都与 p 匹配,但不能包括匹配 p 的那些字符;也称为正向先行断言;
// 负向前瞻:将模式放到(?! 或 ) 之间,如:(?!p);要求接下来的字符不与 p 匹配;也称为负向先行断言;

注:虽然用到括号,但这不是分组;JS 不支持后瞻,后瞻可以匹配,如:匹配 b 且仅当它前面没有 a;

var str1 = "bedroom Bedding";
var re = /([bB]ed(?=room))/; // bed
var re = /([bB]ed(?!room))/; // Bed
console.log(str1.match(re));
console.log(RegExp.$1);
var str = "Javascript: function is simple javascript.";
var re = /[jJ]avascript(?=:)/g; // Javascript 后面有冒号才匹配
var re = /[jJ]avascript(?!:)/g; // javascript 后面没有冒号才匹配
console.log(str.match(re));
var str = "JavaScript Javascript JavaBeans javascripter";
var re = /[jJ]ava(?=[sS]cript)/g; // ["Java", "Java", "java"]
var re = /[jJ]ava(?![sS]cript)/g; // Java
var re = /[jJ]ava(?![sS]cript)\w+/g; // JavaBeans
console.log(str.match(re));
// 添加千分位
var str = "钱:1234567890123";
var re = /(?=(\B)(\d{3})+$)/g;
console.log(str.match(re));
console.log(str.replace(re, ","));
7-6 多行模式
// 要指定多行模式,需要指定 m 选项,如果待匹配字符串包含多行,那么^与$锚字符除了匹配整个字符串的开始和结尾之外,还能匹配每行的开始和结尾;
var str = "wangwei is\ntechnical director\nof zeronetwork";
var re = /(\w+)$/g; // zeronetwork
var re = /(\w+)$/gm; // ["is", "director", "zeronetwork"]
console.log(str.match(re));
var re = /^(\w+)/g; // wangwei
var re = /^(\w+)/gm; // ["wangwei", "technical", "of"]
console.log(str.match(re));
var re = /^(.+)$/g; // null 如果待匹配字符串中有换行,如果不使用 m,则结果为 null
console.log(str.match(re));

RegExp 实例属性:

global:// 只读,布尔值,表示是否设置了 g 标志
ignoreCase:// 只读,布尔值,是否设置了 i 标志
multiline:// 只读,布尔值,是否设置了 m 标志
lastIndex:// 可读写,整数,如果模式带有 g,表示开始搜索下一个匹配项的字符位置,从 0 起;该属性一般会在 exec()和 test() . 中用到;
source:// 只读,正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回;但不包含修饰符;
var str = "wangwei name is Wangwei \r\n WANGWEI age is 18 wangwei";
var reg = /Wangwei/img;
console.log(reg.global); //true
console.log(reg.ignoreCase); //true
console.log(reg.multiline); //true
var str = "wangwei name is Wangwei \r\n WANGWEI age is 18 wangwei";
var reg = /Wangwei/img;
reg.test(str);
console.log(reg.lastIndex); //7
// 是匹配完以后最后一个字符后面的位置

匹配多次以后,可以依次找到匹配的位置

var str = "wangwei name is Wangwei \r\n WANGWEI age is 18 wangwei";
var reg = /Wangwei/img;
reg.test(str);
console.log(reg.lastIndex); //7
reg.test(str);
console.log(reg.lastIndex); //23
reg.test(str);
console.log(reg.lastIndex); //34

可以通过设置 lastIndex 的值来控制搜索匹配的位置

var str = "wangwei name is Wangwei \r\n WANGWEI age is 18 wangwei";
var reg = /Wangwei/img;
reg.test(str);
console.log(reg.lastIndex); //7
reg.test(str);
console.log(reg.lastIndex); //23
reg.lastIndex = 0; //从头开始匹配
reg.test(str);
console.log(reg.lastIndex); //7

11-5 RegExp 实例方法

toLocaleString()和 toString()方法,是 RegExp 实例继承的方法,其都会返回正则表达式的字面量,与正则表达式的方式无关,如

var pattern = new RegExp("\\[bc\\]]at","gi");
alert(pattern.toString()); // /\[bc\]]at/gi
alert(pattern.toLocaleString()); // /\[bc\]]at/gi

说明:即使是通过 RegExp 构造函数创建的,但 toLocaleString()和 toString()方法仍然会像它以字面量的形式返回;

正则表达式的 valueOf()方法返回正则表达式本身;instanceof 判断的话是 RegExp

var pattern = new RegExp("\\[bc\\]]at","gi");
alert(pattern.valueOf()); // /\[bc\]]at/gi

RegExp 对象定义了两个用于执行模式匹配操作的方法;它们的行为与 String 类中的与模式相关的方法很类似;

11-5-1 test() 方法

测试目标字符串与某个模式是否匹配,接受一个字符串参数,如果匹配返回 true,否则返回 false,该方法使用非常方便,一般被用在 if 语句主,如:

var pattern = /zero/i;
console.log(pattern.test("Zeronetwork")); // true
var text = "000-00-0000";
var pattern = /\d{3}-\d{2}-\d{4}/;
if (pattern.test(text)){
alert("匹配");
}
11-5-2 exec()方法
// 是 RegExp 对象最主要的方法,该方法是专门为捕获组而设计的;

// 其会接受一个参数,即要应用模式的字符, 会返回第一个匹配项信息的数组(就像 String 类的 match()方法为非全局检索返回的数组一样);如果没有匹配成功,返回 null;在数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串(如果模式中没有捕获组,该数组只包含一项);

// 返回的虽然为 Array 实例,但包含两个额外的属性:index 和 input;index 表示匹配项在字符串中的位置,而 input 表示应用正则表达式的字符串;
var text = "mom and dad and baby";
var pattern = /mom( and dad( and baby)?)?/gi;
var arr = pattern.exec(text);
console.log(arr); // mom and dad and baby, and dad and baby, and baby
console.log(arr.index); // 0
console.log(arr.input); // mom and dad and baby
console.log(arr[0]); // mom and dad and baby
console.log(arr[1]); // and dad and baby
console.log(arr[2]); // and baby
// 对于 exec(),不管有没有设置全局标志,其每次也只会返回一个匹配项;
// 在不设置全局标志情况下,在同一个字符串上多次调用 exec() 将始终返回第一个匹配项的信息。
// 而设置全局标志的情况下,每次调用 exec()则都会在字符串中继续查找新匹配项,会将当前正则对象的 lastIndex 属性设置为紧挨着匹配子串的字符位置,为下一次执行 exec()时指定检索位置;如果 exec()没有匹配结果,lastIndex 被重置为 0;
var text = "cat, bat, sat, fat";
var pattern = /.at/; // 没有使用 g 全局
var pattern = /.at/g; // 使用 g 全局
var matches = pattern.exec(text);
console.log(matches);
console.log("match:"+matches[0]+",index:" + matches.index + ",lastIndex:" + pattern.lastIndex);
// 再一次执行,使不使用 g,结果不一样
matches = pattern.exec(text);
console.log(matches);
console.log("match:"+matches[0]+",index:" + matches.index + ",lastIndex:" + pattern.lastIndex);
// 再一次执行,使不使用 g,结果不一样
matches = pattern.exec(text);
console.log(matches);
console.log("match:"+matches[0]+",index:" + matches.index + ",lastIndex:" + pattern.lastIndex);

image-20240131200619720

使用 test()与 exec()是等价的,当 exec()的结果不是 null,test()返回 true 时,它们都会影响模式的 lastIndex()属性,当然是在全局下;如此,也可以使用 test()遍历字符串,就跟 exec()一样,如

var text = "cat, bat, sat, fat";
var pattern = /.at/g;
console.log(pattern.test(text));
console.log(pattern.lastIndex);
console.log(pattern.test(text));
console.log(pattern.lastIndex);
// 一直调用 4 次,test()就会返回 false
// 遍历
var text = "cat, bat, sat, fat";
var pattern = /.at/g;
while(pattern.test(text)){
console.log(pattern.lastIndex);
}

image-20240131200705297

// lastIndex 属性是可读写的,可以在任何时候设置它,以便定义下一次搜索在目标字符串中的开始位置;exec()和test()在没有找到匹配项时会自动将 lastIndex 设置为 0;如果在一次成功的匹配之后搜索一个新的字符串,一般需要显式地把这个属性设置为 0;
var text = "cat, bat, sat, fat";
var pattern = /.at/g;
console.log(pattern.exec(text));
console.log(pattern.lastIndex);
// 匹配新的字符串
var str = "gat,good,pat";
pattern.lastIndex = 0; // 3 如果没有此句,返回 9,即 pat 的位置
console.log(pattern.exec(str));
console.log(pattern.lastIndex);

11-6 RegExp 构造函数属性

// RegExp 构造函数包括一些属性,这些属性适用于作用域中的所有正则表达式,并且基于所执行的最近一次操作而变化,另外这些属性名都有别名,即可以通过两种方式来访问它们,可以称为长属性和短属性;
1. input -- $_ :// 最近一次要匹配的字符串;
2. lastMatch -- $&// 最近一次的匹配项;
3. lastParen -- $+// 最近一次匹配组;
4. leftContext -- $` :// input 字符串中 lastMatch 之前(左边)的文本
5. rightContext -- $’ :// input 字符串中 lastMatch 之后(右边)的文本
6. multiline -- $*// 布尔值,表示是否所有表达式都使用多行模式,部分浏览器未实现

使用这些属性都可以从 exec()或 test()执行的操作中提取出更具体的信息;

var text = "this has been a short summer";
var pattern = /(.)hort/gm;
if(pattern.test(text)){
    console.log(RegExp.input); // this has been a short summer
    console.log(RegExp.lastMatch); // short
    console.log(RegExp.lastParen); // s
    console.log(RegExp.leftContext); // this has been a
    console.log(RegExp.rightContext); // summer
    console.log(RegExp.multiline); // undefined
}

由于短属性大都不是有效 JS 标识符,所以必须通过方括号访问;

var text = "this has been a short summer";
var pattern = /(.)hort/gm;
if(pattern.test(text)){
    console.log(RegExp.$_); // this has been a short summer
    console.log(RegExp["$&"]); // short
    console.log(RegExp["$+"]); // s
    console.log(RegExp["$`"]); // this has been a
    console.log(RegExp["$'"]); // summer
    console.log(RegExp["$*"]); // false
}
// 除了以上,还有 9 个用于存储捕获组的构造函数属性,语法为: RegExp.$1 . RegExp.$2…..RegExp.$9, 分别用于匹配第一,第二…第九个捕获组,在调用 exec()或 test()方法时,这些属性会被自动填充;
var text = "this has been a short summer";
var pattern = /(..)or(.)/g;
if(pattern.test(text)){
    console.log(RegExp.$1); // sh
    console.log(RegExp.$2); // t
    console.log(RegExp.$3); // ""
}

说明:包含了两个捕获组;本质上就是反向引用。

11-7 字符串属性

7-1 search()方法

接受的参数就一个正则表达式,其返回字符串中第一个匹配项的索引;如果没有找到匹配项,则返回-1;如:

var text = "cat, bat, sat, fat";
// 以下的.at 可以改成 at
var pattern = /.at/; // 0
var pos = text.search(/.at/); // 0
var pos = text.search(/.at/g); // 0
console.log(pos);
// search()不支持全局检索,它会忽略正则表达式参数中的修饰符 g;也会忽略 RegExp 的 lastIndex 属性,总是从String 的开始位置搜索,即它总是返回 String 中第一个匹配子串的位置;
// 如果 search()参数不是正则表达式,则首先会通过 RegExp 构造函数将它转换成正则表达式;
var text = "cat, bat, sat, fat";
var pos = text.search('at'); // 1
console.log(pos);

注:也可以理解为就是查找普通的字符串的索引位置;

7-2 match()方法

本质上与 RegExp 的 exec()方法相同;其只接受一个参数,一个正则表达式或是一个 RegExp 对象,如果参数不是 RegExp,则会被 RegExp()转换为 RegExp 对象,如

var text = "cat, bat, sat, fat";
var pattern = /.at/;//["cat", index: 0, input: "cat, bat, sat, fat", ...]
var pattern = ".at";//会被转换成 RegExp 对象
var pattern = /.at/g; //["cat", "bat", "sat", "fat"]
var matches = text.match(pattern);
console.log(matches);

返回一个包含匹配结果的数组,如果没有匹配结果,返回 null;

// 如果没有使用修饰符 g,match()不会进行全局检索,它只会检索第一个匹配,但其依然会返回一个数组,此时,数组唯一的一项就是匹配的字符串;

// 如果使用了分组,第一项是匹配的整个字符串,之后的每一项(如果有)保存着与正则表达式中的捕获组匹配的字符串;
var str = "zeronetwork is good zeronetwork";
var re1 = /zero(net(work))/;
var re2 = /zero(net(work))/g;
var result1 = str.match(re1);
var result2 = str.match(re2);
console.log(result1);
console.log(result2);

image-20240131201942971

全局与非全局返回的数组不一样

如果使用了全局检索,数组会保存所有匹配结果;而不会显示分组里面匹配的结果,数组没有 input,index属性

// match()方法如果使用了一个非全局的正则,实际上和给 RegExp 的 exec()方法传入的字符串是一样的,它返回的数组带有两个属性:index 和 input,index 指明了匹配文本在 String 中的开始位置,input 则是对该 String本身的引用。
// 匹配 URL
var str = "访问零点网络官网:https://www.zeronetwork.cn/index.html";
var re = /(\w+):\/\/([\w.]+)\/(\S*)/;
var result = str.match(re);
console.log(result);
var o={};
if(result != null){
o.fullurl = result[0];
o.protocol = result[1];
o.host = result[2];
o.path = result[3];
}
for(var p in o)
console.log(p + ":" + o[p]);

image-20240131202043963

如果使用了全局,返回的是每个匹配的子串数组,没有 index 和 input 属性;如果希望在全局搜索时取得这此信息,可以使用 RegExp.exec()方法;

7-3 replace()方法
// 该方法接受两个参数,第一个参数可以是一个 RegExp 对象或一个字符串(这个字符串不会被转换成正则表达式),第二个参数是要替换的字符串,可以是一个字符串或一个函数;如果第一个参数是字符串,那么只会替换第一个子字符串,要想替换所有子字符串,唯一的办法就是提供一个正则表达式,而且要指定全局 g 标志,如
var text = "cat, bat, sat, fat";
console.log(text.replace("at","ond"));// cond, bat, sat, fat
console.log(text.replace(/at/g,"ond"));// cond,
// 在第二个参数中使用捕获组:使用$加索引数字,replace()将用与指定的子表达相匹配的文本来替换字符;这是一个非常实用的特性,如
var str = "cat, bat, sat, fat";
console.log(str.replace(/(at)/g,"$1er"));//cater, bater, sater, fater
// 将英文引号替换为中文引号
var str = 'zero"net"work';
var quote = /"([^"]*)"/g;
console.log(str.replace(quote,'“$1”'));

如果第二个参数是字符串,还可以使用一些特殊的字符序列,将正则表达式操作得到的值插入到结果字符串中,如:

$$ :// $美元符号
$&// 匹配整个模式的子字符串,与 RegExp.lastMath 的值相同
$` :// 匹配的子字符串之前(左边)的子字符串,与 RegExp.leftContext 的值相同
$’ :// 匹配的子字符串之后(右边)的子字符串,与 RegExp.rightContext 的值相同
$n :// 匹配第 n 个捕获组的子字符串,其中 n 等于 0-9,如,$1 是匹配第一个捕获组的子字符串,$2 是第二个捕获组的子字符串,以此类推;如果正则表达式中没有定义捕获组,则使用空字符串
$nn :// 匹配第 nn 个捕获组的子字符串,其中 nn 等于 00-99,如$01 是匹配第一个捕获组的子字符串,$02 是匹配第二个捕获组的子字符串,以此类推;如果正则表达式中没有定义捕获组,则使用空字符串

通过这些特殊的字符序列,可以使用最近一次匹配结果中的内容,如:

var text = "my name is wangwei zero";
var re = /(\w+)g(\w+)/g;
console.log(text.replace(re,"$$"));
console.log(text.replace(re,"$&"));
console.log(text.replace(re,"$`"));
console.log(text.replace(re,"$'"));
console.log(text.replace(re,"$1"));
console.log(text.replace(re,"$01"));
var text = "cat, bat, sat, fat";
var result = text.replace(/(.at)/g,"word ($1)");
console.log(result); // word (cat), word (bat), word (sat), word (fat)
// 把名字格式颠倒
var name = "Wei, Wang";
console.log(name.replace(/(\w+)\s*,\s*(\w+)/, "$2 $1")); // Wang Wei
// replace()方法的第二个参数也可以是一个函数;该回调函数将在每个匹配结果上调用,其返回的字符串则将作为替换文本;

// 该函数最多可传递 3 个参数:模式的匹配项、模式匹配项在字符串中的位置和原始字符串;

// 在正则表达式中定义了多个捕获组的情况下,传递给函数的参数依次是模式的匹配项、第一个捕获组的匹配项、第二个捕获组的匹配项……,但最后两个参数仍然分别是模式的匹配项在字符串的位置和原始字符串;此种方式,可以实现更加精细、动态的替换操作,如:
var str = "zero net work";
var result = str.replace(/\b\w+\b/g, function(match,pos,text){
return 'match:'+match +",pos:"+pos +',text:'+text +'\n'
// word.substring(0,1).toUpperCase() + word.substring(1);
});
console.log(result);

image-20240131202459573

// 所有单词的首字母大写
var str = "zero net work";
var result = str.replace(/\b\w+\b/g, function(word){
return word.substring(0,1).toUpperCase() + word.substring(1);
});
console.log(result); // Zero Net Work
// 为 HTML 字符转换为实体
function htmlEscape(text){
return text.replace(/[<>"&]/g, function(match, pos, originalText){
    switch(match){
        case "<":
        return "&lt;";
        case ">":
        return "&gt;";
        case "&":
        return "&quot;";
        case "\"":
        return "&amp;";
    	}
	});
}
var text = "<p class=\"greeting\">zero network!</p>";
console.log(text);
console.log(htmlEscape(text)); // <p class="greeting">zero network!</p>
document.write(text);
document.write(htmlEscape(text));

image-20240131202615867

image-20240131202626038

还可以在 replace 中回调函数中使用捕获组:

var str = "aabb";
var reg = /(\w)\1(\w)\2/g;
console.log(str.replace(reg,function($,$1,$2){
//return $ + "," + $1 + "," + $2; aabb a b
return "提取了两个字母:"+$1+"和"+$2;
}));
// 替换成驼峰写法

image-20240131203930388

var str = "zero-net-work";
var reg = /-(\w)/g;
console.log(str.replace(reg, function($,$1){
return $1.toUpperCase();
}));

image-20240131203937133

注:与 exec()和 test()不同,String 类的方法 search()、replace()和 match()并不会用到 lastIndex 属性;实际上,String 类的这些方法只是简单的将 lastIndex 属性值重置为 0;

7-4 split 方法

可以基于指定的分隔符将一个字符串分割成多个子字符串,并将结果放在一个数组中;分隔符可以是字符串,也可以是 RegExp 对象;其可以接受可选的第二个参数,用于指定数组的大小,以便确保返回的数组不会超过既定大小,如:

console.log("1, 2, 3, 4, 5".split(/\s*,\s*/)); // ["1", "2", "3", "4", "5"]
var colorText = "red,blue,green,yellow";
var colors = colorText.split(","); // red,blue,green,yellow
var colors = colorText.split(",",2);// red,blue
var colors = colorText.split(/[^\,]+/); // ["", ",", ",", ",", ""]
console.log(colors);

11-8 ES 正则的局限性

尽管 ECMAScript 的正则表达式的功能比较完备,但仍缺少某些语言所支持的高级正则表达式特性,如下:

// 匹配字符串开始和结尾的\A 和\Z 锚(但支持^和$);
// 向后查找(lookbehing(但完全支持向前查找 lookahead);
// 并集和交集类;
// 原子组(atomic grouping);
// Unicode 支持(单个字符除外,如\rFFFF);
// 命名的捕获组(但支持编号的捕获组);
// s(single, 单行)和 x( free-spacing, 无间隔)匹配模式;
// 条件匹配;
// 正则表达式注释;

即使存在这些限制,ECMAScript 正则表达式仍然是非常强大的,能够完成绝大多数模式匹配任务。

12 ,变量,作用域,预编译

12-1 变量的作用域

// 变量的作用域是指一个变量在哪个范围内可以使用,可以分为两种:

全局变量:// 在所有函数之外定义的变量,其作用范围是整个变量定义之后的所有语句,包括其后定义的函数及其后的<script>中的代码;
局部变量:// 定义在函数之内的变量,只有在该函数中才可使用;

// 如果函数中定义了与全局变量同名的局部变量,会覆盖全局变量;
var msg = "这是全局变量的值";
function show(){
    var str = "局部变量";
    console.log(msg);
    console.log(str);
}
show();
console.log(str);  // Error str is not defined

如果函数中使用隐式声明变量,即没有使用var声明,则该变量自动变成全局变量;使用var声明的变量会自动被添加到最接近的环境中;在函数内部,最接近的环境就是函数的局部环境;如:

function add(num1,num2){
var sum = num1+num2;
// sum = num1+num2;
    return sum;
}
var result = add(10,20);
alert(sum);  // 由于sum不是有效的变量,因此会导致错误
// 注:在JavaScript中,不声明而直接始初化变量是一个常见的错误做法,因为这样可能会导致意外;建议在初始化变量之前,一定要先声明;并且,在严格模式下,初始化未经声明的变量会导致错误;

// ES的变量与其他语言的变量有很大区别;ES变量是松散类型的,这个特点决定了它只是在特定时间用于保存特定值的一个名字而已;由于不存在定义某个变量必须要保存何种数据类型值的规则,所以,变量的值及其数据类型可以在脚本的生命周期内可以被改变;尽管从某种角度看,这可能是一个灵活强大的特性,但同时也是容易出问题的特性;

// 在实际应用中,ES变量还是比较复杂的;比如:函数的参数,由于参数的数据类型不一致,导致的结果也不致;嵌套函数:

// 也称为私有函数:是指处于局部作用域中的函数;当函数嵌套定义时,子级函数就是父级函数的私有函数;外界不能调用私有函数,私有函数只能被拥有该函数的函数代码调用;子级函数可以使用父级函数定义的变量,父级函数不能使用子级函数定义的变量;其他函数不能直接访问子级函数,如此,就实现了信息的隐藏;如:
function funA(){
    var strA = "funA定义的变量strA";
    funB();
    function funB(){
        var strB = "funB定义的变量strB";
        console.log(strA);
        console.log(strB);
    }
}
funA();

12-2 预编译

// ES是一种具有函数优先的轻量级解释型或即时编译型的编程语言,其可以不经过编译而直接运行,但是ES存在一个预编译的机制,这也是Java等一些语言中没有的特性,也就正是因为这个预编译的机制,导致了ES中变量提升的一些问题;
// JavaScript运行三部曲:
// 脚本执行期间JS引擎按照以下步骤进行处理:
· 1.语法分析;
· 2.预编译;
· 3.解释执行;
// 即在执行代码前,还需要两个步骤:
// 语法分析,就是引擎检查你的代码有没有什么低级的语法错误;
// 解释执行:就是执行代码;
// 预编译简单理解就是在内存中开辟一些空间,存放一些变量与函数;
// JS预编译发生时刻:
// 预编译是在脚本执行前就发生了,更确切的说是在函数执行前发生的,也就是说函数执行时,预编译已经结束;
12-2-1 预编译前奏

mply global暗示全局变量:任何变量,如果未经声明就赋值,这些变量就为全局对象(window)所有(即为window对象的属性);一切声明的全局变量,也是window所有;如:

var a = 123;
window.a = 123;
function test(){
    // 这里的b是未经声明的变量,所以是归window所有的; 连等的操作也视为无var
    var a = b = 110;
}
12-2-2 变量声明提升

在JavaScript函数里的所有声明(只是声明,不涉及赋值)都被提前到函数体的顶部,预编译时并不会对变量进行赋值(即不会进行初始化),变量赋值是在脚本执行阶段进行的;如:

console.log('before:' + a);  // before:undefined
var a = 1;
console.log('after:' + a);  // after:1
12-2-3 函数声明整体提升

函数声明语句将会被提升到外部脚本或者外部函数作用域的顶部,如:

a();        // function
console.log(a);     // f a(){...}
function a(){
    console.log("function");
}
console.log(a);
a();

在预编译时,function的优先级比var高,如:

// var a=1;  // 异常,会导致下行的a()异常
a();
var a=1;
function a(){console.log("function");}
var a;
console.log(typeof a);

此时a的类型是function,而不是number;

函数表达式用的是变量,函数并不会提升:

b();// b is not a function
var b = function{
console.log('function b');
};
b();

声明同名的函数会覆盖掉之前声明的函数:

function c(){
    console.log('function c1');
}
c();    // function c2
function c(){
    console.log('function c2');
}

要理解预编译,只要弄清两点:变量/函数声明与变量赋值;在预编译阶段,只进行变量/函数声明,不会进行变量的初始化(即变量赋值,所有变量的值都是undefined);变量赋值是在执行阶段才进行的;

12-3 预编译步骤

// 首先JavaScript的执行过程会先扫描一下整体语法语句,如果存在逻辑错误或者语法错误,那么直接报错,程序停止执行,没有错误的话,开始从上到下解释一行执行一行。

执行器上下文,// 英文名Activation Object,简称AO,也称为活动对象;
全局对象,// 英文名Global Object,简称GO;
// 函数执行前会进行预编译,产生AO;
// 全局变量在执行前也会有预编译,产生GO;

// 局部预编译的4个步骤:
    创建AO;
    找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
    将实参值和形参统一;
    在函数体里面找函数声明,值赋予函数体;
// 由于全局中没有参数的概念,所以省去了实参形参相统一这一步;
// 注:GO对象是全局预编译,所以它优先于AO对象所创建和执行;

AO对象示例:

function fn(a) {
    console.log(a);     // f a(){}
    // 变量声明+变量赋值,但只提升变量声明,不提升变量赋值
    var a = 123;
    console.log(a);     // 123
    // 函数声明
    function a() {}
    console.log(a);     // 123
    // 函数表达式
    var b = function() {}
    console.log(b);     // f (){}
    // 函数
    function c() {}
}
fn(1);  // 调用

在进行完预编译后,执行函数则会以AO为基础对函数中的变量进行赋值,函数执行完毕,销毁AO对象。

GO对象的示例:

global = 100;
function test() {
    console.log(global);    // undefined
    var global = 200;
    console.log(global);    // 200
    var global = 300;
}
test();
var global;
// 注:关于GO对象和AO对象,它们俩是一个种链式关系,如上例,如果在函数体的内部没有定义global变量,这也意味着AO对象中将有这个global这个属性;如果没有,会去GO对象中寻找,即是就近原则;

// 另外需要注意的是JS不是全文编译完成再执行,而是块编译,即一个script块中预编译然后执行,再按顺序预编译下一个script块再执行,但是此时上一个script块中的数据都是可用的了,而下一个块中的函数和变量则是不可用的。

12-4 执行环境

// 执行环境(execution context)是ES中最为重要的一个概念,也称为执行上下文;

// 执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为;每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中;但无法访问这个对象,只有解析器在处理数据时会在后台使用它;

// 执行环境中有个全局执行环境的概念;

// 全局执行环境是最外围的一个执行环境;根据ES实现所在的宿主环境不同,表示执行环境的对象也不一样;在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的;某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。每个函数都有自己的执行环境;当执行流进入一个函数时,函数的环境就会被推入一个环境栈中;而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境;ES程序中的执行流就是由这个的机制控制着;

12-5 作用域链

// 当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain);作用域链的用途,能够保证对执行环境有权访问的所有变量和函数的有序访问;作用域的前端,始终都是当前执行的代码所在环境的变量对象;如果这个环境是函数,则将其活动对象作为变量对象;活动对象在最开始时只包含一个对象,即arguments对象;作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境;这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域中的最后一个对象;
function f(){
var a = 10;
function b(){};
}
var g = 100;
f();
// 查询标识符(在作用域链中查找):

// 当在某个环境中为了读取或写入而引用一个标识符时,必须通过搜索作用域链来确定.该标识符实际代表什么;搜索过程从作用域链的前端开始,向上逐级查询;如果在局部环境中找到了该标识符,搜索过程停止,变量就绪;如果在局部环境中没有找到该变量,则继续沿作用域链向上搜索;搜索过程一直追溯到全局环境的变量对象;如果在全局环境中也没有找到这个标识符,则意味着该变量尚未声明,从而会导致错误发生,如:
var color="blue";
function getColor(){
    // var color = "red";
return color;
}
console.log(getColor());
// 内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数;这些环境之间的联系是线性的、有次序的;每个环境都可以向上搜索作用域链,以查询变量和函数名;但任何环境都不能通过向下搜索作用域链而进入另一个执行环境,如:
var color="blue";
function changeColor(){
    var anotherColor="red";
    function swapColors(){
        // 这里可以访问color、anotherColor和tempColor
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;
    }
    swapColors(); // 这里可以访问color和anotherColor,但不能访问tempColor
}
changeColor();  // 这里只能访问color
console.log("color is " + color);  // red

注:函数参数也被当作变量来对待,因此其访问规则与执行环境中的其他变量相同;

12-6 延长作用域链

// 虽然执行环境的类型总共只有两种:全局和局部(函数),但还是有其他办法来延长作用域链;因为有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象在代码执行时,作用域就会加长,在代码执行后被移除:

// try-catch语句的catch块及with语句;这两个语句都会在作用域链的前端添加一个变量对象;对with语句来说,会将指定的对象添加到作用域链中;对catch语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明;如:
function buildUrl(){
    var qs = "?name=wangwei";
    with(location){
        var url = href+qs;
    }
    return url;
}
console.log(buildUrl());

12-7 作用域中的this对象

// this引用的是函数执行的环境对象,即是调用函数的对象,或者也可以说是this值(当在网页的全局作用域中调用函数时,this对象引用的就是window)。
window.color = "red";
var o = {color:"blue"};
function sayColor(){
    console.log(this.color);
}
sayColor();  // red
o.sayColor = sayColor;
o.sayColor();  // blue

12-8 没有块级作用域

ES没有块级作用域;在其他类C的语言中,由花括号封闭的代码块都有自己的作用域(如果用ES的角度来讲,就是它们自己的执行环境),因而支持根据条件来定义变量,如,下面的代码在ES并不会得到想象中的结果:

if(true){
    var color="blue";
}
console.log(color);  // blue;

在ES中,if语句中的变量声明会将变量添加到当前的执行环境(在这里是全局环境)中;在使用for语句时表现的最为明显,如:

function outputNumber(count){
    for(var i = 0; i<count; i++){
        console.log(i);
    }
    // var i;
    console.log("最后的值:" + i)  // 5
}
outputNumber(5);

可以模拟块级作用域,使用立即执行函数进行模拟

12-9 立即执行函数

立即执行函数也称为自运行函数,其没有声明,本质上就是匿名函数;其在一次执行后立即释放;

适合做一些初始化的工作或者模拟块级作用域;

(function(){
    console.log("这里是立即执行函数");
})();

立即执行函数不允许使用函数声明方式,但是如果在function前加一个+号即可,同时在控制台中,该函数名也会被忽略,如:

+function myFun(){
    console.log("这里是立即执行函数");
}(); 

在function前加上+、!、一、~等一元操作符,也是立即执行函数的写法,等同上面的立即执行函数,如果没有这些符号,解析器会把function认为为一个函数声明;

同理,只要在function前加上其他的表达式语句,都可以,如:

true && function myFun(){
    console.log("这里是立即执行函数");
}();
// 或
0,function(){
    console.log("ok");
}();

立即执行函数可以传值,也可以有返回值,如:

// 传值
(function(x,y,z){
    console.log(x+y+z)
})(1,2,3);  // 6
// 返回值
var result = (function(x,y,z){
    var sum = x+y+z;
    return sum;
})(1,2,3);
console.log(result);  // 6//传值
(function(x,y,z){
console.log(x+y+z)
})(1,2,3);// 6
/返回值
var result =(function(x,y,z){
var sum =x+y+z;
return sum;
})(1,2,3);
console.log(result);// 6

特例

function myFun(){
    console.log("这里是立即执行函数");
}(1,2,3);
// 此时,不会报错,函数也存在,但不会立即执行,原因是解析器会把它拆分成两条语句;如:
function myFun(){console.log("这里是立即执行函数");};
(1,2,3);
// 这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数;

// 一般来说,应该尽量少向全局作用域中添加变量和函数;在一个由很多开发人员共同参与的大型应用程序中,过多的全局变量和函数很容易导致命名冲突,而通过创建私有作用域,每个开发人员都可以使用自己的变量,而不必担心搞乱全局作用域;
(function(){
    var now = new Date();
    if(now.getMonth() == 0 && now.getDate() == 1){
        console.log("Happy new Year");
    }
})();

说明:变量now是匿名函数的局部变量;

立即执行函数也是后面要讲的闭包的基本形式,但闭包有个问题,就是内存占用的问题,此种方法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用,只要函数执行完毕,就立即销毁其作用域链了;

思考两个小示例

var foo = (
    function f(){return "1";},
    function g(){return 2;}
)();
console.log(typeof foo);  // number
 
var x = 1;
// (function fun(){}) 因为在括号中,所以是一个表达式,
// 运行完后就消失了,所以typeof fun就是undefined
if(function fun(){}){
    x += typeof fun;
    console.log(typeof fun);  // undefined
}
console.log(x);  // 1undefined

但是,IE中某些对象还在采用引用计数方式,这些对象不是原生的Javascript对象,如BOM和DOM中的对象就是使用C++以COM对象的形式实现的,而COM对象的垃圾收集机制采用的就是计数策略;因此,即使IE的JavaScript引擎是使用标记清除策略来实现的,但Javascript访问的COM对象依然是基于引用计数策略的;换句话说,只要在IE中涉及COM对象,就会存在循环引用的问题;如:

var element = document.getElementById("some_element");
var myObject = new Object();
myObject.element = element;
element.someObject = myObject;

由于存在这个循环引用,即使将示例中的DOM从页面中移除,其也永远不会被回收;

// 为了避免类似这样的循环引用问题,最好是在不使用它们的时候手工断开原生JavaScript对象与DOM元素之间的连接,如:
myObject.element = null;
element.someObject = null;

目前,IE早已把BOM和DOM对象都转换成了真正的JavaScript对象;这样,就避免了两种垃圾收集算法并存导致的问题,也消除了常见的内存泄漏现象;

1-3 管理内存
// 使用具备垃圾收集机制的语言编写程序,开发人员一般不必要操心内存管理的问题;但是,JavaScript在进行内存管理及垃圾收集时面临的问题还是与众不同;其中最主要的一个问题,就是分配给Web浏览器的可用内存数量通常要比分配给桌面应用程序的要少;这样做的目的主要是出于安全方面的考虑,目的是防止运行JavaScript的网页耗尽全部系统内存而导致系统崩溃;内存限制问题不仅会影响给变量分配内存,同时还会影响调用栈以及在一个线程中能够同时执行的语句数量。

因此,确保占用最少的内存可以让页面获得更好的性能;而优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据;一旦数据不再有用,最好通过将其值设置为null来释放其引用,即解除引用(dereferencing),其适用于大多数全局变量和全局对象的属性;如:

function createPerson(name){
    var localPerson = new Object();
    localPerson.name = name;
    return localPerson;
}
var globalPerson = createPerson("wangwei");
globalPerson=null;  // 手工解除globalPerson的引用
// 注:解除一个值的引用并不意味着自动回收该值所占用的内存;解除引用的值作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。

// JS的自动内存管理存在一些问题,例如垃圾回收实现可能存在缺陷或者不足,因此,需要找到一个合适的解决方法;
  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

傻猫A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值