1 JS基本概念
1.1 JS简介
-
前端三层
- HTML —— 结构层从语义的角度描述页面的结构 - CSS —— 样式层从装饰的角度描述页面的样式 - JavaScript —— 行为层从交互的角度描述页面的行为
-
JS的组成:ECMAScript + BOM + DOM
-
JavaScript 作用:数据验证、读写HTML元素、与浏览器窗口及内容交互、网页特效、WEB游戏制作、基于Node.js 技术进行服务器端编程
-
ECMAScript 版本
ECMAScript 1 (1997年06月:发布首版) ECMAScript 2 (1998年06月:版本变更) ECMAScript 3 (1999年12月:添加正则表达式、try/catch) ECMAScript 4 (放弃发布) ECMAScript 5 (2009年12月:添加严格模式、JSON支持) ECMAScript 5.1 (2011年06月:版本变更) ECMAScript 6 (2015年06月:添加类和模块) ES2015 ECMAScript 7 (2016年06月:增加指数运算符) ES2016 ECMAScript 8 (2017年06月:增新功能) ES2017 ECMAScript 9 (2018年06月:增新特性) ES2018 ECMAScript 10 (2019年06月:增新特性) ES2019 ECMAScript 11 (草案中) ES2020
1.2 JS引用
<div οnclick="alert('hello world')">点我</div> <!--内部引用-->
<body>
...... <!--html代码-->
<script>alert("hello world");</script> <!--内嵌式-->
</body>
<head>
<script src="xxxx.js"></script> <!--外联式-->
</head>
[注]
- 老版本的 script 标签中会添加 type="text/javascript" 属性,HTML5中已不必添加这一属性。
<noscript>
元素:用于给早期不支持 JavaScript的浏览器作替换提示文本;若浏览器支持脚本,则会忽略注释,不会显示出 noscript 元素中的文本。
1.2.1 script元素属性
- src:表示包含要执行代码的外部文件。
- type:表示编写代码使用的脚本语言的内容类型(MIME类型),默认值为text/javascript,非必需。(已替代废弃的 language 属性)
- charset:表示通过 src 属性指定的代码的字符集。
- async:表示应该立即下载脚本,但不应妨碍页面中的其他操作,比如下载其他资源或 等待加载其他脚本。只对外部脚本文件有效。
- defer:表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。
延迟脚本与异步脚本
- 使用defer属性用于表明脚本在执行时不会影响页面的构造,脚本会被延迟到整个页面都解析完毕后再运行,即在遇到</html>标签后再执行。
- 使用async属性是不让页面等待两个脚本下载和执行,从而异步加载页面其他内容
1.3 标识符
- 第一个字符必须是字母、下划线(_)、美元符号($),不能以数字开头
- 其他字符可以是字母、下划线、美元符号或数字
- 不能使用JS中的关键字和保留字
- 区分大小写
- 遵循驼峰原则
- 建议语义化命名
//正确
var abc;
var _abc;
var $abc;
var _abc1$;
function $$abc_(){}
//错误
var 1abc;
var *abc;
function #abc(){}
1.4 注释
// 单行注释
/* 多行块级注释 */
1.5 关键字和保留字
// 关键字
break do instanceof typeof default
case else new var if
catch finally return void throw
continue for switch while delete
in function this with try
debugger*
// 保留字(ver5,*为非严格模式下)
class* enum* extends* super* const* export* import*
abstract short debugger synchronized let
boolean interface static implements static
byte long protected int implements
char final native volatile protected
float package throws double yield
goto private transient public private
package public interface
1.6 输出方式
//页面直接输出
document.write("sunck is a nice man");
//控制台输出
console.log("sunck is a good man");
//提示框输出
alert("sunck is a handsome man");
//直接在HTML文档中输出内容
document.write();
2 JS基本语法
2.0 常量
常量也称字面量(literal),是一种固定值的表示方法,即字面上的意思,看见什么就能认出是什么,如数字、字符串、数组、json对象等类型的数据都有自己的字面量表示法。
-
数字字面量:常用的数学意义上的数字的表示法,书写时不需要添加任何特殊符号,直接写数字。
-
字符串字面量:字符串是人类说的语言、词语,包括各种数字、符号;由双引号对或单引号对包围;字符串内容被限定在同种引号之间,在字符串中某些字符需要用转义字符表示,如\n(换行)、\t(制表符)、"(引号)
console.log("我会说English,考试拿第1"); //直接显示引号里的内容
console.log(1); //表示字面量1
console.log("1"); //表示字符串1
console.log("这是\n一个引号\"和一个反斜杠\\"); //转义字符
2.1 变量
var message; //定义了一个名为message的变量
var message = "hi"; //定义message变量的同时赋值保存一个字符串值"hi"
var message = "hi", found = false, age = 29;
//使用一条语句,定义多个变量
-
变量(Variables)
- 相当于一个容器,可用来保存任何类型的数据
- ECMAScript的变量为
松散类型
- 变量名必须按照
标识符命名规范
-
变量的声明
- 即变量的定义,表示可以往里面存储数据
- 变量必须先声明(使用关键字
var
)才能使用 - 用 var 声明一个未赋初值的变量,它的值会提前被设定为 undefined
- 若试图访问一个没有声明的变量会显示RefersnceErmor错误
- 对已经赋值的变量重新声明,该变量的值不会丢失
-
变量声明提升(hoisting)
- JS变量的另一特别之处是变量可以引用稍后声明,而不会引发异常
- 变量会被“举起”或提升到所有函数和语句之前,提升后的变量将返回undefined值,所以即使在使用或引用某个变量之后存在声明和初始化操作,仍将得到undefined值,但变量的提升,只提升定义,不提升赋值,相当于引用的时候只定义,没赋值,输出undefined
console.log(a); //先使用
var a = 125; //后定义
//相当于
var a; //先定义
console.log(a); //再使用
a = 125; //后赋值
2.2 数据类型
JS的数据类型是一种动态的数据类型,体现在变量上,变量的数据类型是随着内部存储数据的类型变化而变化的,变量的数据类型,就是它内部存储的数据的数据类型,且数据类型之间也可以进行相互转换
2.2.1 Undefined 类型
var message;
alert(message == undefined); //true
- Undefined 类型只有一个值,即在使用 var 声明变量但未对其加以初始化时, 其变量值就是 undefined
- 定义为undefined的变量与尚未定义的变量是不一样的
- 对未初始化和未声明的变量执行 typeof 操作符都会返回 undefined 值
2.2.2 Null 类型
var car = null;
alert(typeof car); // "object"
- Null 类型只有一个值,表示一个==空对象指针==,使用typeof操作符检测null值时会返回 "object"
- 如果定义的变量准备在将来用于保存对象,那么最好将该变量初始化为 null 而不是其他值,这样一来只要直接检查 null 值就可以知道相应的变量是否已经保存了一个对象的引用
- 实际上,undefined 值是派生自 null 值的
- ==可以通过将变量的值设置为 null 来清空变量==
2.2.3 Boolean 类型
var booleanValue = true;
alert(typeof booleanValue); // "Boolean"
布尔类型只有两个字面值 —— true 和 false,但ECMAScript中所有类型的值都有与这两个 Boolean 值等价的值。要将一个值转换为其对应的 Boolean 值,可以调用 转型函数 Boolean( ) ,如下例所示:
数据类型 | 转换为true的值 | 转换为false的值 |
---|---|---|
Boolean | true | false |
String | 任何非空字符串 | "" (空字符串) |
Number | 任何非零数字值(包括∞) | 0 和 NaN |
Object | 任何对象 | null |
Undefined | N/A(不适用) | undefined |
2.2.4 Number 类型
/*-------------- 整数字面量 -----------*/
//十进制:最基本的数值字面量格式,直接在代码中输入
console.log(100);
console.log(-12);
//八进制:前缀为0、0O、0o,若以0为前缀,超出取值范围会直接转换微十进制显示;若以0o/0O为前缀,超出取值范围直接显示报错
console.log(071); //表示为八进制数(71)
console.log(089); //表示为十进制数(89)
console.log(0o89); console.log(0O89); //显示出错
//十六进制:前缀为0x、0X,超出取值范围会直接报错,字母可大小写
console.log(0x5c);
console.log(0x5g); //显示出错
/*-------------- 浮点数字面量 -----------*/
//所有的浮点数都是以十进制表示
console.log(3.14159);
console.log(10.0); //被解析为整数10
console.log(0.618);
console.log(.618); //表示0.618
console.log(1.); //小数点后面没有数字会被解析为1
console.log(-.618); //表示-0.618
console.log(42356.89);
console.log(0.4235689e5);//e5表示×10^5,结果依然为42356.89
/*-------------- 特殊值 -----------*/
//无穷:计算机计算能力有限,如果大于或小于某个临界点,计算机没法算出具体数字,会直接输出是一个Infinity或-Infinity,无穷值无法继续参与下一次的计算
console.log(8.6e987654322344677807); //显示Infinity
console.log(-8.6e98765056760544387); //显示-Infinity
//NaN(not a number):不是一个数,说明这个数字已经没办法正常数值形式表示,不是一个正常意义的数字,但依然是一个数字字面量
console.log(0/0); //显示NaN
console.log(12/0); //显示NaN
- 进行算术计算时,所有八进制和十六进制表示的数值 都将被转换成十进制数值
- 浮点数值的最高精度是==17位小数==,但有些浮点数值计算会产生舍入误差的问题,这是使用基于IEEE754数值的浮点计算的通病,也会出现在其他语言上
2.2.5 String 类型
-
字符串:用于表示由零或多个16位Unicode字符组成的字符序列
var firstName = "Nicholas"; var lastName = 'Zakas'; var lastName = 'Nicholas"; // 语法错误(左右引号必须匹配)
-
字符字面量:也叫转义序列,用于表示非打印字符或其他用途
var text = "This is the letter sigma: \u2764."; //该转义字符为❤ alert(text.length); //任何字符串的长度 可通过访问其length属性取得,输出28
字符串一旦创建,它们的值就不能改变,要改变某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量,如下
var lang = "Java"; lang = lang + "Script"; /* 把lang的值重新定义为"Java"与"Script"的组合,即"JavaScript" */
2.2.6 Object 类型
2.2.7 数据转换
2.2.7.1 转Number
// Number():用于任何数据类型
var a = Number(null), //0
b = Number(undefined), //NaN
c = Number(true), //1
d = Number(false), //0
e = Number("-8"), //-8
f = Number("+8"), //8
g = Number("000011"), //11 (忽略0)
h = Number("0xf"), //15 (16转10进制)
i = Number(""), //0 (空白内容)
j = Number("12+3"), //NaN(存在符号)
k = Number("12-3"), //NaN(存在符号)
l = Number("a123"), //NaN(存在字母)
m = Number("12.."), //NaN(存在小数点)
n = Number(" 123"), //123(忽略空格)
o = Number("Hello world!"), //NaN(存在字母、间空格、符号)
// parseInt():仅能返回整数,丢失小数部分,指定基数,可进行进制转换
var A = parseInt("123blue"), //123
B = parseInt(""), //NaN
C = parseInt("Infinity"), //NaN
D = parseInt("12abc"), //12(忽略字符)
E = parseInt("a12bc"), //NaN(头部非数字,识别出错)
F = parseInt("0xAF", 16), //175
G = parseInt("AF", 16), //175
H = parseInt("AF"), //NaN(指定基数会出错)
I = parseInt("10", 2), //2 (按二进制解析)
J = parseInt("10", 8), //8 (按八进制解析)
K = parseInt("10", 10), //10 (按十进制解析)
L = parseInt("10", 16), //16 (按十六进制解析)
M = parseInt(22.5), //22
N = parseInt(5.2e10); //5
// parseFloat():将字符串转为带小数的浮点数
var A1 = parseFloat("12.23"), //12.23
A2 = parseFloat("12.23.34"),//12.23
A3 = parseFloat("12.23AAA"),//12.23(忽略尾部字符)
A4 = parseFloat("12.3+123");//12.3(忽略尾部字符)
2.2.7.2 转String
// console.log():null与undefined类型直接用控制台输出变成String类型
console.log(null); //"null"
console.log("Hello"+null); //"Hellonull"
console.log(undefined); //"undefined"
console.log("Hello"+undefined); //"Helloundefined"
// toString()
var a = true;
var aStr = a.toSting(); //"true"
var b = 11;
var bStr = b.toSting(); //"11"
// String():能够将任何类型的值转换为字符串
var W = 10;
var X = true;
var Y = null;
var Z; //未定义值 undefined
alert(String(W)); //"10"
alert(String(X)); //"true"
alert(String(Y)); //"null"
alert(String(Z)); //"undefined"
由于 null 和 undefined 没有 toString() 方法,所以只能使用String()函数
2.2.7.3 转Boolean
// Boolean():能将大多数类型的值转换为布尔值
// 绝大多数转成布尔值,结果都为true
// 以下6种情况转为boolean,结果为false
console.log(Boolean(0)); //false
console.1og(Boolean("")); //false
console.1og(Boolean(false)); //false
console.1og(Boolean(NaN)); //false
console.1og(Boolean(undefined));//false
console.log(Boolean(null)); //false
2.3 操作符
运算符(Operators),也称操作符,是发起运算的最简单形式
2.3.1 一元操作符
- 自加(
++
)自减(--
):放于前“先±1再取值”,放于后“先取值再±1” - 一元加(
+
)减(-
):相当于正负号
2.3.2 加性操作符
- 加法(
+
) - 减法(
-
)
+
可以作连接符
进行字符串操作:("OX"+"456" → OX456)
2.3.3 乘性操作符
- 乘法(
*
) - 除法(
/
) - 求模取余(
%
)
2.3.4 赋值操作符
- 赋值运算符(
=
):把右侧的值赋给左侧的变量 - 复合赋值运算符:
加等+=
减等-=
乘等*=
除等/=
余等%=
左移等<<=
有符号右移等>>=
无符号右移等>>>=
var num = 10;
num = num + 10;
num += 10; // 等价于上一条语句,复合赋值仅作简化操作
复合赋值运算时,等号后面的部分是一个整体,如
y*=x-3
——>y=y*(x-3)
2.3.5 比较操作符
- 关系操作符(
>
<
>=
<=
) - 相等(
==
)不相等(!=
):只比较数值,不比较类型,比较前自动统一类型 - 全等(
===
)不全等(!==
):数值和类型都要比较
所有的比较操作符都会返回一个布尔值(true 与 false)
关系运算符不能连用。(例如 1 <= num <= 5 会存在语法错误)
2.3.6 位操作符
- 按位与(
~
) - 按位与(
&
) - 按位或(
|
) - 按位异或(
^
) - 左移(
<<
) - 有符号右移(
>>
) - 无符号右移(
>>>
)
2.3.7 布尔操作符
- 逻辑非(
!
):有非则反 - 逻辑与(
&&
):两真才真 - 逻辑或(
||
):有真就真
对于 A&&B,当 A 被认定为 false,就不会再进行 B 的操作了
2.3.8 条件操作符
// (判断条件)? 满足输出 : 不满足输出
var max = (num1 > num2) ? num1 : num2;
2.3.9 逗号操作符
使用逗号(,
)操作符可以在一条语句中执行多个操作。
var num1=1, num2=2, num3=3; // 声明多个变量
var num = (5,1,4,8,0); // num的值为0(多赋值时会返回表达式中的最后一项)
2.3.X 运算优先级
从高到低:算术操作符 → 比较操作符 → 逻辑操作符 → "="赋值符号
同级运算:按从左到右次序进行,多层括号由里向外
2.3.Ⅺ 隐式转换
数字与纯数字字符串字符串之间进行运算,除了+号之外,使用其他运算符会自动将字符串转成数字,其他数据类型参与任何数学运算都会被隐式转换,这个过程是私下进行,不需要使用parseInt等方法
console.log(12+"2"); //结果为122
console.log(12-"2"); //结果为10
console.log(12*"2"); //结果为24
console.log(12/"2"); //结果为6
console.log(12%"2"); //结果为0
console.log("12"%"2"); //结果为122
//其他隐式转换
- 转换为0 : "", null, false
- 转换为1 : true
- 转换为NaN : "非空字符串", undefined, NaN
- 转换为Infinity : 情况由具体决定
2.4 语句
2.4.1 if-else语句
==条件判断语句==,判断condition返回的布尔值,若是true执行statement1,若是false则执行statement2。
// if (condition) statement1 else statement2
if (i > 25)
alert("Greater than 25."); // 单行语句
else {
alert("Less than or equal to 25."); // 代码块{}中的语句
}
// if (condition1) statement1 else if (condition2) statement2 else statement3
if (i > 25) {
alert("Greater than 25.");
} else if (i < 0) { //多层嵌套 条件判断
alert("Less than 0.");
} else {
alert("Between 0 and 25, inclusive.");
}
2.4.2 do-while 语句
一种==后测试循环语句==,即先执行一次statement后,再判断expression,若返回false,则重新执行一次statement,直到返回true,才能往下继续执行。
// do {statement} while (expression);
var i = 0;
do {
i += 2;
} while (i < 10);
alert(i);
2.4.3 while 语句
一种==前测试循环语句==,即先判断expression,若返回true则执行一次statement,执行完后重新判断expression,若为false则跳过statement,再往下进行。
// while (expression) statement
var count = 10;
var i = 0;
while (i < count) {
alert(i);
i++;
}
2.4.4 for 语句
另一种==前测试循环语句==,先执行initialization来设定一个初始值,再判断expression,若返回true则去执行statement,执行完statement再去执行post-loop-expression,然后重新判断expression,直到返回false才跳出循环,往下执行。
// for (initialization; expression; post-loop-expression) statement
var count = 10;
for (var i = 0; i < count; i++){
alert(i);
} // 与前面while代码块执行的功能相同
- for循环是while循环的另一种表达,两者作用相同;
- initialization可以不使用var关键字而在外部去定义;
- 由于ECMAScript中不存在块级作用域,因此在循环内部定义的变量也可以在外部被访问到;
- for 语句中的初始化表达式、控制表达式和循环后表达式都是可选(可省略)的,如 for ( , , )。
2.4.5 switch 语句
另一种==条件判断分支语句==,设置一个变量 i,让其值与结构中的每个情况 case 值做比较,若存在匹配,则执行该 case 关联的代码块,执行完后使用 break 来跳出此 case同时阻止继续运行下一个 case,若无对应值时直接执行 default 关联的代码块。
// switch (expression) {case A: statet1 break; case B: state2 break; default: state3}
switch (i) {
case 25:
alert("25");
break;
case 35:
alert("35");
break;
case 45:
alert("45");
break;
default:
alert("Other");
}
- expression可以使用任何数据类型,无论是字符串或是对象;
- 每个 case 的值不一定是常量,可以是变量,甚至是表达式。
2.4.6 break 和 continue 语句
一种==断点语句==,用于在循环中精确地控制代码的执行。
var num = 0;
for (var i=1; i < 10; i++) {
if (i % 5 == 0) {
break; //立即跳出循环,强制继续执行循环后面的语句
}
num++;
}
alert(num); // 输出4
var num = 0;
for (var i=1; i < 10; i++) {
if (i % 5 == 0) {
continue; //结束本次循环后会从循环的顶部重新执行
}
num++;
}
alert(num); // 输出8
2.4.7 label 语句
一种==标签语句==,在代码中添加标签,标签可以在将来由 break 或 continue 语句引用。一般都要与 for 语句等循环语句配合使用,从而返回代码中特定的位置。
// label: statement
var num = 0;
outermost:
for (var i=0; i < 10; i++) {
for (var j=0; j < 10; j++) {
if (i == 5 && j == 5) {
break outermost; /* 退出内部循环(使用变量j循环),也退出外部循环(使用变量i循环)*/
}
num++;
}
}
alert(num); //55
var num = 0;
outermost:
for (var i=0; i < 10; i++) {
for (var j=0; j < 10; j++) {
if (i == 5 && j == 5) {
continue outermost; /* 强制继续执行循环 —— 退出内部循环,执行外部循环 */
}
num++;
}
}
alert(num); //95
2.4.8 for-in 语句
一种精准的==迭代语句==,用来枚举对象的属性。
// for (property in expression) statement
for (var propName in window) {
document.write(propName);
}
/*
这里显示了 BOM 中 window 对象的所有属性(变量 in 对象)。即每次执行循环时,都会将 window 对象中存在的一个属性名赋值给变量 propName 。这个过程会一直持续到对象中的所有属性都被枚举一遍为止。
*/
ECMAScript对象的属性没有顺序,因此不可预测的。所有属性都会被返回一次,但返回的先后次序可能会因浏览器而异。
如果要迭代的对象是null或者是undefined,那么会抛出异常。
2.4.9 with 语句
将代码的作用域设置到一个特定的对象中,目的主要是==为了简化多次编写同一个对象的工作==。
// with (expression) statement;
原程序 //(包含了多个location对象)
var qs = location.search.substring(1);
var hostName = location.hostname;
var url = location.href;
改为
with (location) { // 简化
var qs = search.substring(1);
var hostName = hostname;
var url = href;
}
大量使用 with 语句会导致性能下降,同时也会给调试代码造成困难,因此在开发大型应用程序时,不建议使用 with 语句。
2.5 函数
函数可以封装任意多条语句,在任何地方在、任何时候需要调用时执行。函数在定义时不必指定是否返回值,但可通过 return 语句来实现值的返回。
function functionName (arg0, arg1, ...) {statements}
function sayHi(name, message) {
alert("Hello " + name + "," + message);
}
sayHi("Nicholas", "how are you today?");
/* 这个函数的输出结果是 "Hello Nicholas,how are you today?" */
function sum (num1, num2) {return num1 + num2;}
var result = sum(5, 10);
function sum(num1, num2) {
return num1 + num2; // 执行完return语句之后停止并立即退出
alert("Hello world"); // 永远不会执行
}
return语句也可以不带有任何返回值(
return;
),函数在停止执行后将返回 undefined值,但推荐做法是要么让函数始终都返回一个值,要么永远都不要返回值
3 JS对象
3.1 Object
- 对象是单个事物的抽象,如一本书、一辆车、一张网页;当实物被抽象成对象,实物间的关系就变成了对象间的关系,从而可以模拟现实情况,针对对象进行编程,故“万物皆对象”
- 对象是一个容器,封装了属性(property)和方法(method),可简单理解为数据集或功能集,是状态和行为的集合,是变量和函数的集合,是无序属性的集合
- Object 是一个基础类型,其他所有类型都从 Object 继承了基本的行为
- 对象在JS中被称为引用类型的值,即每个对象都基于一个引用类型创建,可以是系统内置的原生类型,或是开发人员自定义类型
- 对象的属性的操作:增、删、改、查、遍历
3.1.1 创建与访问
//使用 new + Object()创建
var person = new Object();
person.name = "Nicholas";
person.age = 29;
//省略new操作符创建 //{}相当于new Object() 的缩写
var person = {};
person.name = "Nicholas";
person.age = 29;
//对象字面量表示法创建 // 再进一步缩写 可省略对象名
var person = {
name : "Nicholas",
age : 29 //最后一个属性后不能加逗号(,)
};
// 通过点.或中括号[]来访问对象
console.log(person.name); //点表示法:更简洁(推荐)
console.log(person["name"]); //方括号表示法:更方便,可直接通过变量名访问
3.1.2 属性与方法
- constructor 返回用于创建当前对象的函数
-
hasOwnProperty( ) 用于检查给定的属性在当前对象实例中是否存在
-
isPrototypeOf( ) 用于检查传入的对象是否是传入对象的原型
-
propertyIsEnumerable( ) 用于检查给定的属性是否能够使用 for-in 语句来枚举
-
toString( ) 返回对象的字符串表示
-
toLocaleString( ) 返回对象的字符串表示,该字符串与执行环境的地区对应
-
valueOf( ) 返回对象的字符串、数值或布尔值表示
复制变量值
复制引用类型的值,副本变量是一个指针(并非自身),原变量与副本都将引用同一个对象,其一改变都会影响另一变量,因为ECMAScript 中所有函数的参数都是按值传递
的,即与复制变量值一样
var obj1 = new Object();
var obj2 = obj1;
obj1.name = "Nicholas";
alert(obj2.name); //"Nicholas"
3.2 Array
- Array类型是一组值的有序列表
- 可以存储多个不同类型的数据
- 数组是一种特殊类型的对象
3.2.1 创建与访问
// new操作符构建法
var arr = new Array(); //创建空数组
var arr = new Array(20); //创建确定容量的数组
var arr = new Array("red", "blue", "green");//创建带有数据的数组
var arr = Array(); //省略new 构造法
// 字面量表示法
var arr = []; // 创建空数组
var arr = ["red","blue","green"]; //创建一个包含3个字符串的数组
var arr = [1,2,]; //不要这样!这样会创建一个包含2或3项的数组
var arr = [,,,,,]; //不要这样!这样会创建一个包含5或6项的数组
- 推荐使用字面量表示法来创建数组
- 若函数没有return,使用new会返回一个对象,不使用new会返回undefined
var colors = ["red", "blue", "green"]; // 序号为0、1、2…
// 通过方括号中的数字下标索引,来访问数组中的值
var color1 = colors[0]; //创建一个变量保存数组第一项
console.log(color1); //"red"
// 通过赋值来修改数组
colors[2] = "black"; //修改替换第三项
colors[3] = "brown"; //新增第四项(索引超过项数时)
console.log(colors); //"red","blue","black","brown"
3.2.2 方法
检测方法
if (value instanceof Array){} //旧方法,可能分别具有各自不同的构造函数
if (Array.isArray(value)){} //新方法,能最终确定某个值到底是不是数组
遍历方法
var array = [1,2,3,4,5];
// forEach():ES5的新方法,不支持低版本IE
array.forEach(function(item, index, array){
console.log(index + "-" + item);
}); //0-1 1-2 2-3 3-4 4-5
// for-in 快速遍历
for(var i in array){
console.log(i); //0 1 2 3 4
console.log(array[i]);} //1 2 3 4 5
// for 循环遍历
for(var i=0,len=array.length; i<len; i++){
console.log(array[i]);} //1 2 3 4 5
长度方法
var num = [1,2,3,4,5];
//查询数组长度(个数)
console.log(num.length); //5
//数组长度增加与减少
num.length = 7; //增加时,默认赋值undefined
console.log(num); //1,2,3,4,5,undefined,undefined
num.length = 3; //减少时,从末项开始删除
console.log(num); //1,2,3
//数组元素值的修改、添加与删除
num[2] = 10; //若索引号重复,直接覆盖替换
console.log(num); //1,2,10,4,5
num[7] = 10; //若索引号超过数组长度,则添加指定值并自动补齐undefined
console.log(num); //1,2,3,4,5,undefined,undefined,10
delete num[2]; //delete操作符不会改变数组长度,只是替换对应值为undefined
console.log(num); //1,2,undefined,4,5
操作方法
var num = [1,2,3];
// 栈方法 (类似栈“后进先出”)
var numPush = num.push(7, 8); //在数组末尾推入两项
console.log(numPush); //5 (返回修改后数组的长度)
console.log(num); //1,2,3,7,8
var numPop = num.pop(); //删除数组的末项元素
console.log(numPop); //3 (返回移除的项的长度)
console.log(num); //1,2
//队列方法 (类似队列的“先进先出”)
var numShift = num.shift(); //移除第一项
console.log(numShift); //1 (返回该项,这是一个值!)
console.log(num); //2,3
var numUnshift = num.unshift(4,5,6); //在前端添加任意个项
console.log(numUnShift); //6 (返回新数组的长度)
console.log(num); //4,5,6,1,2,3
//字符串转换方法
var numJoin = num.join(-); //将数组中的元素用一个字符串拼接起来
console.log(numJoin); //1-2-3 (返回拼接后的结果)
console.log(num); //1,2,3 (原数组不会改变)
var num = [1,2,3,4,5,6];
//截取方法
var numASlice = num.slice(1); //单参数,返回该项至末项的所有项
var numBSlice = num.slice(1,4); //双参数,返回两参数对应项间不含末项的所有项
console.log(numASlice); //2,3,4,5
console.log(numBSlice); //2,3,4
console.log(num); //1,2,3,4,5,6 (原数组不会改变)
//修改方法1:删除
var numASplice = num.splice(1,2); //两参数(起始位置、删除项数),
console.log(numASplice); //2,3 (返回删除项)
console.log(num); //1,4,5,6
//修改方法2:替换
var numBSplice = num.splice(2,2,9,11);//多参数(起始位置、删除项数、插入项..),
console.log(numASplice); //3,4 (返回删除项)
console.log(num); //1,2,9,11,5,6
//拼接方法
var numConcat = num.concat("ok",true,[9,11]);//
console.log(numConcat); //1,2,3,4,5,6,"ok",true,9,11
console.log(num); //1,2,3,4,5,6 (原数组不会改变)
重排序方法
var num2 = [10,5,40,25,1000,1];
var num3 = ["George","John","Thomas","James","Adrew"];
//倒置数组项的顺序
var num2Reverse = num2.reverse();
console.log(num2Reverse); //1,1000,25,40,5,10
//按字符编码顺序升序排序
var num2Sort = num2.sort();
console.log(num2Sort); //1,10,1000,25,40,5
var num3Sort = num3.sort();
console.log(num3Sort); //Adrew,George,James,John,Thomas
//以上两种方法都会改变原数组原来的排序
//冒泡排序
......
位置方法
var num = [2,5,3,4,5,6,1];
//顺序查找位置方法
var numX = num.indexOf(3) //单参数 (对应值)
var numY = num.indexOf(5,2) //双参数 (对应值,第几个值)
console.log(numX); //2 (从左到右找到第一个对应值3,返回索引值2)
console.log(numY); //4 (从左到右找到第二个对应值5,返回索引值4)
//逆序查找位置方法
var numM = num.lastIndexOf(6) //单参数 (对应值)
var numN = num.lastIndexOf(5,3) //双参数 (对应值,第几个值)
console.log(numM); //5 (从右到左找到第一个对应值6,返回索引值5)
console.log(numN); //-1 (从右到左找到第三个对应值5,未找到,返回索引值-1)
迭代方法
var a = [1, 2];
//使用迭代方法时,会接收2个参数(一个是每一项运行的函数,一个是运行该函数的作用域对象),这时会对数组中的每一项运行一个给定的函数,这个函数会接收三个参数(对应值,该项位置,数组对象本身)
//every():若该函数对“每”一项都返回true,则最终结果返回true
a.every(function(item, index, array){ //
return (item >1);}) //false
//some():若该函数对“任”一项都返回true,则最终结果返回true
a.some(function(item) {
return (item >1);}) //true
//filter():返回该函数中 会返回true的项 所组成的数组
a.filter(function(item) {
return (item >1);}) //[2]
//map():返回每次函数调用的结果 组成的数组
a.map(function(item) {
return item;}) //[1,2]
//forEach():此方法没有返回值
a.forEach(item) {
console.log(item);} //1 2
归并方法
var b = [1,2,3,4,5];
//reduce():顺序遍历
var sum1 = b.reduce(function(prev, cur, index, array){
return prev + cur;});
alert(sum1); //15
//reduceRight():逆序遍历
var sum2 = b.reduceRight(function(prev, cur, index, array){
return prev + cur;});
alert(sum2); //15
3.3 Date
3.3.1 时间格式
- 格里尼治时间(GTM):英国郊区格林尼治天文台时间,用于世界上某些重大时间
- 世界标准时间(UTC):世界时间1970年1月1日0时开始(毫秒数)
3.3.2 创建时间
//使用Date()函数创建
var nowDate = Date();
console.log(nowDate); //得到运行此语句时刻的当前时间
console.log(typeof nowDate); //string (时间实际上是字符串类型)
//使用构造函数创建(对象类型)
var nowDate = new Date();
console.log(typeof nowDate); //object
//可指定参数来设置时间格式
var date = new Date("2017/11/3 12:30:12");
console.log(date);//Wed Aug 07 2019 15:43:48 GMT+0800 (中国标准时间)
3.3.3 方法
//Date对象的方法大致分为3类
//get 获取(年、月、日、星期、时、分、秒、毫秒)
//set 设置(年、月、日、星期、时、分、秒、毫秒)
//to 格式化转换
//[注1]大部分的返回值中,0代表现实意义中的1(如1号、1月份、1秒)
//[注2]时间运算:两个时间对象相减,得到的是两个对象间相差的毫秒数
[图片上传失败...(image-2a712e-1587459554178)]
3.4 RegExp
正则表达式:用于匹配规律规则的表达式,一种过滤逻辑公式,通常被用来检索、替换那些符合某个模式(规则)的文本。
正则表达式实际上也是一种object类型。
3.4.1 创建
//字面量创建
var 变量名 = /表达式/匹配模式修饰符;
//构造函数创建
var 变量名 = new RegExp("表达式","匹配模式修饰符");
3.4.2 表达式
//常用
[abc] 查找方括号之间的任何字符
[^abc] 查找任何不在方括号之间的字符
[0-9] 查找任何从 0 至 9 的数字
[a-z] 查找任何从小写 a 到小写 z 的字符
[A-Z] 查找任何从大写 A 到大写 Z 的字符
[A-z] 查找任何从大写 A 到小写 z 的字符
a[bcd]e 字符中间匹配 (abe,ace,ade都行)
X|y|z 或匹配 (匹配x或y或z,存在其中一个即可)
//单字符&数字
. 匹配除换行符之外的任意字符 (匹配点"."需要转义字符/\./)
\d 匹配数字
\D 匹配非数字
\w 匹配字母
\W 匹配非字母
//空白字符
\0 匹配 null 字符
\s 匹配空白字符、空格、制表符或换行符
\S 匹配非空白字符
\n 匹配换行符
\r 匹配回车符
\f 匹配进纸符
\t 匹配制表符
\v 匹配垂直制表符
//定位符
^ 行首匹配
$ 行尾匹配
\A 只匹配字符串的开始处
\b 匹配单词边界,词在[]内无效(\bchild\b ≡ children× child√ )
\B 匹配非单词边界
\z 只匹配字符串结束处
\Z 匹配字符串结束处或行尾
\G 匹配当前搜索的开始位置
//进制数
\xxx 查找以八进制数 xxx 规定的字符
\xdd 查找以十六进制数 dd 规定的字符
\uxxxx 查找以十六进制数 xxxx 规定的 Unicode 字符
//限定符 量词
n+ 匹配任何包含至少一个n的字符串 (n+ ≡ a× n√ nnn√)
n* 匹配任何包含零个或多个n的字符串
n? 匹配任何包含零个或一个n的字符串
n{X} 匹配包含X个n的序列的字符串 (n{4} ≡ nnnn)
n{X,Y} 匹配包含X至Y个n的序列的字符串 (an{1,3} ≡ an√ ann√ annn√)
n{X,} 匹配包含至少X个n的序列的字符串 (an{2,} ≡ an× ann√ annnnn√)
^n 匹配任何开头为n的字符串 (^n ≡ an× na√ nan√)
n$ 匹配任何结尾为n的字符串
^n$ 匹配开头为n且结尾为n的字符串(^n$ ≡ an× na× nan√)
?=n 匹配任何其后紧接指定字符串 n 的字符串
?!n 匹配任何其后没有紧接指定字符串 n 的字符串
//引用
\1..\9
$1...$9 返回九个在模式匹配期间找到的、最近保存的部分
匹配模式修饰符
g globle 全局匹配模式(查找所有匹配,而不光在找到第一个匹配后停止)
i ignorCase 忽略大小写模式
m mutiple 允许多行匹配模式
3.4.3 属性
实例属性
global 检测是否设置g标记
ignoreCase 检测是否设置标记
multiline 检测是否设置了m标记
source 返回正则表达式的字符串表示
lastIndex 返回被查找字符串中下一次成功匹配的开始位置
构造函数属性
$_ input 返回最近一次匹配的字符串
$& lastMatch 返回最近一次的匹配项
$+ lastParen 返回最近一次匹配的捕获组
$` leftContext 返回被查找的字符串中从字符串开始位置到最后匹配之前的位置之间的字符
$' rightContext 返回被搜索的字符串中从最后一个匹配位置开始到字符串结尾之间的字符
s* multiline 检测表达式是否采用多行模式匹配m
3.4.4 方法
实例方法
//exec() 检索字符串中指定的值。返回找到的值,并确定其位置。
特性
- 有全局标记g 持续查找所有匹配项并返回
- 无全局标记g 始终返回第一个匹配项信息
执行过程
1.检索字符串参数,获取正则表达式匹配文本
2.找到匹配文本则返回一个数组
- 第0个元素 与整个模式匹配的字符串
- 其他元素 与捕获组匹配的字符串
3.否则返回null
//test() 在字符串中测试模式匹配,返回true或false
var reg = /aaa/;
console.1og(reg.test("aabc")); //false
字符串方法
//match() 将符合条件的正则表达式的内容提取出来
- 默认情况下,只会找到第一个符合要求的内容,找到后停止检索
- 可以设置全局匹配模式,这样就能匹配到所有符合的内容
- 匹配到的内容会封装到一个数组中返回,即使只查询到一个结果
var str = "1a2b3c4d5e6f7A8B9C";
var result1 = str.match(/[a-z]/);
var result2 = str.match(/[a-z]/ig); //可设置多个匹配模式 顺序无所谓
console.log(result1); //a
console.log(result2); //a,b,c,d,e,f,A,B,C
console.log(typeof result1); //object
//replace("表达式","替换内容") 将与正则表达式匹配的值替换为新的内容
- 默认只会替换第一个
var str = "1a2b3c4";
var result = str.match(/[a-z]/,"@_@");
var result2 = str.match(/[a-z]/ig,"@_@");
console.log(result1); //1@_@2b3c4
console.log(result2); //1@_@2@_@3@_@4
//search() 检索与指定的正则表达式相匹配的值
- 如果搜索到指定内容,则会返回第一次出现的索引
- 如果没有搜索到返回-1
var str = "hello abc hello aec afc";//搜索字符串中是否含有abc或aec或afc
var result = str.search(/a[bef]c/);
console.1og(result); //6 (在索引6处首次被搜到)
//split() 把字符串分割为字符串数组
var str="1a2b3c4d5e6f7";
var result=str.split(/[A-z]/); //1,2,3,4,5,6,7
3.4.5 实例
//手机号的规则 13567890123 (11位)
1.以1开头 ^1
2.第二位3-9任意数字 [3-9]
3.三位以后任意数字9个 [0-9]{9}$
var phoneStr = "13067890123"
var phoneReg = /^1[3-9][0-9]{9}$/;
console.log(phoneStr.test(phoneReg)); //ture
//去除误输入的字符前后空格 - 使用""来替换空格
var str = " he llo ";
str = str.replace(/\s/,""); //去除一个空格
str = str.replace(/\s/g,""); //去除所有的空格,包括中间
str = str.replace(/^\s*/,""); //去除开头的空格
str = str.replace(/\s*$/,""); //去除结尾的空格
str = str.replace(/^\s*$/g,""); //无效
str = str.replace(/^\s*|\s*$/g,"");//匹配开头和结尾的空格
console.log(str); //"hello"
//匹配中文字符
[\u4e00-\u9fa5]
//匹配双字节字符(包括汉字)
[^\x00-\xff]
//匹配空白行的正则表达式(用来删除空白行)
\n\s*\r
//匹配HTML标记(对复杂的嵌套标记无用)
<(\S*?)[^>]*>.*?</\1>|<.*? />
//匹配首尾空白字符(删除行首行尾的空白字符)
^\s*|\s*$
//匹配Email地址(表单验证)
\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
//匹配网址URL的正则表达式
[a-zA-z]+://[^\s]*
//匹配帐号是否合法(表单验证:字母开头,允许5-16字节,允许字母数字下划线)
^[a-zA-Z][a-zA-Z0-9_]{4,15}$
//匹配国内电话号码(形式如 0511-4405222 或 021-87888822)
\d{3}-\d{8}|\d{4}-\d{7}
//匹配腾讯QQ号(QQ号从10000开始)
[1-9][0-9]{4,}
//匹配中国邮政编码(6位数字)
[1-9]\d{5}(?!\d)
//匹配身份证(一代15位或二代18位)
\d{15}|\d{18}
//匹配ip地址(用于提取ip地址)
\d+\.\d+\.\d+\.\d+
//匹配特定数字(处理大量数据)
^[1-9]\d*$ //正整数
^-[1-9]\d*$ //负整数
^-?[1-9]\d*$ //整数
^[1-9]\d*|0$ //非负整数(正整数 与 0)
^-[1-9]\d*|0$ //非正整数(负整数 与 0)
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ //正浮点数
^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ //负浮点数
^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$ //浮点数
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$ //非负浮点数
^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$ //非正浮点数
//匹配特定字符串
^[A-Za-z]+$ //由26个英文字母组成的字符串
^[A-Z]+$ //由26个英文字母的大写组成的字符串
^[a-z]+$ //由26个英文字母的小写组成的字符串
^[A-Za-z0-9]+$ //由数字和26个英文字母组成的字符串
^\w+$ //由数字、26个英文字母或者下划线组成的字符串
3.5 Function
函数实际上是 Function 类型的实例,因此函数也是对象;而这一点正是 JavaScript有特色的地 方。由于函数是对象,所以函数也拥有方法,可以用来增强其行为。
属性与方法
-
arguments
- arguments和this类似,都是函数中的隐含的参数
- arguments是一个类数组元素,它用来封装函数执行过程中的实参
所以即使不定义形参,也可以通过arguments来使用实参 - arguments中有一个属性callee表示当前执行的函数对象
-
this 函数的上下文对象,根据函数的调用方式不同会执向不同的对象
- 以函数的形式调用时,this是window
- 以方法的形式调用时,this是调用方法的对象
- 以构造函数的形式调用时,this是新建的那个对象
- 使用call和apply调用时,this是指定的那个对象
- 在全局作用域中this代表window
- call( ) 直接调用函数1,直接传递函数的实参
- apply( ) 直接调用函数2,将实参封装到一个数组中传递
3.6 Boolean
Boolean 对象在 ECMAScript 中的用处不大,因为它经常会造成人们的误解
var falseValue = false; //创建布尔值
result = falseValue && true;
alert(result); //false
var falseObject = new Boolean(false); //创建布尔对象
var result = falseObject && true;
alert(result); //true
//所以进行逻辑运算时,有这样一个规律
Boolean() && boolean //结果是boolean
boolean && Boolean() //结果是Boolean( )
//基本类型与引用类型的布尔值在进行typeof()instanceof()测试时,返回的结果都不同
alert(typeof falseObject); //object
alert(typeof falseValue); //boolean
alert(falseObject instanceof Boolean); //true
alert(falseValue instanceof Boolean); //false
//所以,建议是永远不要使用 Boolean 对象!!!
3.7 Number
Number对象提供了
valueOf() toLocaleString() toString() toFixed() toExponential() toPrecision()
等方法,但是对不同的浏览器兼容性不同,并且在进行typeof()instanceof()测试时出现的情况与 Boolean 对象相似。因此,仍然不建议直接使用实例化 Number 对象!!!
3.8 String
字符串有两种形式:基本类型、对象类型
- 字符串在底层中是以字符数组的形式保存的
- 对象类型字符串封装了一些属性及方法,而基本类型则没有
可直接用基本类型字符串调用字符串对象封装的属性及方法,无须手动转换
//基本类型字符串 String
var str1 = "Hello World";
console.log(typeof str1); //string
//对象类型字符串 Object
var str2 = new String("Hello World");
console.log(typeof str2); //object
3.8.1 方法
字符方法
var str ="what color do you like";
//charAt(index) 获取对应下标处的字符
console.log(str.charAt(9)); //r
console.log(str.charAt(30)); //""(未找到返回空字符串)
console.log(typeof str.charAt(30)); //string
//charCodeAt(index) 获取对应下标处的字符编码(0~65535)
console.log(str.charCodeAt(3)); //116 (ASCII码)
console.log(str.charCodeAt(30)); //NaN
console.log(typeof str.charCodeAt(30)); //number
//下标索引直接获取法 (IE7即以下不支持)
console.log(str[11]); //d
//String.fromCharCode(ASCII) 获取ASCII码对应的字符
var str1ASCII = String.fromCharCode(117);
console.log(str1ASCII); //u
var str2ASCII = String.fromCharCode(104, 101, 108, 108, 111);
console.log(str2ASCII); //"hello"
操作方法
var str = "hello world";
//concat() 将字符串拼接起来,返回拼接后的新字符串
var strConcat1 = str.concat(" wow");
var strConcat2 = str.concat(" wow", "!"); //多拼接
console.log(strConcat1); //"hello world wow"
console.log(strConcat2); //"hello world wow!"
console.log(str); //"hello world"
//slice() 截取介于两个指定下标之间的字符(开始索引,结束索引)
//substring() 截取介于两个指定下标之间的字符(开始索引,结束索引)
//substr() 截取从头索引开始的指定个数的字符(开始索引,截取位数)
//单参数时,默认截取到尾部
console.log(str.slice(3)); //"lo world"
console.log(str.substring(3)); //"lo world"
console.log(str.substr(3)); //"lo world"
console.log(str.slice(3, 7)); //"lo w"
console.log(str.substring(3,7)); //"lo w"
console.log(str.substr(3, 7)); //"lo worl"
//参数为负值时,则从后往前截取 (substring所有负值参数都转换为0)
console.log(stringValue.slice(-3)); //"rld"
console.log(stringValue.substring(-3)); //"hello world"
console.log(stringValue.substr(-3)); //"rld"
console.log(stringValue.slice(3, -4)); //"lo w"
console.log(stringValue.substring(3, -4)); //"hel"
console.log(stringValue.substr(3, -4)); //""(空字符串)
console.log(str); //"hello world"(原字符串不改变)
分割方法
//split() 把一个字符串分割成字符串数组
var str= "Tom is a good man!";
//未指定长度
console.log(str.split(" ")); //["Tom","is",a","good","man!"]
//指定返回数组的最大长度3
console.log(str.split(" ",3)); //["Tom","is","a"]
console.log(str.split("",6)); //["T","o","m","","i","s"]
位置方法
var str = "hello world";
//indexOf() 顺序查找指定字符,并返回索引值
//lastIndexOf() 逆序序查找指定字符,并返回索引值
console.log(str.indexOf("o")); //4
console.log(str.lastIndexOf("o")); //7
//双参数时,第二个参数表示从哪个索引值位置开始搜索
console.log(str.indexOf("o", 6)); //7
console.log(str.lastIndexOf("o", 6)); //4
//若找不到指定字符,会返回-1
console.log(str.indexOf("y")); //-1
修整方法
//trim() 删除前置及后缀的所有空格创建其副本,返回结果
var str = " hello world ";
var strTrim = str.trim();
console.log(strTrim); //"hello world"
console.log(str); //" hello world "(原字符串不改变)
//trimLeft() 删除头部所有空格创建其副本,返回结果
//trimRight() 删除尾部所有空格创建其副本,返回结果
大小写转换方法
var str = "hello world";
console.log(str.toUpperCase()); //"HELLO WORLD" (转大写)
console.log(str.toLowerCase()); //"hello world" (转小写)
//针对特定地区的少数语言,使用以下方法
console.log(str.toLocaleUpperCase()); //"HELLO WORLD"
console.log(str.toLocaleLowerCase()); //"hello world"
console.log(str); //"hello world"(原字符串不改变)
模式匹配方法
//match()
//search()
//replace() 字符串替换
var str = "A good man!A nice man";//只能替换第一次出现的字符串
var strRep = str.replace("man","woman");
console.log(strRep); //"A good woman!A nice man"
strRep = str.replace(/man/g,"woman");//通过正则表达式实现全部替换
console.log(strRep); //"A good woman!A nice woman"
console.log(str); //"A good man!A nice man" (原字符串不变)
比较大小方法
//localeCompare() 从左往右单字符比较其ASCII码值,值大者大
var str = "yellow";
console.log(str.localeCompare("brick")); //1 (yellow更大)
console.log(str.localeCompare("yellow")); //0 (相等)
console.log(str.localeCompare("zoo")); //-1 (zoo更大)
HTML方法
早期时为了方便而扩展这种动态格式化HTML标准,但无语义化,==请避免使用!==
[图片上传失败...(image-a69306-1587459554178)]
3.9 Global
3.9.1 方法
URI 编码方法
//encodeURI() 对本身不属于URI的特殊字符进行编码
//encodeURIComponent() 对任何非标准字符进行编码 (开发中更常用)
var uri = "http://www.wrox.com/illegal value.htm#start";
alert(encodeURI(uri));
//"http://www.wrox.com/illegal%20value.htm#start"
alert(encodeURIComponent(uri));
//"http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start"
URI 解码方法
//decodeURI() 只能对使用encodeURI()替换的字符进行解码
//decodeURIComponent() 可解码任何特殊字符的编码
var uri="http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start";
alert(decodeURI(uri));
//http%3A%2F%2Fwww.wrox.com%2Fillegal value.htm%23start
alert(decodeURIComponent(uri));
//http://www.wrox.com/illegal value.htm#start
在以前会使用escape()和unescape()方法,但在ECMA-262第3版已经被废弃,请不要使用!!!
ECMAScript 解析器
//eval() 获取返回值,可将字符串转换为代码执行,并返回一或多个值,在执行时遇错,则抛出异常给调用者
eval("alert('hi')"); //等价于 alert("hi");
var msg = "hello world";
eval("alert(msg)"); //"hello world"
eval()方法解释代码字符串的能力非常强大,但也非常危险,所以谨慎使用!!!
3.9.2 属性
[图片上传失败...(image-ec8db-1587459554178)]
浏览器都会将全局对象作为window对象的一部分来实现。因此在全局作用域中声明的所有变量和函数,都是window 对象的属性
3.X Math
3.X.1 属性
Math.E //自然对数的底数,即常量 e 的值
Math.LN10 //10的自然对数
Math.LN2 //2的自然对数
Math.LOG2E //以2为底 e 的对数
Math.LOG10E //以10为底 e 的对数
Math.PI //π的值
Math.SQRT1_2 //1/2的平方根(即2的平方根的倒数)
Math.SQRT2 //2的平方根
3.X.2 方法
Math.round(4.5) //5 四舍五入
Math.ceil(3.1) //4 向上取整
Math.floor(3.9) //3 向下取整
Math.max(3,6,76,8,89,32) //89 取最大值
Math.min(3,6,76,8,89,32) //3 取最小值
Math.abs(-10) //10 取绝对值
Math.pow(2,3) //8 x的y次方
Math.sqrt(25) //5 开平方
Math.random() //0.6334986525422 生成一个0-1之间的随机数
4 DOM/BOM
定时器
function fn1(){
console.log('test1');
}
function fn2(){
console.log('test2');
}
function fn3(){
console.log('test3');
}
function fn4(a){
console.log(a);
}
setTimeout(fn1, 2000); // 无括号 定时执行
setTimeout(fn2(), 2000); // 空参数 立即执行
setTimeout('fn3()', 2000); // 空参数作为字符串 定时执行
setTimeout(fn4('test4'), 2000); // 带参数 立即执行
setTimeout("fn4('test5')", 2000); // 带参数作为字符串 定时执行
/* --------最后执行结果------
(立即)
test2
test4
(2s后)
test1
test3
test5
--------------------------*/
5 函数
- 返回值:函数执行的结果
- 使用
return
来设置函数的返回值,它可以被一个变量所接收 - return 后的代码不会执行,一旦执行到return语句时,函数将会立刻退出
- return 后可跟任意类型的值,基本数据类型或对象
- 如果return后不跟值,或者是不写return,则函数默认返回undefined
- 使用
- 参数:
- 形参:function(1, 2)
- 实参:function(a, b)
- 作用域:一个变量的作用范围
- 全局作用域
- 全局作用域在打开页面时创建,在页面关闭时销毁
- 全局作用域中有一个全局对象window,window对象由浏览器提供
- 在全局作用域中创建的变量和函数可以在页面的任意位置访问
- 尽量不要在全局中创建变量
- 函数作用域
- 每次调用函数都会创建一个新的函数作用域
- 函数作用域在函数执行时创建,在函数执行结束时销毁
- 在函数作用域中创建的变量,不能在全局中访问
- 当在函数作用域中使用一个变量时,它会先在自身作用域中寻找,如果找到了则直接使用,如果没有找到则到上一级作用域中寻找
- 全局作用域
- 函数的声明提前:和变量的声明提前一样,在全局作用域中,使用函数声明
function
创建的函数,会被浏览器预先把声明调到前面去,即预解析。
6 JS深入
6.1 重新认识JS
-
JS是一种JIT编译语言,也叫“即时编译”,是”动态编译“的一种,当某段代码即将第一次被执行时进行编译。解释一行执行一行,相较于其他语言较慢。
java、c#等语言是“编译执行”,一次性把代码编译成可执行代码,再一行一行执行,速度较快。
-
具有灵活性,动态特性,可以随时随意给对象增加属性和方法
-
函数是JS中的一等公民
-
一般在宿主环境下运行,如浏览器(除node.js)
-
JS运行前会进行“预解析”,先全局再函数内部
6.2 面对对象编程
6.2.1 编程思想
- 面向过程:所有的事情都亲力亲为,注重
过程
- 面向对象:提出需求 - 找对象 - 对象解决,注重
结果
- JS不是一门面向对象的语言,而是基于对象,将执行者转变成指挥者,用JS来模拟面向对象的语言,它不是面向过程的替代,而是面向过程的封装
- 面向对象の特性
- 封装:即包装,把一些重用的内容进行打包,在需要时直接使用
- 继承:类与类之间的关系,JS中没有类的概念,但有构造函数的概念,基于原型,是可以有继承的
- 多态(抽象性):同一个行为,针对不同的对象,产生不同的效果
- 面向对象编程(OOP),是一种编程开发思想,是过程式代码的一种高度封装,目的在于提高代码的开发效率和可维护性,比起传统过程式编程,更适合多人合作大型软件项目
- 面向对象的抽象程度比函数要高,因为一个类/模板(Class)既包含数据,又包含操作数据的方法
6.2.2 体会面向对象
//面向过程的方式
// 1 记录学生的成绩
var stu1 = {name: 'zs', subject: '语文', score: 90};
var stu2 = {name: 'ls', subject: '语文', score: 80};
// 2 打印学生的成绩
console.log(stu1.name, stu1.subject, stu1.score);
console.log(stu2.name, stu2.subject, stu2.score);
// 面向对象的方式
/*-----------------------------------------
首选思考的不是程序执行流程,而是把`Student`视为对象,并拥有`name`和`score`两个属性;
而打印成绩,首先必须创建出这个`Student`对应的对象,再给对象发一个`printScore`消息,让对象把自己的数据打印出来
-----------------------------------------*/
// 1 抽象数据行为变成模板 (Class)
function Student(name, subject, score) {
this.name = name;
this.subject = subject;
this.score = score;
this.printScore = function () {
console.log(this.name, this.subject, this.score);
}
}
// 2 根据模板创建具体实例对象 (Instance)
var stuA = new Student('zs', '语文', 90);
var stuB = new Student('ls', '语文', 80);
// 3 实例对象具有自己的具体行为 (指挥Instance得到结果)
stuA.printScore();
stuB.printScore();
/*-----------------------------------------
在此例中,我们处理的多个对象都具有一个共同点,即他们都是学生,所以把他们抽象归成 类/模板(Class),再根据每一个具体的学生去创建具体的实例对象(Instance),最后再单独赋予给他们各自的具体行为
-----------------------------------------*/
- 在JS中创建对象的模板是构造函数,而在其他语言中创建对象的模板是类
- 创建实例对象(Instance)时,需要使用new操作符
6.2.3 创建对象的方法
// 1 new Object()创建
// var person = {}; 也可以省略new Object() 缩写成{}
var person = new Object();
person.name = "Nico";
person.age = 21;
person.sayHi = function(){
console.log("hello, I am" + this.name);
}
// 2 对象字面量创建
var person = {
name: "Nico",
age: 21;
sayHi: function(){
console.log("hello, I am" + this.name);
}
}
// 3 工厂函数
/*---------------------------------------
当要创建多个实例对象时 就要重复写很多份对象 代码太冗余 不方便 使用我们使用工厂函数 它用于批量创建多个对象 解决代码重复问题
----------------------------------------*/
function createPerson (name, age) {
return {
name: name,
age: age,
sayHi: function () {
console.log("hello, I am" + this.name);
}}}
// 再生成实例对象
var p1 = createPerson("Nico", 21);
var p2 = createPerson("Mike", 18);
/*-------------------------------------
工厂函数虽解决了批量创建对象的问题 但存在缺点:它无法确定真实的对象类型
-------------------------------------*/
console.log(typeof p1); // Object
console.log(p1 instanceof createPerson); // false
构造函数
构造函数是专门用来创建对象的函数
一个构造函数我们也可以称为一个类
通过一个构造函数创建的对象,我们称该对象时这个构造函数的实例
通过同一个构造函数创建的对象,我们称为一类对象
构造函数就是一个普通的函数,只是他的调用方式不同
- 如果直接调用,它就是一个普通函数
- 如果使用new来调用,则它就是一个构造函数
// 4 构造函数
/*------------------------------------
1 这里引入更优解 即更优雅的工厂函数 即构造对象的函数
2 构造函数比工厂函数创建更方便
2.1 会在内存中创建一个空对象 (无需内部创建新对象)
2.2 直接将属性和方法赋给了this 让this指向刚刚创建好的对象
2.3 执行构造函数中的代码
2.4 没有return语句 无需返回对象(会自动返回)
3 构造函数一般用首字母大写 普通函数用小写 便于区分
4 同时解决了工厂函数不能识别对象具体类型的缺陷
------------------------------------*/
function Person (name, age) {
this.name = name,
this.age = age,
this.sayHi = function () {
console.log("hello, I am" + this.name);
}}
// 生成实例对象必须使用new操作符
var p1 = new Person("Nico", 21);
var p2 = new Person("Mike", 18);
console.log(p1 instanceof Person); // true
/*---------------------------------------
每一个实例对象中的 __proto__ 中有一个constructor属性,该属性指向创建该实例的构造函数,它最初是用来标识对象类型的,它虽然可以用于检测对象的类型,但是生产中还是使用instanceof操作符更可靠。
console.log(p1.constructo); // 返回它的对象构造器
console.log(p1.constructor === Person); // true
--------------------------------------*/
/*-----------------------------------------
用上面的方法去构造函数虽然方便,但当多个对象去调用这个构造函数时,会产生存储多个方法的内存区域,会造成极大的内存浪费。
p1.sayHi();
p2.sayhi();
console.log(p1.sayHi === p2.sayHi); // false
// 说明两者不是指向同一方法 而是创建了两块内存区来存储
所以我们把需要共享的函数定义到构造函数的外部,但若有多个需要共享的函数,可能会造成全局命名空间冲突的问题,因此有可以把多个函数放到一个对象中来保存来避免问题。
----------------------------------------*/
function sayHi() {
console.log("hello, I am" + this.name);
}
//或
var fns = {
sayHi: function () {
console.log("hello, I am" + this.name);
},
sayAge: function () {
console.log("I am" + this.age);
}
}
// 这样基本上解决了构造函数的内存浪费问题 但代码看起来会有些怪 所以引入原型
原型
通过构造函数创建的对象,解析器都会默认在函数中添加一个prototype属性对象,即每一个构造函数都有一个prototype属性,称为原型/原型对象。原型中的成员,都会被构造函数的实例间接继承,故可以把所有对象实例需要共享的属性和方法直接定义在prototype对象上。
// 5 原型
/*-----------------------------------------
-------------------------------------------*/
function Person (name, age) {
this.name = name;
this.age = age;
}
Person.prototype.type = "student";
Person.prototype.sayHi = function () {
console.log("hello, I am" + this.name);
}
var p1 = new Person("Nico", 21);
var p2 = new Person("Mike", 18);
console.log(p1.sayHi === p2.sayHi); //true
构造函数、实例、原型三者之间的关系
(图略)
console.log(p1.__proto__ === Person.prototype); //true console.log(p1.constructor === Person); //true
prototype
属性是一个对象,所有实例属性和方法都指向它,它们共用同一个内存地址,标准属性,给程序员使用__proto__
属性是非标准的属性,指向构造函数的原型,供浏览器使用constructor
是构建器,指向构建函数
实例对象访问原型对象中的成员的搜索原则:原型链
- 搜索首先从对象实例本身开始
- 若在实例中找到了指定属性,则返回该属性的值
- 若未找到,则继续搜索指针指向的原型对象,在原型对象中查找指定属性
- 若在原型对象中找到了指定属性,则返回该属性的值
- 若一直到原型链末端还未找到,则返回
undefined
- Object的原型的原型为
null
// 6 最优的原型
/*-----------------------------------
为减少不必要的每次都输入一遍Person.prototype,常见做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象
----------------------------------*/
function Person (name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
constructor: Person, //避免原型对象丢失了constructor成员
type: "student",
sayHi: function () {
console.log("hello, I am" + this.name);
}
}
什么数据需要写在原型中
- 数据共享是原型的作用之一,故需要共享的数据就可以写在原型中
- 不需要共享的数据写在构造函数中
说明
- 任何函数都有prototype属性对象,包括原生对象的原型,如Date.prototype,因此可以利用此特性来扩展原生对象
- 最好不要让实例之间互相共享数组或对象成员,因为一旦修改会导致数据的走向很不明确,而且难以维护
- 原型对象的使用建议
- 私有成员(如非函数成员)放到构造函数中
- 共享成员(一般为函数)放到原型对象中
- 如果重置了
prototype
记得修正constructor
的指向原型的作用
- 数据共享(为了节省内存空间)
- 继承
6.3 继承
- 继承是指一个对象直接使用另一对象的属性和方法
- 一般会创建子类和父类,子类可使用父类的所有功能,且对这些功能进行扩展
- 继承有两种方式:接口继承和实现继承,ECMAScript只支持实现继承,主要是依靠原型链来实现
6.3.1 对象拷贝
对象拷贝:将对象的成员复制一份给需要继承的对象,但并非真正的继承,真正的继承是类与类的关系
// 案例1 一般的对象拷贝
var obj1 = {
name: 'zs',
age: 18,
sex: '男'
}
var obj2 = {};
// 封装函数 - 把o1的成员复制给o2
function copy(o1, o2) {
for (var key in o1) {
o2[key] = o1[key];
}
}
copy(obj1, obj2);
console.dir(obj2); // obj2 拥有了obj1的name age sex属性
obj1.name = 'xx'; // 修改obj1的成员
console.dir(obj2); // 不会影响obj2
那么要如何实现 完全的拷贝 呢?
这里涉及到递归的知识 去查看 ----> 递归 (按住Ctrl点击)
// 案例2
// 创建父对象
var father = {
name: '王健林',
money: 10000000,
cars: ['BMW', 'Tesla'],
hobby: function () {
console.log('Play golf');
}
}
// 创建需要继承的子对象
var son = {
name: '王思聪'
}
// 对象拷贝函数 (使用for...in...循环 并封装到函数中)
function extend(parent, child) {
for (var key in parent) {
if (child[key]) { // 若有相同属性则跳过
continue;
}
child[key] = parent[key];
}
}
extend(father, son);
// 此时son就也有了father的cars money hobby 但name依然自己保留
/*--------------------------------------
对象拷贝存在问题:
如果继承过来的成员是引用类型的话(即对象属性 对象里包含另一个对象),那么这个引用类型的成员在父对象和子对象之间是共享的,也就是说修改了之后,父子对象都会受到影响。
这个问题可以通过函数"递归"来解决。
---------------------------------------*/
6.3.2 原型式继承
// 借用构造函数的原型对象实现继承
// 创建父构造函数
function Parent(name){
this.name = name;
this.showName = function(){
console.log(this.name);
}
}
// 设置父构造器的原型对象
Parent.prototype.showAge = function(){
console.log(this.age);
}
// 创建子构造函数
function Child(){
}
// 设置子构造函数的原型对象实现继承
Child.prototype = Parent.prototype;
var child = new Child();
console.dir(child); //child只在原型上继承了showAge方法
/*--------------------------------------
问题:
父构造函数的原型对象和子构造函数的原型对象上的成员有共享问题,包含引用类型的属性值会共享,且只能继承父构造函数的原型对象上的成员, 不能继承父构造函数的实例对象的成员
--------------------------------------*/
6.3.3 原型链继承
原型链:每个对象都有原型对象,原型对象也有原型对象。由此,我们的对象,和对象的原型,以及原型的原型,就构成了一个原型链。
// 核心:子构造函数.prototype = new 父构造函数()
// 创建父构造函数
function Parent(){
this.name = 'baba';
this.age = 35;
this.showName = function(){
console.log(this.name);
}
}
// 设置父构造函数的原型
Parent.prototype.friends = ['小名', '小强'];
Parent.prototype.showAge = function(){
console.log(this.age);
}
// 创建子构造函数
function Child(){
}
// 实现继承
// 同时修改子构造函数的原型的构造器属性 防止执行错误
Child.prototype = new Parent();
Child.prototype.constructor = Child;
// 实例对象
var child = new Child();
console.log(child.name); // baba
console.log(child.age); // 35
child.showName(); // baba
child.showAge(); // 35
console.log(child.friends); // ['小名','小强']
// 当我们改变friends时, 父构造函数的原型对象的也会跟着变化
child.friends.push('小王');
console.log(child.friends); // ["小名", "小强", "小王"]
var father = new Parent();
console.log(father.friends); // ["小名", "小强", "小王"]
/*--------------------------------------------
特点:
- 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
- 父类新增原型方法/原型属性,子类都能访问到
问题:
- 创建子类实例时,不能给父构造函数传递参数
- 要想为子类新增属性和方法,必须创建实例对象后才行,不能放到构造器中
- 无法实现多继承
- 来自原型对象的引用属性是所有实例共享的,子对象修改时父对象也会被动修改
---------------------------------------------*/
6.3.4 借用构造函数
// 使用call借用其他构造函数的成员, 用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类
// 创建父构造函数
function Parent(name, age) {
this.name = name;
this.age = age;
}
Parent.prototype.sayName = function () {
console.log(this.name);
}
// 创建子构造函数
function Child(name, age, sex) {
Parent.call(this, name, age, sex);
this.sex = sex;
}
// 创建实例对象
var son = new Child('zs', 18, '男');
console.dir(son);
// son访问到了Child的sex 还访问到了Parent的name和age 但未访问到Parent原型中的sayName方法
/*-----------------------------------------------
特点:
- 解决了传递参数、子类实例共享父类引用属性的问题
- 可以实现多继承(call多个父类对象)但不完美,没有父类方法
缺点:
- 实例并不是父类的实例,只是子类的实例
- 只能继承父类的实例属性和方法,不能继承原型属性/方法
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
----------------------------------------------*/
6.3.5 组合继承
// 借用构造函数 + 原型式继承
// 创建父构造函数
function Parent(name, age) {
this.name = name;
this.age = age;
}
Parent.prototype.sayName = function () {
console.log(this.name);
}
// 创建子构造函数
function Child(name, age, sex) {
Parent.call(this, name, age, sex);
this.sex = sex;
}
// 通过原型,让子类型,继承父类型中的方法
Child.prototype = new Parent();
Child.prototype.constructor = Child;
// 可以赋予子类型特有的方法
Child.prototype.sayHi = function () {
console.log('你好');
}
/*-------------------------------------------------
特点:
- 弥补了借用构造函数的缺陷,可继承实例属性/方法 和 原型属性/方法
- 既是子类的实例,也是父类的实例
- 不存在引用属性共享问题
- 可传参
- 函数可复用
- 可以实现多继承
缺点:
- 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了,仅仅多消耗了一点内存)
---------------------------------------------------*/
6.3.6 寄生式继承
寄生式继承是与原型式继承紧密相关的一种思路,寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
6.3.7 寄生组合式继承
寄生组合式继承,集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式。
6.4 函数进阶
定义函数的方式有两种:函数声明、函数表达式
// 1 函数声明
function fn() {
console.log('这是函数声明!');
}
// 2 函数表达式
var fn = function () {
console.log('这是函数表达式');
}
fn();
区别:在执行函数时,
fn( );
的代码顺序对1函数声明
没有任何影响,但若把fn( );
写于2函数表达式
之前,程序会报错。原因:JS代码是按从上往下顺序链执行的,但在执行前会进行预解析,预先把代码中的函数声明(
function
)和变量声明(var
)提前,内部运行原理如下:fn1(); function fn1(){}; fn2(); var fn2 = function (){}; ===> function fn1(){}; // 被提前 var fn2; // 被提前 fn1(); // 运行无问题 fn2(); // 运行 发现首个空变量 运行出错! fn2 = function (){}; // fn2最后才被赋值 已无效
注意:不要在if else while for等语句中去定义一个函数声明,因为在现代浏览器中,这些语句中函数声明不会被提前,若这样做,运行时容易出错!
6.4.1 函数中this
this 指上下文对象,每次调用函数时,解析器都会将一个this作为隐含参数传递进函数。根据函数调用形式的不同,this的值也不同,函数的调用方式决定了this的指向
调用方式 | this指向 | 备注 |
---|---|---|
普通函数调用 | window | 严格模式下是 undefined |
构造函数调用 | 实例对象 | 原型方法中 this 也是实例对象 |
对象方法调用 | 该方法所属对象 | 紧挨着的对象 |
事件绑定方法 | 绑定/触发事件对象 | |
定时器函数 | window |
总结:函数内部的this,是由函数被调用时来确定其指向的,即 谁调用指向谁
6.4.2 函数对象
函数其实也是一种特殊的对象,即
Function()
,它拥有原型,拥有一些属性和方法,且所有函数都是Function
构造函数的实例[图片上传失败...(image-4d38b9-1587459554178)]
方法
call()
bind()
apply()
是函数常用的方法,用于改变函数中的this指向
function fn(x, y) {
console.log(this); // this原本指向 window
console.log(x + y); // 求两数之和
}
fn(5, 6); // 11
var another = {
value: "这是另一个对象的值"
}
fn.call(anotherFn, 3, 4); //返回 "anotherFn对象" 和 "7"
fn.apply(anotherFn, [3, 4]);//返回 "anotherFn对象" 和 "7"
fn.bind(anotherFn, 3, 4); //运行无反应 无效语句
var other = fn.bind(anotherFn, 3, 4); //只能先绑定函数
other(); // 再去运行变量 //返回 "anotherFn对象" 和 "7"
/*--------------------------------------------
call()
:调用一个函数,其具有一个指定的this
值和传入提供的参数
apply()
:调用一个函数,其具有一个指定的this
值和作为数组传入提供的参数
bind()
:创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体,目标函数会重新指定this指向,并传入参数
- 参数:
- 第一个参数 设置函数内部this的指向
- 其它参数,对应函数的参数
- call函数的返回值:
- 即指定函数传参后的返回值
call()和apply()的区别:call()可接受若干个参数的列表,而apply()接受的是一个包含多个参数的数组
call()和bind()的区别:call()改过this的指向后,会再执行函数,bind()改过this后,不会执行调用函数,而是把函数复制一份,返回给一个绑定新this的函数
共同点:一般都用于改变this的指向
// call()的应用
// 对于真正的数组,我们可以用push()和splice()方法对数组元素进行增加或删除,但对于伪数组,这些方法是不可行的,但可以调用call()方法来实现
var fakeArr = { // 这是一个伪数组
0: 100,
1: 10,
2: 11,
3: 20,
length: 4
};
//一般解决方法
fakeArr['4'] = 30;
fakeArr.length ++;
//调用call()方法 (length会自动增减 无需操作)
Array.prototype.push.call(fakeArr, 30); //增加元素
Array.prototype.splice.call(fakeArr, 0, 3); //删减元素
// apply()的应用
//Math.max()方法可以求传入数值参数的最大值或者打印出来,但若传入的是数组,它是不可行的,但可以调用apply()方法来实现
console.log(Math.max(3, 5, 6)); // 6 (可以求最大值)
var arr = [5, 10, 1, 3];
console.log(Math.max(arr));// NaN (说明不能求数组中的最大值)
// 调用apply()方法
console.log(Math.max.apply(null, arr)); // 10
console.log(Math.max.apply(Math, arr)); // 10 (指向Math)
console.log(1, 2, 3); // 1 2 3
console.log(arr); //(5) [5, 10, 1, 3] (不是我们想要的形式)
// 调用apply()方法 打印
console.log.apply(console, arr);//5 10 1 3 (指向console)
// bind()的应用 常用于改变this的指向
//实现1秒打印对象中的指定属性的值
var obj = {
value: '1234',
fn: function() {
setInterval(function() {
console.log(this.value);
}.bind(this), 1000);
}
}
obj.fn(); // (输出的是空字符串 并未实现打印)
btn.onclick = function () {
// 事件处理函数中的this 是触发该事件的对象
// 通过bind 改变事件处理函数中this的指向
}.bind(obj); // 1234...1234... (调用bind()实现)
属性
- arguments 伪数组 获取函数的实参 返回变成数组形式
- caller 返回函数的调用者,但在全局范围调用时返回 null
- length 返回形参的个数
- name 返回函数的名称
6.4.3 高阶函数
- 函数可以作为参数
- 函数可以作为返回值
// 作为参数
function eat(fn) {
setTimeout(function () {
console.log('吃完了');
fn();
}, 1000)
}
eat(function () {console.log('去唱歌');});//吃完了 去唱歌
// 作为返回值 (实际上利用到了"闭包"现象)
// 案例一 返回随机数
function getRandom() {
var random = parseInt(Math.random() * 10) + 1;
return function () {
return random;
}
}
var fn = getRandom();
console.log(fn()); // 7
console.log(fn()); // 7 (两次返回的随机数都不会改变)
// 案例二 求两个数的和
function getFun(n) {
return function (m) {
return n + m;
}
}
var fn100 = getFun(100); // 求 100 + m
var fn1000 = getFun(1000); // 求 1000 + m
console.log(fn100(1)); // 101
console.log(fn1000(1)); // 1001
6.5 闭包
闭包:即在一个作用域中可以访问另一个作用域的变量,它延展了函数的作用域范围,可简单理解成 “定义在一个函数内部的函数”,本质上就是将函数内部和函数外部连接起来的一座桥梁
闭包的用途:
- 解决变量私有化问题
- 可以在函数外部读取函数内部成员
- 让函数内成员始终存活在内存中
- 缓存数据
- 延长作用域链
// 未发生闭包
function fn() {
var n = 10;
return n;
}
fn();
// 发生闭包 (函数嵌套在return中 作为返回值返回)
function fn() {
var n = 10;
return function () {
return n;
}
}
var f = fn();
console.log(f()); // 10
/*----------------------------------------
函数在Global作用域上访问到了Local作用域中的n值,并出现Closure(fn()),这就是闭包,它会作为一个函数来返回
----------------------------------------*/
闭包案例1
<ul id="phone">
<li>华为</li>
<li>小米</li>
<li>vivo</li>
<li>oppo</li>
</ul>
<script>
// 实现点击li的时候输出当前li对应的索引
var phone = document.getElementById('heroes');
var list = heroes.children;
// 方式1 一般实现 (利用索引)
for (var i = 0; i < list.length; i++) {
var li = list[i];
li.index = i;
li.onclick = function () {
console.log(this.index);
}
}
// 方式2 用闭包实现 (利用自调用函数)
for (var i = 0; i < list.length; i++) {
var li = list[i];
(function (i) {
li.onclick = function () {
console.log(i);
}
})(i);
}
</script>
闭包案例2
console.log('start');
setTimeout(function () {
console.log('timeout');
}, 0);
console.log('over');
// 输出顺序 start over timeout
/*—————————————————————————————————————————————————————
为什么timeout会最后输出?
原因是setTimeout()本身就是一个函数,它被放到"执行栈"中,运行到它时,它会把内部存在的函数先暂存到"任务列表"中,待"执行栈"中的命令执行完,再去执行"任务列表"中的命令。
————————————————————————————————————————————————————*/
[图略]
console.log('start');
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 0);
}
console.log('end');
// 输出顺序 start over 3 3 3
console.log('start');
for (var i = 0; i < 3; i++) {
(function (i) {
setTimeout(function () {
console.log(i);
}, 0);
})(i);
}
console.log('end');
// 输出顺序 start over 0 1 2 (因为这里调用了自调用函数 产生了闭包 访问到了其他作用域)
闭包案例3
<body>
*** 改变这行文字的字体大小 ***
<div id="box">
<button id="btn1" size="12">按钮1</button>
<button id="btn2" size="14">按钮2</button>
<button id="btn3" size="16">按钮3</button>
</div>
<script>
var btn1 = document.getElementById('btn1');
var btn2 = document.getElementById('btn2');
var btn3 = document.getElementById('btn3');
// 方法1 一般实现
btn1.onclick = function () {
document.body.style.fontSize = '12px';
}
btn2.onclick = function () {
document.body.style.fontSize = '14px';
}
btn3.onclick = function () {
document.body.style.fontSize = '16px';
}
// 方法2 利用函数闭包实现
function makeFun(size) {
return function () {
document.body.style.fontSize = size + 'px';
}
}
btn1.onclick = makeFun(12);
btn2.onclick = makeFun(14);
btn3.onclick = makeFun(16);
// 方法3 利用闭包 避免重复代码
var box = document.getElementById('box');
var buttons = box.children;
for (var i = 0; i < buttons.length; i++) {
var btn = buttons[i]; // 为标签添加自定义属性
var size = btn.getAttribute('size');
btn.onclick = makeFun(size);
}
</script>
闭包思考
// 案例1
var name = "The Window";
var object = {
name: "My Object",
getNameFn: function () {
return function () {
return this.name; //指向全局
}
}
}
console.log(object.getNameFn()()); // The Window
// 案例2
var name = "The Window";
var object = {
name: "My Object",
getNameFn: function () {
var that = this; // 改变this指向
return function () {
return that.name; //指向object内部
}
}
}
console.log(object.getNameFn()()); // My Object
// getNameFn()() 首个括号仅返回函数 第二个括号返回内部的return值
6.6 递归
递归: 函数自己调用自己
注意:递归一般都要写一个结束的条件,防止过程中出错,造成内存溢出(超过了最大的堆栈大小,类似出现死循环)
递归案例1
// 用递归来实现 1 + 2 + 3 + 4 + .... + n
function getSum(n) {
if (n === 1) { // 设定递归结束条件
return 1;
}
return n + getSum(n - 1);
}
console.log(getSum(3)); // 6
/*--------------------------------------------
***递归内部过程***
getSum(3)
n = 3, 3 + getSum(3 - 1)
getSum(3 - 1)
n = 2, 2 + getSum(2 - 1)
getSum(2 - 1)
n = 1, 1
getSum(3) + getSum(3 - 1) + getSum(2 - 1) // 3+2+1
return 6
----------------------------------------------*/
递归案例2
// 用递归实现 n的阶乘 1 * 2 * 3....* n
function fn(n) {
if (n === 1) {
return 1;
}
return n * fn(n - 1);
}
console.log(fn(3));
// n = 3, 3 * fn(3 - 1)
// n = 2, 2 * fn(2 - 1)
// n = 1, 1
递归案例3
// 用递归实现 斐波那契数列 1、1、2、3、5、8、13、21、34、.....
function fn(n) {
if (n === 1 || n === 2) {
return 1;
}
return fn(n - 1) + fn(n - 2);
}
console.log(fn(3)); // 2
console.log(fn(5)); // 5
递归案例4
/*----------- 浅拷贝 --------------*/
// 若obj1中拥有 引用类型属性
var obj1 = {
name: 'zs',
age: 18,
sex: '男',
dog: { // 为obj1对象里又添加一层对象
name: '金毛',
age: 2,
yellow: '黄色'
}
}
var obj2 = {};
copy(obj1, obj2); //调用拷贝函数
// 再去修改obj1的成员
obj1.name = 'xx'; // 修改obj1的成员
obj1.dog.name = '大黄';
console.dir(obj2); // obj2的name属性没被改变 但是dog.name变了
/*------------------------------------------------------
原因:因为obj2拷贝过去的dog对象 与 obj1自己的dog对象 都是指向同一个对象,拷贝后,再做修改操作,等于是对深层中的同一个对象进行修改,由于两者共用共享这一对象,所以出现属性改变
而这里的拷贝,只复制了一层,即只是把第一层(浅层)的属性拷贝过去去生成一个副本,而下一层(深层)的对象,只做指向,并未被拷贝到另一个对象的副本中
**** 这被称为"浅拷贝" ****
-----------------------------------------------*/
/*----------- 深拷贝 --------------*/
var obj1 = {
name: 'zs',
age: 18,
sex: '男',
dog: {
name: '金毛',
age: 2
},
friends: ['ls', 'ww']
}
var obj2 = {};
// 封装"深拷贝"函数 把o1的成员拷贝给o2
function deepCopy(o1, o2) {
for (var key in o1) {
var item = o1[key];
if (item instanceof Object) { // 若item是对象{}
o2[key] = {};
deepCopy(item, o2[key]);
} else if (item instanceof Array) { // 若item是数组[]
o2[key] = [];
deepCopy(item, o2[key]);
} else { // (否则)若是简单类型
o2[key] = o1[key];
}
}
}
deepCopy(obj1, obj2); // 执行深拷贝函数
console.dir(obj2);
// 此时修改obj1中的成员 是否会影响obj2?
obj1.dog.name = 'xxx';
obj1.friends[0] = 'xxx';
console.dir(obj2); // 发现 obj2的全部属性都没有被改变
/*------------------------------------------------
这就是"深拷贝",复制对象成员时,把普通属性和对象属性(底层与深层)一起都复制过来,实现完全的拷贝。
--------------------------------------------------*/
(返回 [对象拷贝](#6.3.1 对象拷贝))
递归案例5
// 利用递归 实现遍历DOM树 即遍历某元素下的所有(包括更深层的)子元素
function loadTree(parent) {
for (var i = 0; i < parent.children.length; i++) {
var child = parent.children[i];
// console.log(child); 调试检查
loadTree(child); // 递归 调用自身
}
}
loadTree(document.body); // 遍历body标签下的所有子元素
// 实际上对于上例 仅实现遍历的意义并不大 这里赋予它更多的功能
// 传入一个新的功能 去操作遍历完后获取的元素对象 实现定制目的
function loadTree(parent, callback) {
for (var i = 0; i < parent.children.length; i++) {
var child = parent.children[i];
if (callback) {
callback(child);
}
loadTree(child); // 递归调用
}
}
// 遍历完ul列表 并实现点击某项列表项 显示它的内容
var ul = document.getElementById('list');
loadTree(ul, function (element) {
element.onclick = function () {
console.log(this.innerText);
}
});
网友评论