JavaScript基础

119 阅读15分钟

1 基本语法

1.1 JavaScript 在 HTML 中使用的三种方式

① 行内式

需要结合 JS 的事件来使用,把 JS 事件作为 HTML 标签的属性。

<button onclick="code..."></button>

② 内嵌式

使用 script 标签,把 js 代码写在 script 标签里面。 script 标签放在页面中的任意位置,代码都可以执行,建议 script 放在所有元素的后面(等到其他元素加载完了,在执行 JS)。

<script>
    code...
</script>

③ 外链式

把 JS 代码写在文件后缀是.js 的文件中,使用 script 标签的 src 属性指定外部 JS 文件的地址。标签仍然建议放在所有元素的最后面。

虽然外链式和内嵌式使用的都是 script 标签,但是一个 script 标签无法既外链又内嵌。

<script src="js文件地址"></script>

1.2 JavaScript 注释

// 单行注释

// 后面的内容都会被注释

/*
  多行注释
  多行注释
  多行注释
*/

1.3 JavaScript 语法特点

  • JS 严格区分大小写。
  • JS 由一条一条的语句组成(指令);每一条语句的结束需要写一个指令结束符; 指令结束符可以是换行也可以是分号。

1.4 JavaScript 中输出内容

① 输出到弹框

弹框出来之后,会暂停js程序的执行,直到用户点了确定,弹框后面的代码才继续执行。

alert(内容)

② 输出到页面

document.write(内容)

③ 输出到控制台

需要打开浏览器调试工具,切换到 console 就可以看到控制台输出。 推荐。

console.log(内容)

2 变量

2.1 数据、直接量、变量

数据: 计算机计算的就是数据,计算机可以对数据进行存储、计算。

直接量: 直接使用数据就是直接量。

变量: 给数据取了个名字,通过名字可以使用数据。 变量也可以认为是存储数据的容器。

2.2 变量的意义

把数据赋值给变量之后,就把这个数据存了下来,(在程序执行过程中)在任何适合时候使用变量都可以取到该数据。

使用变量可以保证数据的前后一致。

2.3 变量的语法

// 1. 声明变量并给变量赋值
var username = '朦朦';

// 2. 声明变量,不赋值
var age;

// 3. 一次同时声明多个变量
// var a = 100;
// var b;
// var c = 300;
var a = 100, b, c = 300;

// 4. 修改变量的值, 给已经声明过的变量重新赋值,无需关键子 var
username = '小乐';
  • 声明变量使用关键字 var,可以声明变量的同时赋值,也可以只声明变量然后以后赋值。
  • 变量的值可以修改,如果变量已经声明过,修改变量值的时候,无需添加关键字 var
  • 如果使用不存在的变量,会报错。

2.4 变量的命名规范(标识符的命名规范)

JS 中,变量名要遵循标识符的命名规范。

① 强制规范

  • 标识符必须由数字、字母、下划线、$ 组成,且不能以数字开头。
  • 标识符不能是关键字和保留字。
  • 标识符严格区分大小写。

关键字: JS 中具有特殊含义的一些词。

保留字: 当前还不是关键字但是以后可能成为关键字的词。

① 建议规范

  • 变量名要有意义,最好通过变量名就可以明白所表示的数据。
  • 如果变量名由多个单词组成,建议使用小驼峰命名法。
// 小驼峰命名法
var userInfo = [];
var vipUserAddress = {};

// 大驼峰命名法
var UserInfo = [];
var VipUserAddress = {};

// _ 命名法
var user_info = [];
var vip_info_address = {};

3 数据类型

3.1 数据类型的划分

① 原始类型

JS 中共有 5 种原始类型:

  1. number 数字类型
  2. string 字符串类型
  3. boolean 布尔类型
  4. undefined 未定义类型
  5. null 空类型

② 对象类型

  • Object 类型
  • Array 类型
  • .....

3.2 判断数据的类型

使用 JS 内置的函数 typeof() 来进行判断。

typeof(变量/直接量)

注意:

数据类型是 null 的数据,使用 typeof 判断会得到 object。

3.3 number 数值类型

① 整型

整数会以整型的形式进行存储和运算。

var num1 = 3453245;   // 10 进制表示
var num2 = 010;       // 8 进制表示
var num3 = 0x10;      // 16 进制表示  

② 浮点型

小数会以浮点型的形式进行存储和运算。

var num4 = 2.45; // 普通小数
var num5 = .67;  // 0.67 建议写成 .67
var num6 = 8.56e3;  // 科学计数法  即使最终是个整数也安装浮点型存储

注意:

浮点型数字的运算存在精度问题,因为转为 2 进制小数的时候计算机无法精准转换。

③ NaN

NaN 是一个特殊的 number 值,虽然不是常规的数字,但是数据类型就是 number。

NaN 的特点:

  1. NaN 与任何数进行任何运算,结果还是NaN。
  2. NaN 与任何数都不相等,包括自己。

NaN 的意义:

我们不会主动定义 NaN ,主要是其他数据类型转为 number 类型的时候,可能会转为 NaN,NaN 都是被动获取的。

isNaN() 函数

该函数用来判断数据是不是 NaN,如果是 NaN,返回 true,否则返回 false。

④ JavaScript 中数字的有效范围

如果数字超过 JS 可表示的最大有效范围,会表示为 Infinity-Infinity

Infinity-Infinity 也可以认为是特殊的 number 值,数据类型任然是 number

isFinite() 函数:

用来判断一个数字是否在有效范围内,如果在有效范围内,返回 true; 如果不在有效范围内(Infinity、-Infinity、NaN) 返回 false。

3.4 string 字符串类型

① 字符串的表示方式

  • 定义一个字符串,可以使用双引号也可以使用单引号,二者没有任何区别。
  • 双引号表示的字符串中不能有双引号,单引号表示的字符串中不能有单引号,否则语法错误; 非要嵌套可以转义。

② 转义字符

转义字符用来表示无法在字符串中正常书写的字符。

\n		表示换行符
\'		单引号的转义,用于单引号表示的字符串内
\"		双引号的转义,用于双引号表示的字符串内
\\		对反斜杠本身的转义
\uxxxx	使用4个16进制数字表示 unicode 编码对应的字符

3.5 boolean 布尔类型

boolean 类型的值就两个,true 和 false;

true 表示真,表示肯定; false 表示假,表示否定。

布尔值主要用来表示某种判断的计算结果。

3.6 null 和 undefined

undefined 类型的值就 undefined 自己。表示没有定义值,如变量声明没赋值,变量的值就是 undefined; undefined 一般被动产生的。

null 类型的值就 null 自己,表示空的、没有。 一般是主动定义的。

4 运算符

4.1 运算符和表达式

① 运算符

运算符就是参与运算的符号,像+-*/ 等。

与运算符一起运算的变量、直接量、表达式称之为操作数

② 表达式

表达式是进行计算的式子、表达式有一个最终计算结果,称之为表达式的值

最简单的表达式称之为原始表达式,一个直接量、一个变量都是原始表达式。

通过运算符可以把简单表达式组合成复杂一些的表达式。

有些表达式具有副作用,表达式在计算的过程中,会对参与运算的变量进行修改,称之为表达式的副作用, 表达式的副作用是由运算符决定的。

4.2 运算符的分类

① 按照运算符需要的操作数的个数

一元运算分(一目运算符)
二元运算符(二目运算符)
三元运算符(三目运算符)

② 按照运算符的功能

算术运算符
关系运算符(比较运算符)
逻辑运算符
位运算符
赋值运算符
其他运算符

4.3 运算符详细解(按照功能)

① 算术运算符

运算符含义操作数个数操作数的类型要求组成的表达式的值的类型有无副作用
+2numbernumber
-2numbernumber
*2numbernumber
/2numbernumber
%取余2numbernumber
+正号1numbernumber
-负号1numbernumber
++累加1numbernumber
--累减1numbernumber

+ 正号运算符

把操作数的值作为所组成的表达式值的值,不会做任何运算; 但是会创造一个 number 的运算环境,非 number 类型操作数会自动转换; 所以,该运算符经常用来把其他类型转为 number 类型。

累加和累减

累加或累减的运算符,可以在操作数的前面,也可以在后面; 运算符在前与在后,主要是表达式的值有所不同,但是副作用是一致的。

如果运算符在操作数的前面:把操作数累加或累减之后的值作为表达式的值。

如果运算符在操作数的后面:把操作数没有进行累加或累减之前的值作为表达式的值。

② 关系运算符(比较运算符)

运算符含义操作数个数操作数的类型要求组成的表达式的值的类型有无副作用
>大于2number / stringboolean
>=大于等于2number / stringboolean
<小于2number / stringboolean
<=小于等于2number / stringboolean
==相等2number / stringboolean
!=不相等2number / stringboolean
===全等2没有要求boolean
!==不全等2没有要求boolean

比较大小:

  1. 比较大小的运算符,如果两个操作数类型不一致,自动转为 number 进行比较;

  2. 如果两个操作数都是字符串,按照字符串比较的规则进行比较。字符串比较大小的规则: 按位比,当前位比较大,整个字符串都大; 比较的是每一位的字符对应的 ascii码。

判等规则 ==!= :

  1. 两个操作数的类型不一致,转为 number 之后再比较。

  2. 如果两个操作数都是字符串,安装字符串的规则来判断,两个字符串如果一样就相等。

  3. null 与其他值判断比较特殊:

    console.log(null == 0);  // false
    console.log(null == false);  // false
    console.log(null == '');  // false
    console.log(null == undefined);  // true
    console.log(null == null);   // true
    

全等的规则 ===!==

全等判断不会对操作数进行自动类型转换,如果两个操作数类型不一致,直接不全等; 如果两个操作数类型一致,值相同就全等。

推荐使用!!!

NaN 的比较规则:

NaN 与 任何操作数进行任何比较,都不成立,结果都是 false。(除了判不等 !=!==

常见的 ASCII 码

a -> 97, b -> 98 .....

A -> 65,B -> 66 ...

③ 逻辑运算符

运算符含义操作数个数操作数的类型要求组成的表达式的值的类型有无副作用
&&逻辑与(AND, 并且)2无要求根据操作数的类型
``逻辑或(OR,或者)2无要求根据操作数的类型
!逻辑非1booleanboolean

逻辑与组成的表达式的值:

如果第一个操作数成立,取第二个操作数作为整个表达式的值;

如果第一个操作数不成立,取第一个操作数作为整个表达式的值,第二个操作数直接忽略。

逻辑或组成的表达式的值:

如果第一操作数成立,取第一个操作数作为整个表达式的值,第二个操作数直接忽略。

如果第一个操作数不成立,取第二个操作数作为整个表达式的值。

④ 赋值运算符

运算符含义操作数个数操作数的类型要求组成的表达式的值的类型有无副作用
=赋值2类型无要求,左边操作必须是变量左边操作数被赋值之后的值
+=赋值求和2类型无要求,左边操作必须是变量左边操作数被赋值之后的值
-=赋值求差2类型无要求,左边操作必须是变量左边操作数被赋值之后的值
*=赋值求积2类型无要求,左边操作必须是变量左边操作数被赋值之后的值
/=赋值求商2类型无要求,左边操作必须是变量左边操作数被赋值之后的值
%=赋值求余2类型无要求,左边操作必须是变量左边操作数被赋值之后的值
+=赋值连接2操作数中只要有一个字符串,表示赋值连接左边操作数被赋值之后的值

注意:

  1. 赋值运算符所组成的表达式,都具有副作用;副作用是对左边的操作数(变量)的值进行修改,我们需要的就是副作用。
  2. 赋值运算符左边的操作数必须是变量形式,右边操作数的形式变量、直接量皆可。
  3. 赋值运算符所组成的表达式的值是赋值之后的左边的操作数。

⑤ 其他运算符

运算符含义操作数个数操作数的类型要求组成的表达式的值的类型有无副作用
+字符串连接符2stringstring
,逗号运算符2无要求第二个操作的值不确定
typeof类型判断1无要求string
?:条件运算符3第一个操作数要求是 boolean
第二个和第三个没有类型要求
从第二第三个操作数中取一个

+ 具有三个含义:

  1. 如果操作数只有一个,+ 是正号运算符。
  2. 如果操作数有两个,且其中一个是字符串或者两个都是字符串,+ 是字符串连接符。
  3. 如果操作数有两个,且没有任何一个操作数是字符串,+ 是加号运算符。

三元运算符组成的表达式取值规则:

  1. 如果第一个操作数成立,把第二个操作数作为表达式的值;此时第三个操作数不会执行。
  2. 如果第一个操作数不成立,把第三个操作数作为表达式的值;此时第二个操作数不会执行。

4.4 运算符优先级

一元运算符 (++、--、负号、正号、逻辑非、typeof)
算术运算符(*、 /、 %、 +、 - )
比大小
比相等
逻辑与和逻辑或
赋值运算符
三元运算符

注意:

可以通过添加 () 来提高优先级!

5 流程控制语句

5.1 条件语句

① 单向分支 if

if (条件表达式) {
    code ....
}

② 双向分支 if else

if (条件表达式) {
	code ...
} else {
	coce ...
}

③ 多向分支 else if

if (条件表达式) {
	code ...
} else if (条件表达式) {
    code ...
} else if (条件表达式) {
    code ...
} else {
    code ...
}

④ 多向分支 switch case

switch (表达式) {
    case 表达式可能的值: code...; break;
    case 表达式可能的值: code...; break;
    case 表达式可能的值: code...; break;
	case 表达式可能的值: code...; break;
    default: code...
}

特定:

  1. switch case 适合判等的多向分支的场景。
  2. switch case 判等是按照全等的规则进行判等。
  3. break 会结束本次 case 的执行,如果没有 break,代码会一直向下执行,直到遇到 break。

⑤ 嵌套分支

if (条件表达式) {
    if (条件表达式) {
    }
} else {
    switch (表达式) {
        case: break;
    }
}

5.2 循环语句

① while 循环

while (条件表达式) {
    code ...
}

注意:

  1. 循环的条件表达式不能一直都成立,否则会造成死循环。
  2. 正常的循环,随着循环次数增加,循环条件应该越来越趋近于不成立。

② do while 循环

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

while 循环和 do while 循环的区别:

do wilhe 循环只有第一次循环没有判断条件就行了,以后的每一次都是先判断条件成立了再执行。

③ for 循环

for (初始化循环标记变量; 循环条件; 循环标记变量的变化) {
    code ....
}

注意:

  1. 初始化循环标记标量,只在循环之前执行一次。
  2. 每次执行循环体代码之前,先判断循环条件是否成立。
  3. 循环标记变量的变化,等到大括号中循环体代码执行完毕才执行。

5.3 跳转语句

① break

在循环中使用,执行到 break,会结束整个循环。

在 switch case 使用,执行到 break,结束当前 case。

① continue

在循环中使用,执行到 contine,会跳出当前循环,本次循环内后面的代码不执行了,下次循环继续。

5.4 其他语句

① 异常处理语句

try {
    code ...
} catch (e) {
    // e 就是错误信息,对象类型的数据
}

写在 try 结构中的代码,一旦出现运行时错误,错误不会被系统抛出,被 catch 捕获,开发者可以按照需求自由处理。

try 结构以外的代码不会受到 try 代码错误的影响。

② 严格模式语句

'use strict'

开启严格模式后,一些旧的不合理的语法就不允许使用了。

use strict 代码通常写在最前面。

③ with 语句 (了解)

with (document) {
    write('朦朦<br>');
    write('大朦朦<br>');
    write('小朦朦<br>');
}

6 数组

6.1 什么是数组

  • 数据是值的有序集合
  • 数组中的每个值(成员)称之为元素
  • 每个元素在数组中都有位置,用数字表示,称之为索引 或者 下标
  • 数组中元素可以是任意类型的数据。
  • 数组索引从 0 开始, 最大为 2^32-2,数组最大能容纳 4294967294 个元素。

6.2 声明数组的方式

① 直接量方式

var arr1 = [];
var arr2 = [1000, 2000, '朦朦', true, null, undefined, [10,20,30]];
var arr3 = [100];

② Array 函数方式

var arr4 = Array();
var arr5 = Array('朦朦', '大悲', '曹操', 1000);
var arr6 = Array(90);  // 90会被作为元素的个数 
var arr7 = Array('下了');

注意:

如果 Array() 函数只有一个参数,且该参数是数字,这个参数会被当做元素的个数。

③ Array 构造函数方式

var arr8 = new Array();
var arr9 = new Array(100, 200,300,4005,66);
var arr10 = new Array(100);
var arr11 = new Array(true);

规则同 Array 函数相同!

6.3 数组元素的读写

// 使用数组中的元素	通过索引可以获取指定元素的值
arr[4]

// 可以数组元素重新赋值; 如果索引是不存在的元素会添加一个元素
arr[5] = 'hello';
arr.length;   // 所有的数组都具有 length 属性,通过 length 可以获取数组的长度(元素的个数)

6.4 稀疏数组

  • 数组元素的索引必须是连续的数字!
  • 如果隔着索引对数组元素进行赋值,会变为稀疏数组; 中间跨过去的索引也会自动填充,只是都没有值。

6.5 数组的遍历(迭代)

// 创建一个数组
var nameList = ['独孤奶奶', '欧阳奶奶', '东方奶奶', '西门奶奶', '慕容奶奶'];

// 使用 for 循环遍历数组
for (var i = 0; i < nameList.length; i ++) {
    console.log(i, nameList[i]);
}

// 使用 for in 遍历数组
for (var i in nameList) {
    console.log(i, nameList[i]);
}

注意:

更推荐使用 for 循环遍历数组!

6.6 数组元素的添加和删除

① 添加元素

1. 通过指定索引  arr[23] 保证索是下一个,否则出现稀疏数组
2. 利用 length, arr[arr.length]; arr.length 就是下一个索引。
3. arr.push()	在数组的最后面添加一个或多个元素
4. arr.unshift()	在数组的最前面添加一个或多个元素
5. arr.splice(索引, 0, 新元素...)	在数组的指定位置添加一个或多个元素

② 删除元素

1. 通过修改 length 的值删除后面的元素; arr.length -= 1 删除最后一个, arr.length -= 2 删除最后2个
2. arr.pop()	删除数组最后一个元素,一次只能删一个
3. arr.shift()	删除数组第一个元素,一次只能删除一个
4. arr.splice(索引,数量)	删除数组中指定位置以及数量的元素。	

6.7 多维数组

数组的元素还是数组,成为多维数组。

var arrs = [
    [100,200,300,4000], 
    ['A', 'B', 'C'], 
    10000, 
    'hello', 
    [],
    [
        ['司马奶奶', '诸葛奶奶', '夏侯奶奶'],
        ['东方姥姥', '令狐姥姥']
    ]
];


console.log(arrs[0][2]);   // 300
console.log(arrs[5][0][1]);  // 诸葛奶奶
console.log(arrs[5][1]);  // ['东方姥姥', '令狐姥姥']

6.8 字符串的数组特性

  • 字符串具有与数组相似的结构,可以认为字符串时字符的有序集合、
  • 字符串中每个字符也有索引,从 0 开始,通过 [] 可以读取到指定索引的字符。(只能读取无法修改)
  • 字符串也具有 length 属性,可以获取字符串的长度(字符的个数)。(length 只能读取)
  • 向字符串这种,不是数组但是却具有数组的某些特性,称之为 类数组伪数组

7 函数

7.1 函数概述

① 什么是函数

函数具有某种特定功能的代码块。

函数是 JavaScript 中的一种数据类型,属于对象类型,使用 typeof 判断函数的类型返回 function

② 函数的作用

避免代码重复冗余,提高代码重用性。

③ 函数的组成

7.2 声明函数的三种方式

注意:

函数名的命名规范同变量名一致,因为本质上函数名就是变量名。

① function 关键字方式

function 函数名([参数列表...]) {
    函数体语句;
}

② 表达式方式

var 函数名 = function([参数列表...]){
    函数体语句;
}

③ Function 构造函数方式 (基本不用)

var 函数名 = new Function('参数1', '参数2''参数3', '函数体语句;')

7.3 函数的调用和返回值

① 函数调用

  • 函数名加 () 才是调用函数,只有调用函数,函数内的代码才执行。
  • 函数名后面没有 (),这就是在使用变量的值,值就是一个 function 类型的数据(函数本身),不会调用函数。

② 返回值

  • 函数调用表达式的值就是函数的返回值,只有调用函数才能得到返回值。
  • 函数体内通过 return 返回,return 的右边可以写一个表达式,表达式的值就函数的返回值。
  • 如果函数体内没有 return 语句 或者 return 的右边是空的, 默认函数返回 undefiend
  • return 除了能返回一个值,还可以中断函数的执行(执行到 return)。

7.4 函数的参数

① 形参和实参

形参: 声明函数时给的参数就是形参;形参相当于变量,调用的时候才能赋值; 形参只能在函数里用;形参的形式只能是变量名。

实参: 调用函数时给的参数,实参会按照顺序赋值给对应的形参; 实参的形式可以是直接量、变量或表达式。

② 形参和实参的数量问题

  • 实参给多了,实参数量>形参数量,多给的白给。
  • 实参给少了,实参数量<形参数量,意味着有些形参无法被赋值,默认取值 undefined

③ 形参的默认值(可选参数)

设置方式一:在函数体内判断参数的值是否是 undefined

function fn(a, b, c,) {
    if (c === 'undefined') {
        c = '默认值';
    }
}

设置方式二:(ES6 新增,兼容性并太好)

function fn(a,b,c = '默认值') {
    
}

注意:

不论是哪种方式给参数设置默认值,有默认值的参数应该在后面。

有默认值的参数称之为可选参数,意味着调用函数的时候可以没有与之对应的实参。

④ arguments

  • arguments 只能在函数内使用。
  • arguments 是个伪数组,同数组类似具有 length 属性,也可以通过索引获取其中的成员,也可以用 for 循环遍历。
  • arguments 是由调用函数时给的实参组成的集合, 通过 arguments 可以获取调用函数时给的实参。

arguments 和 形参:

  • arguments 和 形参都可以获取到调用函数时给的实参。
  • 如果声明函数的时候,明确的知道需要几个参数,就使用形参; 如果是可变参数数量的函数,使用 arguments。

7.5 作用域

① 变量的作用域

作用域: 变量的可作用范围称之为作用域; 根据作用域可以把变量分支全局变量和局部变量。

全局变量: 定义在函数外面的变量就是全局变量,作用域是全局的,任何地方都可以使用该变量。

局部变量: 定义在函数内的变量就是局部变量,作用域是本函数,仅限本函数内使用。

注意:

  1. 函数名本质是个变量,所以函数本身也有作用域、
  2. 形参是局部变量,作用域是所在的函数。

② 作用域链

作用域链: A嵌套函数B,函数A的作用域是函数B作用域的上层作用域,层层嵌套形成作用域链。

作用域链的作用: 当使用一个变量的时候,会先从本作用域下查找,如果没有再从上层作用域查找,一直找到全局作用域,哪里找到哪里停止,一直找不到报错。

作用域只与函数声明的位置有关系,与函数调用的位置无关

7.6 变量提升

① 变量提升

在正式执行代码之前,进行与解析; 预解析的时候会对变量进行提升; 把变量提升到本作用域的最前面;

但是变量的提升值提升变量的声明,不会提升赋值,赋值操作需要等到正式执行相关代码。

② 函数提升

相对于普通的变量提升,如果是function关键字方式声明的函数,会在代码正式执行之前把函数的声明以及函数的值都提升到本作用域最前面。

如果是表达式方式或者 Function 构造函数方式声明的函数,提升方式与普通变量一样,值提升函数的声明,不提升值。

由于函数提升的时候比较彻底,所以正式执行到函数声明语句的时候会跳过。

函数的提升认为比普通变量的提升权重高,即使同名的遍历声明代码写在函数声明代码的后代,提升还是函数。

7.7 自调用函数

① 匿名函数

匿名函数就是没有名字的函数,也就是函数直接量。

匿名函数一般作为自调用函数或回调函数。

② 自调用函数

简称 IIFE,英文全称 Immediately Invoked Function Expression

自调用函数就是函数声明完立即调用的函数。

不论是匿名函数还是有名字的函数都可以自调用,一般匿名函数为主。

自调用函数用于产生作用域,避免全局变量,以免全局变量污染。

7.8 回调函数

① 什么是回调函数

  1. 我定义的
  2. 我没有直接调用
  3. 最后函数却执行了

满足以上三个条件的函数,称之为回调函数。

② 回调函数的使用场景

  • 事件的回调函数
  • 定时器的回调函数
  • ajax 的回调函数
  • 生命周期钩子函数
  • 一些系统内置的函数或方法,需要回调函数作为参数。

注意:

匿名非常适合作为回调函数。

7.9 递归函数

① 什么是递归函数

函数内调用自己,我们成为函数的递归调用, 这样的函数称为递归函数

② 递归函数成功的条件

  • 函数内必须有明确的递归结束条件。
  • 随着递归次数的增加,应该越来与接近递归的结束条件。

③ 递归函数的缺点

  • 递归函数容易造成内存泄漏。
  • 递归函数效率很低,能用循环做就使用循环。

④ 递归函数应用场景

  • 后端程序处理目录,删除目录、复制目录、剪切目录等。
  • 前端处理从后端得到 json 数据。

8 对象

8.1 对象的概念

广义的理解: 一切皆对象,数组、函数都是对象的一种。

狭义的理解: Object 数据类型,是对象类型中的一种,与Array、Function是等价的。

8.2 Object类型

8.2.1 什么是 Object

Object 是值的无序集合。

Object 由属性组成,组成有属性名也有属性值,属性值可以是任意类型的数据。

如果属性值的类型是 function,可以称这种属性是方法。

8.2.2 如何声明 Object 对象

① 第一种 直接量方式
// 第一种方式 直接量方式创建对象
var obj1 = {
    name: '朦朦', 
    age: 18, 
    address: '上海', 
    infos: [100,200,300,400], 
    getInfo: function(){}
};
// 给创建好的对象添加属性
obj1.grade = '100';
console.log(obj1);
console.log(typeof obj1);
console.log('');
console.log('');
② 使用 Object 函数
// 第二种方式  Object 函数
var obj2 = Object();
// 给创建好的对象添加属性
obj2.name = '小乐';
obj2.address = '上海';
obj2.getInfo = function(){

};
③ 使用 Object 构造函数
/ 第三种方式 Object 构造函数函数
var obj3 = new Object();
// 给创建好的对象添加属性
obj3.username = 'admin';
obj3.password = '1234565';

8.2.3 属性操作

(1) Object 对象属性的读写

① 语法
  1. 对象的属性名没有规则,属性名应该以字符串的形式(加引号)给出,但是如果属性名满足标识符命名规范,可以省略字符串的引号。
  2. 可以使用 . 语法或 [] 语法读写对象中的属性; 读取不存在的属性返回 undefined ;给不存在的属性赋值会添加新属性。
 var obj = {
     name: '朦朦',
     $age: 19,
     '10abc': 'hello world',
     '地址': '上海',
     getInfo: function(){
         console.log('get Info');
     },
     'get-name': function(){
         console.log('get-name');
     }
 };

 // 读写 object 对象的属性, . 语法  [] 语法
console.log(obj.name);
console.log(obj['name']);

// 必须使用 [] 语法读写属性的第一种情况: 属性名不符合标识符命名规范
// obj.10abc;
console.log(obj['10abc']);

// 必须使用 [] 语法读写属性的第二种情况: 用变量表示属性名
var prop = '$age';
// console.log(obj.prop);
console.log(obj[prop]);


// 给已经存在属性重复赋值
obj['地址'] = '北京';
② 什么情况下必须使用 [] 语法读写属性
  1. 情况一: 属性名不符合标识符命名规范
  2. 情况二: 用变量表示属性名。

(2) 遍历对象的属性

// for in  遍历 Object 对象
for (var i in obj) {
    console.log(i, obj[i]);
}

(3) 删除对象中的属性

使用 delete 运算符。

delete obj.属性名;
delete obj['属性名'];

(4) 判断对象中是否存在某个属性

使用 in 运算符,是二元运算符,组成的表达式返回布尔值。

属性名必须以字符串的形式给出。

'属性名' in 对象

8.3 构造函数

8.3.1 什么是构造函数?

  • 构造函数是用来构造对象的函数,相当于其他编程语言的
  • 构造函数仍然是个函数,是普通函数还是构造函数,取决于怎么使用。
  • 构造函数与数据类型是等价的,相同数据类型的数据(对象),构造函数也一样。

8.3.2 构造函数和对象的关系

  • 构造函数是对象的抽象(描述),对象是构造函数的实例。
  • 一个构造函数可以对应多个对象,而一个对象只能有一个构造函数。

8.3.3 判断对象的构造函数

① 运算符 instanceof

是一个二元运算符,左边的操作数是对象,右边的操作数是构造函数; 组成的表达式返回布尔值。

② constructor 属性

所有的对象都有具有 constructor 属性,该属性是系统自动添加的;可以得到对象的构造函数。

8.3.4 自定义构造函数

function User(username, age, address) {
    // 添加属性
    this.name = username;
    this.age = age;
    this.address = address;

    // 添加方法
    this.getInfo = function(){
        console.log('我叫' + this.name + ',我今年' + this.age + '岁,我住在' + this.address);
    }

}

this 表示构造函数将来实例化的对象,this 是动态的,在实例化的时候动态决定。

8.3.5 实例化

  1. 通过 new 构造函数() 可以实例化出一个对象
  2. 每实例化一次构造函数,就产生一个新的对象(内存中会开辟新的空间),一个构造函数可以实例化无数个对象。
  3. 实例化构造函数的时候,可以传递产生,如果没有参数,可以省略()

8.4 this

8.4.1 this 的含义

this 是内置的一个关键字,this 的值通常是一个 对象

this 的值是动态的,会在执行代码的时候动态决定。

8.4.2 this 的指向

this 的指向分两种情况:

第一种情况:在构造函数中使用(new 后面的函数就是构造函数), this 指向实例化产生的新对象

第二种情况:在方法或函数中(调用就说明是方法或函数),this 指向调用该方法的对象。

8.4.3 window 介绍

window 是JS中的全局对象, 全局的变量和函数本质上是window的一个方法,使用window的属性或方法的时候,可以省略 window.

8.5 原型

8.5.1 原型的概念

每一个对象都有一个原型,原型也是个对象。

对象可以继承(使用)原型上的属性和方法。

8.5.2 如何获取对象的原型

① 隐式原型方式

obj.__proto__

通过对象自身的 __proto__ 属性获取对象的原型。

② 显示原型方式

构造函数.prototype

通过对象构造函数的 prototype 属性来获取该对象的原型。

说明具有相同构造函数的对象,原型也是一样的。

8.5.3 对象、构造函数、原型之间的关系

① 对象和构造函数

对象是构造函数的实例,构造函数是对象的抽象(描述)。

② 对象和原型

每个对象都有原型,对象可以继承原型上的属性和方法。

③ 构造函数和原型

具有相同构造函数的对象,原型也是一样的。

通过 构造函数.prototype 可以获取构造函数实例的原型。

8.5.4 原型的应用

// 自定义构造函数
function User(name, age, address) {
    // 属性
    this.name = name;
    this.age = age;
    this.address = address;
}

// 把方法添加到实例的原型上
User.prototype.login = function(){
    console.log(this.name + ' 登录了');
}
User.prototype.logout = function() {
    console.log(this.name + ' 退出登录了');
}

自定义构造函数的时候,通常通过 this 对象设置属性,方法添加到原型上; 得到实例之后,属性会存储在对象本身上,方法存储在原型对象上。

一般,相同类型的对象属性的值往往是不同的,但是方法的语句是一致的。

这么做可以减少内存存储空间。

8.5.5 判断属性是否属于对象本身

对象.hasOwnPorperty('属性名')	返回布尔值	

判断属性是否在对象本身上,如果在对象本身山返回 true。 在原型上的属性或者原型上也没有返回 false。

8.5.6 创建对象的同时指定原型

// 创建对象的同时指定对象的原型
// a 对象的原型就是 b
var a = Object.create(b);


// 创建没有原型的对象
Object.create(null)

8.6 原型链

8.6.1 原型链的概念

对象有原型,原型也是个对象,原型也有原型,一直到一个顶层原型对象(一个没有原型的对象),构成了一条原型链。

8.6.2 属性查找过程

当使用对象中的一个属性的时候,先从对象本身查找,如果找不到,取对象的原型上查找,如果还找不到继续沿着原型链向上查找,哪里找到哪里停止,一直到找到厅层原型对象,如果都没有自动得到 undefined。

8.6.3 构造函数和原型链

  1. 对象的构造函数和原型绝对不是一回事。
  2. 没有经过特殊初始的内置类型的对象,原型都是 Object 的一个实例。 字符串对象的原型是Object的一个实例、数组对象的原型也是Object的一个实例。
  3. 构造函数本身也是一个函数类型的对象,也有自己的原型(Object的一个实例),也有自己的构造函数(Function); 构造函数.prototype 获取的是构造函数实例的原型,构造函数.__proto__ 获取的是自己的原型,二者完全不是一回事。
  4. Function 的构造函数是本身,自己构造了自己, Function.prototype === Function.__proto__ 是成立。
  5. 作为原型的对象,里面的 constructor 属性指向的不是自己的构造函数,指向的是以该原型对象为原型的对象的构造函数。

8.7 数据类型的其他分类方式

8.7.1 值类型和引用类型

值类型: 原始类型的数据也称为值类型。 值类型的传递方式是值传递。

引用类型: 对象类型的数据也称之为引用类型。引用类型的传递方式是引用传递(地址传递)。

内存原理:

内存中分为栈结构和堆结构; 创建一个变量的时候,变量的名字会存在栈解构中,变量的值如果是原始类型(值类型)就会与变量一起存在栈结构中; 变量的值如果是对象类型(引用类型),值存在堆中,栈结构中存的是值的地址。

注意:

一个值是引用类型的变量,如果对变量重新赋值,原来的引用关系会被修改!

8.7.2 不可变类型和可变类型

不可变类型: 就是原始类型(值类型),整个数据就是一个整体,不能修改其中的一部分。

可变类型: 就是对象类型(引用类型),可以修改数据中的一部分(属性的值)。

9 内置对象

9.1 Boolean

// ① 直接量方式
var b1 = true;

// ② Boolean 函数
var b2 = Boolean(false);

// ③ Boolean 构造函数
var b3 = new Boolean('hello');

9.2 Number

① 构造函数本身的属性和方法

Number.MAX_VALUE	获取 JS 中能表示的最大的数
Number.MIN_VALUE	获取 JS 中能表示的最小的数

② 实例的属性和方法

toFixed()	保留指定位数的小数,默认取整,参数指定小数位数
toString()	返回数字的字符串形式,可以指定参数,参数表示进制

9.3 String

构造函数本身的属性和方法

String.fromCharCode(编码)		指定编码(数字),返回编码对应的字符。

实例的属性和方法

length		属性,得到字符串的长度(字符串的个数),只读。

indexOf(val)	返回 val 在字符串中第一次出现的位置(索引),如果不包含 val,返回 -1

lastIndexOf(val)	返回 val 在字符串中最后一次出现的位置(索引),如果不包含 val,返回 -1

slice(start [,end])		截取字符串,第一个参数指定开始截取的位置,第二个参数指定结束位置;顾头不顾尾(end对应的字符不会被截取);如果不指定第二个参数,一直截取到最后。

substring()		同 slice

substr(start [,length])	截取字符串,第一个参数指定开始截取的位置,第二个参数指定要截取的长度;如果不指定第二个参数,一直截取到最后。

split([分隔符])	把字符串分隔为数组,参数指定分隔符。

toUpperCase()	返回转为大写的字符串

toLowerCase()	返回转为小写的字符串

charCodeAt(index)	返回索引对应的字符的 unicode 编码

提示:

indexOf()lastIndexOf() 通常用来判断字符串中是否包含某个值

9.4 Math

Math.PI		属性,圆周率
Math.abs()		返回指定数字的绝对值
Math.sqrt()		返回指定数字的平方根
Math.pow(x, y)		返回 x 的 y 次方
Math.max()		返回所有参数中最大的
Math.min()		返回所有参数中最小的
Math.round()	四舍五入取整
Math.floor()	舍一取整
Math.ceil()		进一取整
Math.random()	取随机数,随机范围:0~1 之间的小数; 0 有一定的概率被取到,1 绝无可能。

Math.floor(Math.random() * n) 可以取到 0 到 n~1 之间的随机整数,n ~ 1 可以被取到

Math.floor(Math.random() * n) + m 可以取到 m 到 (n-1 + m) 之间的随机整数

9.5 Date

9.5.1 创建时间日期的对象

var date = new Date();  // 不带参数,data对象中的时间是创建对象那一刻的时间
var date1 = new Date('December 17, 1995 03:24:00');
var date2 = new Date('2025-04-01 12:12:12');
var date3 = new Date(2030, 4, 4, 10, 23, 18);

9.5.2 实例的属性和方法

getFullYear()		年
getMonth()			月,值范围 0 ~ 11
getDate()			日
getDay()			星期几
getHours()			小时,24小时进制
getMinutes()		分钟
getSeconds()		秒
getMilliseconds()	毫秒

getTime()			1970-01-01:00:00:00 至今(对象中的时间)的毫秒数

getUTC....			获取标准时区的年月日时分秒

set...				给对象设置日期时间
setUTC...			给对象设置标准时区日期时间
setTime()			

9.5.3 Date 构造函数本身的方法

Date.now()		1970-01-01:00:00:00 至今的毫秒数
Date.UTC()		1970-01-01:00:00:00 距离指定时间日期的毫秒数; 通过6个参数指定时间日期

9.6 Array

实例的属性

length		数组的长度,可读可写

实例的方法(修改器方法)

所谓修改器方法,就是对象调用完该方法之后,对象本身也被修改了,修改器方法并不常见。

push()		在数组的后面添加一个或多个元素,返回添加之后的长度
unshift()	在数组的前面面添加一个或多个元素,返回添加之后的长度
pop()		删除数组的最后一个元素,返回被删除的元素
shift()		删除数组的第一个元素,返回被删除的元素
splice()	替换数组中的元素,返回被删除元素组成的数组(没有删除就是空数组)
sort()		数组排序,返回排好序的数组
reverse()	翻转数组,返回翻转好的数组

实例的方法(访问方法)

对象调用完方法后,结果以返回值的形式返回,不会修改对象本身,大部分方法都是访问方法

concat()	把多个数组合并为一个,返回合并好的数组
slice()		截取数组,返回新数组; 参数与字符串的slice方法规则相同
join()		把数组合并为字符串,返回合并好的字符串。 参数可以指定分隔符,不指定默认逗号

ES5 增加的方法 (访问方法)

indexOf()			返回指定的元素第一次出现的位置
lastIndexOf()		返回指定的元素最后一次出现的位置
forEach(callback)	遍历数组
filter(callback)	过滤数组,返回过滤好的新数组;回调函数返回true或false决定当前元素是否加入新数组。map(callback)		 返回与原数组长度一致的新数组,新数组中元素的值取决于每次回调函数的返回值。
every(callback)		返回布尔值。如果所有回调函数都返回true,every() 返回true,否则false
some(callback)		返回布尔值,只要有一个返回函数返回true,some()就返回true。
reduce(callback [,inital])		返回最后一次回调函数的返回值
reduceRight(callback [,inital])   返回最后一次回调函数的返回值,从后向前遍历

总结:

forEach、filter、map、some、every 的回调函数,都是接收三个参数,分别是当前的元素、元素的索引、调用方法的数组。

reduce、reduceRight 的回调函数,接收三个参数,分别是初始值(上一个回调函数返回值)、当前元素、元素的索引。

9.7 内置对象

9.7.1 Function

实例的属性和方法

length		返回函数形参的数量
call()		调用函数并指定函数中的this, 第一个参数是函数中 this 的值,后面的参数任意个,对应函数的参数
apply()		调用函数并指定函数中的this, 第一个参数是函数中 this 的值,第二个参数是数组,数组元素对应函数的参数
bind()		相对于 call() 和 apply,bind 返回修改了 this 的新的函数。

9.7.2 全局对象

全局对象的方法

eval()		把传入的字符串作为代码执行
encodeURI()	对 URL 进行编码
decodeURI()	对编了码的 URL 进行解码
parseInt(str, num)	从字符串中提取数字, num 指定字符串中数字的进制,默认 10.
parseFloat(str, num)	从字符串中提取数字, num 指定字符串中数字的进制,默认 10.