<div id="content_views" class="markdown_views prism-atom-one-dark">
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path>
</svg>
<p></p>
目录
- 第一章 JavaScript简介
- 第二章 JavaScript基础语法
- 第三章 JavaScript常用对象
- 第四章 JavaScript DOM
- 第五章 JavaScript BOM
- 第六章 JavaScript高级语法
- 第七章 JavaScript新特性
-
- 7.1、ECMAScript6新特性
- 7.2、ECMAScript7新特性
- 7.3、ECMAScript8新特性
- 7.4、ECMAScript9新特性
- 7.5、ECMAScript10新特性
- 7.6、ECMAScript11新特性
- 第八章 JavaScript项目百练
配套资料,免费下载
链接:https://pan.baidu.com/s/152NnFqzAUx9br2qb49mstA
提取码:ujyp
复制这段内容后打开百度网盘手机App,操作更方便哦
第一章 JavaScript简介
1.1、JavaScript的起源
JavaScript诞生于1995年,它的出现主要是用于处理网页中的前端验证。所谓的前端验证,就是指检查用户输入的内容是否符合一定的规则。比如:用户名的长度,密码的长度,邮箱的格式等。但是,有的同学可能会有疑问,这些验证,后端不也可以进行验证吗?确实,后端程序的确可以进行这些验证,但你要清楚,在1995年那个年代,网速是非常慢的,向后端发送一个请求,浏览器很久才能得到响应,那这无疑是一种非常不好的用户体验。
为了解决前端验证的问题,当时的浏览器巨头NetScape(网景)公司就开发出一种脚本语言,起初命名为LiveScript,后来由于SUN公司的介入更名为了JavaScript。但是你要清楚,Java和JavaScript是没有什么关系的,只不过当时Java非常流行,为了蹭热度,才将LiveScript更名为JavaScript,它们的关系就像雷锋和雷峰塔的关系一样,没啥关系。
但是,浏览器开发商不止网景一家,还有一个大家都知道的公司,微软公司,它们的主打产品是IE(Internet Explorer)浏览器,当网景公司的Netscape Navigator浏览器推出JavaScript语言时,微软就急了啊,好家伙,人网景都推出了专门用于前端验证的语言,不仅大大减少了后端程序的压力,还提高了用户的体验。我微软这么大的公司不也得整一个,在1996年,微软公司在其最新的IE3浏览器中引入了自己对JavaScript的实现JScript。
于是在市面上存在两个版本的JavaScript,一个网景公司的JavaScript和微软的JScript,虽然当时浏览器的巨头是网景,但是网景的浏览器是收费的,虽然微软的IE浏览器在全球的市场份额远远不及网景,但是微软的拳头产品是Windows操作系统,每一个操作系统都自带一个IE浏览器并且免费,那么,未来的发展大家可能也想到了,网景让微软给干倒闭了,1998年11月,网景被美国在线(AOL)收购。
老大哥就是老大哥,为了抢先获得规则制定权,网景最先将JavaScript作为草案提交给欧洲计算机制造商协会,也就是ECMA组织,希望能将JavaScript做成行业标准,最终在网景、SUN以及微软等公司的参与下,由一众程序员和相关组织人员组成的第39技术委员会也就是TC39发布了ECMA-262标准,这个标准定义了名为ECMAScript的全新脚本语言,为啥又来了个ECMAScript?
因为Java是SUN的商标,SUN授权了NetScape可以叫JavaScript,但是ECMA没有SUN的授权就不能叫JavaScript,哪怕NetScape成员特别希望ECMA把它叫做JavaScript,但是ECMA也有成员并不希望这个标准就叫JavaScript,总之经过几轮磋商和博弈,ECMAScript这个名字就定下来。
我们可以简单看一下历史事件发展表:
1.2、JavaScript的组成
ECMAScript是一个标准,而这个标准需要由各个厂商去实现,不同的浏览器厂商对该标准会有不同的实现。
我们已经知道ECMAScript是JavaScript标准,所以一般情况下这两个词我们认为是一个意思。但是实际上JavaScript的含义却要更大一些。一个完整的JavaScript实现应该由以下三个部分构成:
由此我们也知道了我们所要学习的内容就是这三部分,它们具体的含义后边章节会具体介绍。
1.3、JavaScript的特点
解释型语言
JavaScript是一门解释型语言,所谓解释型值语言是指不需要被编译为机器码在执行,而是直接执行。由于少了编译这一步骤,所以解释型语言开发起来尤为轻松,但是解释型语言运行较慢也是它的劣势。不过解释型语言中使用了JIT技术,使得运行速度得以改善。
动态语言
JavaScript是一门动态语言,所谓的动态语言可以暂时理解为在语言中的一切内容都是不确定的。比如一个变量,这一时刻是个整型,下一时刻可能会变成字符串了。当然这个问题我们以后再谈。不过在补充一句动态语言相比静态语言性能上要差一些,不过由于JavaScript中应用的JIT技术,所以JavaScript可能是运行速度最快的动态语言了。
类似于 C 和 Java 的语法结构
JavaScript的语法结构与C和Java很像,向for、if、while等语句和Java的基本上是一模一样的。所以有过C和Java基础的同学学习起来会轻松很多。不过JavaScript和与Java的关系也仅仅是看起来像而已。
基于原型的面向对象
JavaScript是一门面向对象的语言。啥是对象?下次聊。
Java也是一门面向对象的语言,但是与Java不同JavaScript是基于原型的面向对象。啥是原型?下次聊。
严格区分大小写
JavaScript是严格区分大小写的,也就是abc和Abc会被解析器认为是两个不同的东西。
1.4、JavaScript的使用
1.4.1、标签引用
在HTML中在script标签中就可以编写JavaScript代码,以下是一个简单演示。
<script>
alert("Hello,World!");
</script>
- 1
- 2
- 3
1.4.2、文件引用
在一个单独的js文件中也可以编写JavaScript代码,然后在HTML文件中使用script标签进行引用,以下是一个简单演示。
main.html
<script src="main.js"></script>
- 1
main.js
alert("Hello,World!");
- 1
1.5、JavaScript的输出
1.5.1、页面输出
如何使用JavaScript向页面输出一句话,请参考以下代码。
<script>
document.write("Hello,World!");
</script>
- 1
- 2
- 3
1.5.2、控制台输出
如何使用JavaScript向控制台输出一句话,请参考以下代码。
注意:页面按F12弹出控制台
<script>
console.log("输出一条日志");//最常用
console.info("输出一条信息");
console.warn("输出一条警告");
console.error("输出一条错误");
</script>
- 1
- 2
- 3
- 4
- 5
- 6
1.5.3、弹出窗口输出
如何使用JavaScript向弹出窗口输出一句话,请参考以下代码。
<script>
alert("Hello,World!");
</script>
- 1
- 2
- 3
1.6、JavaScript的注释
注释中的内容不会被解析器解析执行,但是会在源码中显示,我们一般会使用注释对程序中的内容进行解释。
JS中的注释和Java的的一致,分为两种:
- 单行注释:
// 注释内容
- 多行注释:
/* 注释内容 */
1.6.1、单行注释
<script>
// 这是注释内容
console.log("Hello,World!");
</script>
- 1
- 2
- 3
- 4
1.6.2、多行注释
<script>
/**
* 这是注释内容
*/
console.log("Hello,World!");
</script>
- 1
- 2
- 3
- 4
- 5
- 6
第二章 JavaScript基础语法
2.1、标识符
所谓标识符,就是指给变量、函数、属性或函数的参数起名字。
标识符可以是按照下列格式规则组合起来的一或多个字符:
- 第一个字符必须是一个字母、下划线( _ )或一个美元符号( $ )。
- 其它字符可以是字母、下划线、美元符号或数字。
- 按照惯例,ECMAScript 标识符采用驼峰命名法。
- 标识符不能是关键字和保留字符。
关键字:
保留字符:
其它不建议使用的标识符:
单个单词的标识符举例:
name、age、gender、hobby
- 1
多个单词的标识符举例:
studentName、studentAge、studentGender、studentHobby
- 1
2.2、字面量和变量
2.2.1、字面量
字面量实际上就是一些固定的值,比如:1、2 、3、true、false、null、NaN、“hello”,字面量都是不可以改变的,由于字面量不是很方便使用,所以在JavaScript中很少直接使用字面量,使用的而是变量。
2.2.2、变量
变量的作用是给某一个值或对象标注名称。比如我们的程序中有一个值123,这个值我们是需要反复使用的,这个时候 我们最好将123这个值赋值给一个变量,然后通过变量去使用123这个值。
变量的声明: 使用var关键字声明一个变量。
var a;
- 1
变量的赋值: 使用=为变量赋值。
a = 123;
- 1
声明和赋值同时进行:
var a = 123;
- 1
2.3、数据类型
2.3.1、类型分类
数据类型决定了一个数据的特征,比如:123和”123”,直观上看这两个数据都是123,但实际上前者是一个数字,而后者是一个字符串。
对于不同的数据类型我们在进行操作时会有很大的不同。
JavaScript中一共有5种基本数据类型:
- 字符串型(String)
- 数值型(Number)
- 布尔型(Boolean)
- undefined型(Undefined)
- null型(Null)
这5种之外的类型都称为Object,所以总的来看JavaScript中共有六种数据类型。
2.3.2、typeof运算符
使用typeof操作符可以用来检查一个变量的数据类型。
使用方式:
typeof 数据
- 1
示例代码:
console.log(typeof 123);
console.log(typeof "Hello,World!");
console.log(typeof true);
console.log(typeof undefined);
console.log(typeof null);
- 1
- 2
- 3
- 4
- 5
运行结果:
2.3.3、String
String用于表示一个字符序列,即字符串。字符串需要使用 单引号 或 双引号 括起来。
转义字符:
注意:使用typeof运算符检查字符串时,会返回"string"。
2.3.4、Number
Number 类型用来表示整数和浮点数,最常用的功能就是用来表示10进制的整数和浮点数。
Number表示的数字大小是有限的,如果超过了这个范围,则会返回 ±Infinity。
- 最大值:+1.7976931348623157e+308
- 最小值:-1.7976931348623157e+308
- 0以上的最小值:5e-324
特殊的数字:
- Infinity:正无穷
- -Infinity:负无穷
- NaN:非法数字(Not A Number)
其它的进制:
- 二进制:0b 开头表示二进制,但是,并不是所有的浏览器都支持
- 八进制:0 开头表示八进制
- 十六进制:0x 开头表示十六进制
注意:使用typeof检查一个Number类型的数据时(包括NaN 和 Infinity),会返回"number"。
2.3.5、Boolean
布尔型也被称为逻辑值类型或者真假值类型。
布尔型只能够取真(true)和假(false)两种数值。除此以外, 其它的值都不被支持。
2.3.6、Undefined
Undefined 类型只有一个值,即特殊的 undefined。
在使用 var 声明变量但未对其加以初始化时,这个变量的值就是 undefined。
注意:使用typeof对没有初始化和没有声明的变量,会返回“undefined”。
2.3.7、Null
Null 类型是第二个只有一个值的数据类型,这个特殊的值是 null。
undefined值实际上是由null值衍生出来的,所以如果比较undefined和null是否相等,会返回true。
注意:从语义上看null表示的是一个空的对象,所以使用typeof检查null会返回一个Object。
2.4、强制类型转换
强制类型转换指将一个数据类型强制转换为其它的数据类型。一般是指,将其它的数据类型转换为String、Number、Boolean。
2.4.1、转换为String类型
将其它数值转换为字符串有三种方式:toString()、String()、 拼串。
-
方式一:调用被转换数据类型的toString()方法,该方法不会影响到原变量,它会将转换的结果返回,但是注意:null和undefined这两个值没有toString()方法,如果调用它们的方法,会报错。
var a = 123; a = a.toString(); console.log(a); console.log(typeof a);
- 1
- 2
- 3
- 4
-
方式二:调用String()函数,并将被转换的数据作为参数传递给函数,使用String()函数做强制类型转换时,对于Number和Boolean实际上就是调用的toString()方法,但是对于null和undefined,就不会调用toString()方法,它会将 null 直接转换为 “null”,将 undefined 直接转换为 “undefined”。
var a = 123; a = String(a); console.log(a); console.log(typeof a);
var b = undefined;
b = String(b);
console.log(b);
console.log(typeof b);
var c = null;
c = String(c);
console.log(c);
console.log(typeof c);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
-
方式三:为任意的数据类型
+""
var a = 123; a = a + ""; console.log(a); console.log(typeof a);
- 1
- 2
- 3
- 4
2.4.2、转换为Number类型
有三个函数可以把非数值转换为数值:Number()、parseInt() 和parseFloat()。Number()可以用来转换任意类型的数据,而后两者只能用于转换字符串。parseInt()只会将字符串转换为整数,而parseFloat()可以将字符串转换为浮点数。
-
方式一:使用Number()函数
- 字符串 --> 数字
- 如果是纯数字的字符串,则直接将其转换为数字
- 如果字符串中有非数字的内容,则转换为NaN
- 如果字符串是一个空串或者是一个全是空格的字符串,则转换为0
- 布尔 --> 数字
- true 转成 1
- false 转成 0
- null --> 数字
- null 转成 0
- undefined --> 数字
- undefined 转成 NaN
- 字符串 --> 数字
-
方式二:这种方式专门用来对付字符串,parseInt() 把一个字符串转换为一个整数
var a = "123"; a = parseInt(a); console.log(a); console.log(typeof a);
- 1
- 2
- 3
- 4
-
方式三:这种方式专门用来对付字符串,parseFloat() 把一个字符串转换为一个浮点数
var a = "123.456"; a = parseFloat(a); console.log(a); console.log(typeof a);
- 1
- 2
- 3
- 4
注意:如果对非String使用parseInt()或parseFloat(),它会先将其转换为String然后在操作
2.4.3、转换为Boolean类型
将其它的数据类型转换为Boolean,只能使用Boolean()函数。
- 使用Boolean()函数
- 数字 —> 布尔
- 除了0和NaN,其余的都是true
- 字符串 —> 布尔
- 除了空串,其余的都是true
- null和undefined都会转换为false
- 对象也会转换为true
- 数字 —> 布尔
2.5、运算符
运算符也叫操作符,通过运算符可以对一个或多个值进行运算并获取运算结果。
比如:typeof就是运算符,可以来获得一个值的类型,它会将该值的类型以字符串的形式返回(number string boolean undefined object)
2.5.1、算术运算符
算术运算符用于表达式计算。
y=5,下面的表格解释了这些算术运算符:
运算符 描述 例子 x 运算结果 y 运算结果 在线实例 + 加法 x=y+2 7 5 实例 » - 减法 x=y-2 3 5 实例 » * 乘法 x=y*2 10 5 实例 » / 除法 x=y/2 2.5 5 实例 » % 取模(求余数) x=y%2 1 5 实例 » ++ 自增 x=++y
x=y++6
56
6实例 »
实例 »– 自减 x=–y
x=y–4
54
4实例 »
实例 »2.5.2、关系运算符
关系运算符在逻辑语句中使用,以测定变量或值是否相等。
x=5,下面的表格解释了比较运算符:
2.5.3、赋值运算符
赋值运算符用于给 JavaScript 变量赋值。
x=10 和 y=5,下面的表格解释了赋值运算符:
运算符 例子 等同于 运算结果 在线实例 = x=y x=5 实例 » += x+=y x=x+y x=15 实例 » -= x-=y x=x-y x=5 实例 » *= x*=y x=x*y x=50 实例 » /= x/=y x=x/y x=2 实例 » %= x%=y x=x%y x=0 实例 » 2.5.4、逻辑运算符
逻辑运算符用于测定变量或值之间的逻辑。
给定 x=6 以及 y=3,下表解释了逻辑运算符:
运算符 描述 例子 && and (x < 10 && y > 1) 为 true || or (x==5 || y==5) 为 false ! not !(x==y) 为 true 关于逻辑运算符我们可以具体探讨一下:
- && 与:&&可以对符号两侧的值进行与运算并返回结果,运算规则如下:
- 两个值中只要有一个值为false,就返回false,只有两个值都为true时,才会返回true
- JS中的“与”属于短路的与,如果第一个值为false,则不会检查第二个值
- 非布尔值时:如果两个都为true,则返回第二个值,如果两个值中有false,则返回靠前的false的值
- || 或:||可以对符号两侧的值进行或运算并返回结果,运算规则如下:
- 两个值中只要有一个true,就返回true,只有两个值都为false,才会返回false
- JS中的“或”属于短路的或,如果第一个值为true,则不会检查第二个值
- 非布尔值时:如果两个都为false ,则返回第二个值,如果两个值中有true,则返回靠前的true的值
- ! 非:!可以用来对一个值进行非运算,所谓非运算就是对一个布尔值进行取反操作,true变false,false变true,运算规则如下:
- 如果对一个值进行两次取反,它不会变化
- 非布尔值时:先会将其转换为布尔值,然后再取反,所以我们可以利用该特点,来将一个其它的数据类型转换为布尔值,可以为一个任意数据类型取两次反,来将其转换为布尔值,原理和Boolean()函数一样
2.5.5、比较运算符
比较运算符用来比较两个值是否相等,如果相等会返回true,否则返回false。
- 使用 == 来做相等运算
- 当使用==来比较两个值时,如果值的类型不同,则会自动进行类型转换,将其转换为相同的类型,然后在比较
- 使用 != 来做不相等运算
- 不相等用来判断两个值是否不相等,如果不相等返回true,否则返回false,不相等也会对变量进行自动的类型转换,如果转换后相等它也会返回false
- 使用 === 来做全等运算
- 用来判断两个值是否全等,它和相等类似,不同的是它不会做自动的类型转换,如果两个值的类型不同,直接返回false
- 使用 !== 来做不全等运算
- 用来判断两个值是否不全等,它和不等类似,不同的是它不会做自动的类型转换,如果两个值的类型不同,直接返回true
2.5.6、条件运算符
JavaScript 还包含了基于某些条件对变量进行赋值的条件运算符。
语法:
variablename=(condition)?value1:value2;
举例:
result=(age<18)?"年龄太小":"年龄合适";
执行流程:如果condition为true,则执行语句1,并返回执行结果,如果为false,则执行语句2,并返回执行结果。
2.5.7、逗号运算符
使用逗号可以在一条语句中执行多次操作。
比如:var num1=1, num2=2, num3=3;
使用逗号运算符分隔的语句会从左到右顺 序依次执行。
2.6、运算符优先级
运算符优先级由上到下依次减小,对于同级运算符,采用从左向右依次执行的方法。
2.7、代码块
2.7.1、语句
前边我所说表达式和运算符等内容可以理解成是我们一 门语言中的单词,短语。而语句(statement)就是我们这个语言中一句一句完 整的话了。语句是一个程序的基本单位,JavaScript的程序就是由一条一条语句构成的,每一条语句使用;结尾。
JavaScript中的语句默认是由上至下顺序执行的,但是我们也可以通过一些流程控制语句来控制语句的执行顺序。
2.7.2、代码块
代码块是在大括号 {} 中所写的语句,以此将多条语句的集合视为一条语句来使用。
例如:
{ var a = 123; a++; alert(a); }
- 1
- 2
- 3
- 4
- 5
我们一般使用代码块将需要一起执行的语句进行分组,需要注意的是,代码块结尾不需要加 分号。
2.8、条件语句
条件语句是通过判断指定表达式的值来决定执行还是跳过某些语句,最基本的条件语句:
- if…else
- switch…case
2.8.1、if…else
if…else语句是一种最基本的控制语句,它让JavaScript可以有条件的执行语句。
-
第一种形式:
if(expression) statement
- 1
- 2
var age = 16; if (age < 18) { console.log("未成年"); }
- 1
- 2
- 3
- 4
-
第二种形式:
if(expression) statement else statement
- 1
- 2
- 3
- 4
var age = 16; if (age < 18) { console.log("未成年"); } else { console.log("已成年"); }
- 1
- 2
- 3
- 4
- 5
- 6
-
第三种形式:
if(expression1) statement else if(expression2) statement else statement
- 1
- 2
- 3
- 4
- 5
- 6
var age = 18; if (age < 18) { console.log("小于18岁了"); } else if (age == 18) { console.log("已经18岁了"); } else { console.log("大于18岁了") }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
2.8.2、switch…case
switch…case是另一种流程控制语句。
switch语句更适用于多条分支使用同一条语句的情况。
语法格式:
switch (语句) { case 表达式1: 语句... case 表达式2: 语句... default: 语句... }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
注意:需要注意的是一旦符合case的条件程序会一直运行到结束,所以我们一般会在case中添加break作为语句的结束。
案例演示1:根据today的数值,输出今天是星期几。
var today = 1; switch (today) { case 1: console.log("星期一"); break; case 2: console.log("星期二"); break; case 3: console.log("星期三"); break; case 4: console.log("星期四"); break; case 5: console.log("星期五"); break; case 6: console.log("星期六"); break; case 7: console.log("星期日"); break; default: console.log("输入错误"); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
案例演示2:根据month的数值,输出对应月份的天数,2月默认28天。
var month = 10; switch (month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: console.log("31天"); break; case 4: case 6: case 9: case 11: console.log("30天"); break; case 2: console.log("28天"); break; default: console.log("输入错误"); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
2.9、循环语句
循环语句和条件语句一样,也是基本的控制语句,只要满足一定的条件将会一直执行,最基本的循环语句:
- while
- do…while
- for
2.9.1、while
while语句是一个最基本的循环语句,while语句也被称为while循环。
语法格式:
while(条件表达式){ 语句... }
- 1
- 2
- 3
案例演示:输出1-10。
var i = 1; while (i <= 10) { console.log(i); i++; }
- 1
- 2
- 3
- 4
- 5
2.9.2、do…while
do…while和while非常类似,只不过它会在循环的尾部而不是顶部检查表达式的值,因此,do…while循环会至少执行一次。相比于while,do…while的使用情况并不 是很多。
语法格式:
do{ 语句... }while(条件表达式);
- 1
- 2
- 3
案例演示:输出1-10。
var i = 1; do { console.log(i); i++; } while (i <= 10);
- 1
- 2
- 3
- 4
- 5
2.9.3、for
for语句也是循环控制语句,我们也称它为for循环。大部分循环都会有一个计数器用以控制循环执行的次数, 计数器的三个关键操作是初始化、检测和更新。for语句 就将这三步操作明确为了语法的一部分。
语法格式:
for(初始化表达式 ; 条件表达式 ; 更新表达式){ 语句... }
- 1
- 2
- 3
案例演示:输出1-10。
for (var i = 1; i <= 10; i++) { console.log(i); }
- 1
- 2
- 3
2.9.4、跳转控制
- break:结束最近的一次循环,可以在循环和switch语句中使用。
- continue:结束本次循环,执行下一次循环,只能在循环中使用。
那如果我们想要跳出多层循环或者跳到指定位置该怎么办呢?可以为循环语句创建一个label,来标识当前的循环,如下例子:
outer: for (var i = 0; i < 10; i++) { for (var j = 0; j < 10; j++) { if (j == 5) { break outer; } console.log(j); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
2.10、对象基础
2.10.1、概述
Object类型,我们也称为一个对象,是JavaScript中的引用数据类型。它是一种复合值,它将很多值聚合到一起,可以通过名字访问这些值。对象也可以看做是属性的无序集合,每个属性都是一个名/值对。对象除了可以创建自有属性,还可以通过从一个名为原型的对象那里继承属性。除了字符串、数字、true、false、null和undefined之外,JavaScript中的值都是对象。
2.10.2、创建对象
创建对象有两种方式:
-
第一种方式:
var person = new Object(); person.name = "孙悟空"; person.age = 18; console.log(person);
- 1
- 2
- 3
- 4
-
第二种方式:
var person = { name: "孙悟空", age: 18 }; console.log(person);
- 1
- 2
- 3
- 4
- 5
2.10.3、访问属性
访问属性的两种方式:
-
第一种方式:使用 . 来访问
对象.属性名
- 1
-
第二种方式:使用 [] 来访问
对象[‘属性名’]
- 1
2.10.4、删除属性
删除对象的属性可以使用delete关键字,格式如下:
delete 对象.属性名
- 1
案例演示:
var person = new Object(); person.name = "孙悟空"; person.age = 18; console.log(person);
delete person.name
console.log(person);- 1
- 2
- 3
- 4
- 5
- 6
- 7
2.10.5、遍历对象
枚举遍历对象中的属性,可以使用for … in语句循环,对象中有几个属性,循环体就会执行几次。
语法格式:
for (var 变量 in 对象) {
}
- 1
- 2
- 3
案例演示:
var person = { name: "zhangsan", age: 18 }
for (var personKey in person) {
var personVal = person[personKey];
console.log(personKey + “:” + personVal);
}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2.10.6、数据类型梳理
2.10.6.1、基本数据类型
JavaScript中的变量可能包含两种不同数据类型的值:基本数据类型和引用数据类型。
JavaScript中一共有5种基本数据类型:String、Number、 Boolean、Undefined、Null。
基本数据类型的值是无法修改的,是不可变的。
基本数据类型的比较是值的比较,也就是只要两个变量的值相等,我们就认为这两个变量相等。
2.10.6.2、引用数据类型
引用类型的值是保存在内存中的对象。
当一个变量是一个对象时,实际上变量中保存的并不是对象本身,而是对象的引用。
当从一个变量向另一个变量复制引用类型的值时,会将对象的引用复制到变量中,并不是创建一个新的对象。
这时,两个变量指向的是同一个对象。因此,改变其中一个变量会影响另一个。
2.10.7、栈和堆梳理
JavaScript在运行时数据是保存到栈内存和堆内存当中的。
简单来说栈内存用来保存变量和基本类型,堆内存是用来保存对象。
我们在声明一个变量时,实际上就是在栈内存中创建了一个空间用来保存变量。
如果是基本类型则在栈内存中直接保存,如果是引用类型则会在堆内存中保存,变量中保存的实际上对象在堆内存中的地址。
当我们写了下边这几句代码的时候,栈内存和堆内存的结构如下:
var a = 123; var b = true; var c = "hello"; var d = {name: 'sunwukong', age: 18};
- 1
- 2
- 3
- 4
栈的特点:先进后出,后进先出
2.11、函数
2.11.1、概述
函数是由一连串的子程序(语句的集合)所组成的,可以被外部程序调用,向函数传递参数之后,函数可以返回一定的值。
通常情况下,JavaScript代码是自上而下执行的,不过函数体内部的代码则不是这样。如果只是对函数进行了声明,其中的代码并不会执行,只有在调用函数时才会执行函数体内部的代码。
这里要注意的是JavaScript中的函数也是一个对象,使用typeof检查一个函数对象时,会返回function。
2.11.2、函数创建
-
使用 函数对象 来创建一个函数(几乎不用)
语法格式:
var 函数名 = new Function("执行语句");
- 1
示例代码:
var fun = new Function("console.log('这是我的第一个函数');");
- 1
-
使用 函数声明 来创建一个函数(比较常用)
语法格式:
function 函数名([形参1,形参2,...,形参N]) { 语句... }
- 1
- 2
- 3
示例代码:
function fun(){ console.log("这是我的第二个函数"); }
- 1
- 2
- 3
-
使用 函数表达式 来创建一个函数(比较常用)
语法格式:
var 函数名 = function([形参1,形参2,...,形参N]) { 语句.... }
- 1
- 2
- 3
示例代码:
var fun = function() { console.log("这是我的第三个函数"); }
- 1
- 2
- 3
2.11.3、函数调用
-
对于无参函数调用:
// 函数声明 var fun = function () { console.log("哈哈,我执行啦!"); }
// 函数调用
fun();- 1
- 2
- 3
- 4
- 5
- 6
- 7
-
-
对于有参函数调用:
// 函数声明 var sum = function (num1, num2) { var result = num1 + num2; console.log("num1 + num2 = " + result); }
// 函数调用
sum(10, 20);- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
2.11.4、函数参数
- JS中的所有的参数传递都是按值传递的,也就是说把函数外部的值赋值给函数内部的参数,就和把值从一个变量赋值给另一个变量是一样的,在调用函数时,可以在()中指定实参(实际参数),实参将会赋值给函数中对应的形参
- 调用函数时,解析器不会检查实参的类型,所以要注意,是否有可能会接收到非法的参数,如果有可能,则需要对参数进行类型的检查,函数的实参可以是任意的数据类型
- 调用函数时,解析器也不会检查实参的数量,多余实参不会被赋值,如果实参的数量少于形参的数量,则没有对应实参的形参将是undefined
2.11.5、函数返回值
可以使用 return 来设置函数的返回值,return后的值将会作为函数的执行结果返回,可以定义一个变量,来接收该结果。
注意:在函数中return后的语句都不会执行,如果return语句后不跟任何值就相当于返回一个undefined,如果函数中不写return,则也会返回undefined,return后可以跟任意类型的值
语法格式:return 值
案例演示:
function sum(num1, num2) { return num1 + num2; }
var result = sum(10, 20);
console.log(result);- 1
- 2
- 3
- 4
- 5
- 6
2.11.6、嵌套函数
嵌套函数:在函数中声明的函数就是嵌套函数,嵌套函数只能在当前函数中可以访问,在当前函数外无法访问。
案例演示:
function fu() { function zi() { console.log("我是儿子") }
<span class="token function">zi</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
}
fu();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2.11.7、匿名函数
匿名函数:没有名字的函数就是匿名函数,它可以让一个变量来接收,也就是用 “函数表达式” 方式创建和接收。
案例演示:
var fun = function () { alert("我是一个匿名函数"); }
fun();
- 1
- 2
- 3
- 4
- 5
2.11.8、立即执行函数
立即执行函数:函数定义完,立即被调用,这种函数叫做立即执行函数,立即执行函数往往只会执行一次。
案例演示:
(function () { alert("我是一个匿名函数"); })();
- 1
- 2
- 3
2.11.9、对象中的函数
对象的属性值可以是任何的数据类型,也可以是个函数。
如果一个函数作为一个对象的属性保存,那么我们称这个函数是这个对象的方法,调用这个函数就说调用对象的方法(method)。
注意:方法和函数只是名称上的区别,没有其它别的区别
案例演示:
var person = { name: "zhangsan", age: 18, sayHello: function () { console.log(name + " hello") } }
person.sayHello();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2.11.10、this对象
解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是this,this指向的是一个对象,这个对象我们称为函数执行的上下文对象,根据函数的调用方式的不同,this会指向不同的对象
- 以函数的形式调用时,this永远都是window
- 以方法的形式调用时,this就是调用方法的那个对象
案例演示:
//创建一个全局变量name var name = "全局变量name";
//创建一个函数
function fun() {
console.log(this.name);
}//创建一个对象
var obj = {
name: “孙悟空”,
sayName: fun
};//我们希望调用obj.sayName()时可以输出obj的名字而不是全局变量name的名字
obj.sayName();- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
2.12、对象进阶
2.12.1、用工厂方法创建对象
我们之前已经学习了如何创建一个对象,那我们要是想要创建多个对象又该怎么办?聪明的同学可能会说,直接在写几个对象不就好了吗?比如下边的代码:
var person1 = { name: "孙悟空", age: 18, sayName: function () { console.log(this.name); } };
var person2 = {
name: “猪八戒”,
age: 19,
sayName: function () {
console.log(this.name);
}
};var person3 = {
name: “沙和尚”,
age: 20,
sayName: function () {
console.log(this.name);
}
};console.log(person1);
console.log(person2);
console.log(person3);- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
的确,上述代码确实可以创建多个对象,但是,这样的解决方案真的好吗?对于少量对象可能使用,我们假设说,要用循环创建1000个对象,那你这种办法似乎就没用了,我们可以这么做,如下代码:
// 使用工厂模式创建对象 function createPerson() { // 创建新的对象 var obj = new Object(); // 设置对象属性 obj.name = "孙悟空"; obj.age = 18; // 设置对象方法 obj.sayName = function () { console.log(this.name); }; //返回新的对象 return obj; }
var person1 = createPerson();
var person2 = createPerson();
var person3 = createPerson();console.log(person1);
console.log(person2);
console.log(person3);- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
上述代码看起来更加简洁,但是你会发现每一个人都是孙悟空,我们要是想要给每一个人不同的属性值,请参考:
// 使用工厂模式创建对象 function createPerson(name, age) { // 创建新的对象 var obj = new Object(); // 设置对象属性 obj.name = name; obj.age = age; // 设置对象方法 obj.sayName = function () { console.log(this.name); }; //返回新的对象 return obj; }
var person1 = createPerson(“孙悟空”, 18);
var person2 = createPerson(“猪八戒”, 19);
var person3 = createPerson(“沙和尚”, 20);console.log(person1);
console.log(person2);
console.log(person3);- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
现在再看上述代码,发现好像已经完美的解决了创建多个对象的难题,那我们是不是可以用循环批量创建1000个对象了呢?那我们就来试试:
// 使用工厂模式创建对象 function createPerson(name, age) { // 创建新的对象 var obj = new Object(); // 设置对象属性 obj.name = name; obj.age = age; // 设置对象方法 obj.sayName = function () { console.log(this.name); }; //返回新的对象 return obj; }
for (var i = 1; i <= 1000; i++) {
var person = createPerson(“person” + i, 18);
console.log(person);
}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
这样我们就实现了批量创建对象的功能,至于对象的名称和年龄,我们可以通过名称数组和年龄数组来获取,但这并不是我们本小节的重点,我们就忽略了。
2.12.2、用构造函数创建对象
在前一节中,我们学会了使用工厂模式创建对象,但是,你会发现我们所创建的对象类型都是Object,具体代码如下:
// 使用工厂模式创建对象 function createPerson(name, age) { // 创建新的对象 var obj = new Object(); // 设置对象属性 obj.name = name; obj.age = age; // 设置对象方法 obj.sayName = function () { console.log(this.name); }; //返回新的对象 return obj; }
for (var i = 1; i <= 1000; i++) {
var person = createPerson(“person” + i, 18);
console.log(typeof person);
}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
那这有问题吗?看起来有,看起来好像又没有,每创建一个都是对象,但是在实际生活中,人应该是一个确定的类别,属于人类,对象是一个笼统的称呼,万物皆对象,它并不能确切的指明当前对象是人类,那我们要是既想实现创建对象的功能,同时又能明确所创建出来的对象是人类,那么似乎问题就得到了解决,这就用到了构造函数,每一个构造函数你都可以理解为一个类别,用构造函数所创建的对象我们也成为类的实例,那我们来看看是如何做的:
// 使用构造函数来创建对象 function Person(name, age) { // 设置对象的属性 this.name = name; this.age = age; // 设置对象的方法 this.sayName = function () { console.log(this.name); }; }
var person1 = new Person(“孙悟空”, 18);
var person2 = new Person(“猪八戒”, 19);
var person3 = new Person(“沙和尚”, 20);console.log(person1);
console.log(person2);
console.log(person3);- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
那这构造函数到底是什么呢?我来解释一下:
构造函数:构造函数就是一个普通的函数,创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写,构造函数和普通函数的还有一个区别就是调用方式的不同,普通函数是直接调用,而构造函数需要使用new关键字来调用。
那构造函数是怎么执行创建对象的过程呢?我再来解释一下:
- 调用构造函数,它会立刻创建一个新的对象
- 将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象
- 逐行执行函数中的代码
- 将新建的对象作为返回值返回
你会发现构造函数有点类似工厂方法,但是它创建对象和返回对象都给我们隐藏了,使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类。我们将通过一个构造函数创建的对象,称为是该类的实例。
现在,this又出现了一种新的情况,为了不让大家混淆,我再来梳理一下:
- 当以函数的形式调用时,this是window
- 当以方法的形式调用时,谁调用方法this就是谁
- 当以构造函数的形式调用时,this就是新创建的那个对象
我们可以使用 instanceof 运算符检查一个对象是否是一个类的实例,它返回true或false
语法格式:
对象 instanceof 构造函数
- 1
案例演示:
console.log(person1 instanceof Person);
- 1
2.12.3、原型
在前一节中,我们学习了使用构造函数的方式进行创建对象,但是,它还是存在一个问题,那就是,你会发现,每一个对象的属性不一样这是一定的,但是它的方法似乎好像是一样的,如果我创建1000个对象,那岂不是内存中就有1000个相同的方法,那要是有10000个,那对内存的浪费可不是一点半点的,我们有没有什么好的办法解决,没错,我们可以把函数抽取出来,作为全局函数,在构造函数中直接引用就可以了,上代码:
// 使用构造函数来创建对象 function Person(name, age) { // 设置对象的属性 this.name = name; this.age = age; // 设置对象的方法 this.sayName = sayName }
// 抽取方法为全局函数
function sayName() {
console.log(this.name);
}var person1 = new Person(“孙悟空”, 18);
var person2 = new Person(“猪八戒”, 19);
var person3 = new Person(“沙和尚”, 20);person1.sayName();
person2.sayName();
person3.sayName();- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
但是,在全局作用域中定义函数却不是一个好的办法,为什么呢?因为,如果要是涉及到多人协作开发一个项目,别人也有可能叫sayName这个方法,这样在工程合并的时候就会导致一系列的问题,污染全局作用域,那该怎么办呢?有没有一种方法,我只在Person这个类的全局对象中添加一个函数,然后在类中引用?答案肯定是有的,这就需要原型对象了,我们先看看怎么做的,然后在详细讲解原型对象。
// 使用构造函数来创建对象 function Person(name, age) { // 设置对象的属性 this.name = name; this.age = age; }
// 在Person类的原型对象中添加方法
Person.prototype.sayName = function() {
console.log(this.name);
};var person1 = new Person(“孙悟空”, 18);
var person2 = new Person(“猪八戒”, 19);
var person3 = new Person(“沙和尚”, 20);person1.sayName();
person2.sayName();
person3.sayName();- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
那原型(prototype)到底是什么呢?
我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype,这个属性对应着一个对象,这个对象就是我们所谓的原型对象,即显式原型,原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中。
如果函数作为普通函数调用prototype没有任何作用,当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性,指向该构造函数的原型对象,我们可以通过
__proto__
(隐式原型)来访问该属性。当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中,这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法了。
2.12.4、原型链
访问一个对象的属性时,先在自身属性中查找,找到返回, 如果没有,再沿着__proto__这条链向上查找,找到返回,如果最终没找到,返回undefined,这就是原型链,又称隐式原型链,它的作用就是查找对象的属性(方法)。
我们使用一张图来梳理一下上一节原型案例的代码:
注意:Object对象是所有对象的祖宗,Object的原型对象指向为null,也就是没有原型对象
2.12.5、toString方法
toString()函数用于将当前对象以字符串的形式返回。该方法属于Object对象,由于所有的对象都"继承"了Object的对象实例,因此几乎所有的实例对象都可以使用该方法,所有主流浏览器均支持该函数。
// 使用构造函数来创建对象 function Person(name, age) { // 设置对象的属性 this.name = name; this.age = age; }
//创建对象的一个实例对象
var p = new Person(“张三”, 20);
console.log(p.toString());- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
JavaScript的许多内置对象都重写了该函数,以实现更适合自身的功能需要。
类型 行为描述 String 返回 String 对象的值。 Number 返回 Number 的字符串表示。 Boolean 如果布尔值是true,则返回"true"。否则返回"false"。 Object
(默认)返回"[object ObjectName]",其中 ObjectName 是对象类型的名称。 Array 将 Array 的每个元素转换为字符串,并将它们依次连接起来,两个元素之间用英文逗号作为分隔符进行拼接。 Date 返回日期的文本表示。 Error 返回一个包含相关错误信息的字符串。 Function 返回如下格式的字符串,其中 functionname 是一个函数的名称
此函数的 toString 方法被调用: “function functionname() { [native code] }”注意:这里我们只是演示toString()方法,其它的一些没有讲到的知识后边会将,我们只看效果就可。
// 字符串 var str = "Hello"; console.log(str.toString());
// 数字
var num = 15.26540;
console.log(num.toString());// 布尔
var bool = true;
console.log(bool.toString());// Object
var obj = { name: “张三”, age: 18};
console.log(obj.toString());// 数组
var array = [“CodePlayer”, true, 12, -5];
console.log(array.toString());// 日期
var date = new Date(2013, 7, 18, 23, 11, 59, 230);
console.log(date.toString());// 错误
var error = new Error(“自定义错误信息”);
console.log(error.toString());// 函数
console.log(Function.toString());- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
2.12.6、hasOwnProperty方法
前边章节我们学过,如何遍历一个对象所有的属性和值,那我们要是判断当前对象是否包含指定的属性或方法可以使用 in 运算符来检查,如下代码演示:
// 创造一个构造函数 function MyClass() { }
// 向MyClass的原型中添加一个name属性
MyClass.prototype.name = “我是原型中的名字”;// 创建一个MyClass的实例
var mc = new MyClass();
mc.age = 18;// 使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
console.log(“age” in mc);
console.log(“name” in mc);- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
如果我只想要检查自身对象是否含有某个方法或属性,我们可以使用Object的hasOwnProperty()方法,它返回一个布尔值,判断对象是否包含特定的自身(非继承)属性。如下代码演示:
// 创造一个构造函数 function MyClass() { }
// 向MyClass的原型中添加一个name属性
MyClass.prototype.name = “我是原型中的名字”;// 创建一个MyClass的实例
var mc = new MyClass();
mc.age = 18;// 使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
console.log(“age” in mc);
console.log(“name” in mc);// 可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性,使用该方法只有当对象自身中含有属性时,才会返回true
console.log(mc.hasOwnProperty(“age”));
console.log(mc.hasOwnProperty(“name”));- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
有同学可能会有疑问,我的这个MyClass类对象中没有hasOwnProperty这个方法啊,它是哪来的?对了,就是原型中的,在执行方法的时候它会通过原型链进行查找,这个方法是Object的特有方法,如下代码演示:
// 创造一个构造函数 function MyClass() { }
// 向MyClass的原型中添加一个name属性
MyClass.prototype.name = “我是原型中的名字”;// 创建一个MyClass的实例
var mc = new MyClass();
mc.age = 18;// 检查当前对象
console.log(mc.hasOwnProperty(“hasOwnProperty”));
// 检查当前对象的原型对象
console.log(mc.proto.hasOwnProperty(“hasOwnProperty”));
// 检查当前对象的原型对象的原型对象
console.log(mc.proto.proto.hasOwnProperty(“hasOwnProperty”));- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
2.12.7、对象继承
前边我们一直在说继承,那什么是继承?它有什么作用?如何实现继承?将会是本章节探讨的问题。
面向对象的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。但是在JavaScript中没有类的概念,前边我们说所的类只是我们自己这么叫,大家要清楚。因此它的对象也与基于类的对象有所不同。实际上,JavaScript语言是通过一种叫做原型(prototype)的方式来实现面向对象编程的。
那实现继承有一个最大的好处就是子对象可以使用父对象的属性和方法,从而简化了一些代码。
JavaScript有六种非常经典的对象继承方式,但是我们只学习前三种:
- 原型链继承
- 借用构造函数继承
- 组合继承(重要)
- 原型式继承
- 寄生式继承
- 寄生组合式继承
2.12.7.1、原型链继承
核心思想: 子类型的原型为父类型的一个实例对象
基本做法:
- 定义父类型构造函数
- 给父类型的原型添加方法
- 定义子类型的构造函数
- 创建父类型的对象赋值给子类型的原型
- 将子类型原型的构造属性设置为子类型
- 给子类型原型添加方法
- 创建子类型的对象: 可以调用父类型的方法
案例演示:
// 定义父类型构造函数 function SupperType() { this.supProp = 'Supper property'; }
// 给父类型的原型添加方法
SupperType.prototype.showSupperProp = function () {
console.log(this.supProp);
};// 定义子类型的构造函数
function SubType() {
this.subProp = ‘Sub property’;
}// 创建父类型的对象赋值给子类型的原型
SubType.prototype = new SupperType();// 将子类型原型的构造属性设置为子类型
SubType.prototype.constructor = SubType;// 给子类型原型添加方法
SubType.prototype.showSubProp = function () {
console.log(this.subProp)
};// 创建子类型的对象: 可以调用父类型的方法
var subType = new SubType();
subType.showSupperProp();
subType.showSubProp();- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
缺点描述:
- 原型链继承多个实例的引用类型属性指向相同,一个实例修改了原型属性,另一个实例的原型属性也会被修改
- 不能传递参数
- 继承单一
2.12.7.2、借用构造函数继承
核心思想: 使用.call()和.apply()将父类构造函数引入子类函数,使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类
基本做法:
- 定义父类型构造函数
- 定义子类型的构造函数
- 给子类型的原型添加方法
- 创建子类型的对象然后调用
案例演示:
借用构造函数继承的重点就在于SuperType**.call(this, name)**,调用了SuperType构造函数,这样,SubType的每个实例都会将SuperType中的属性复制一份。
// 定义父类型构造函数 function SuperType(name) { this.name = name; this.showSupperName = function () { console.log(this.name); }; }
// 定义子类型的构造函数
function SubType(name, age) {
// 在子类型中调用call方法继承自SuperType
SuperType.call(this, name);
this.age = age;
}// 给子类型的原型添加方法
SubType.prototype.showSubName = function () {
console.log(this.name);
};// 创建子类型的对象然后调用
var subType = new SubType(“孙悟空”, 20);
subType.showSupperName();
subType.showSubName();
console.log(subType.name);
console.log(subType.age);- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
缺点描述:
- 只能继承父类的实例属性和方法,不能继承原型属性和方法
- 无法实现构造函数的复用,每个子类都有父类实例函数的副本,影响性能,代码会臃肿
2.12.7.3、组合继承
核心思想: 原型链+借用构造函数的组合继承
基本做法:
- 利用原型链实现对父类型对象的方法继承
- 利用super()借用父类型构建函数初始化相同属性
案例演示:
function Person(name, age) { this.name = name; this.age = age; }
Person.prototype.setName = function (name) {
this.name = name;
};function Student(name, age, price) {
Person.call(this, name, age); // 为了得到父类型的实例属性和方法
this.price = price; // 添加子类型私有的属性
}Student.prototype = new Person(); // 为了得到父类型的原型属性和方法
Student.prototype.constructor = Student; // 修正constructor属性指向
Student.prototype.setPrice = function (price) { // 添加子类型私有的方法
this.price = price;
};var s = new Student(“孙悟空”, 24, 15000);
console.log(s.name, s.age, s.price);
s.setName(“猪八戒”);
s.setPrice(16000);
console.log(s.name, s.age, s.price);- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
缺点描述:
- 父类中的实例属性和方法既存在于子类的实例中,又存在于子类的原型中,不过仅是内存占用,因此,在使用子类创建实例对象时,其原型中会存在两份相同的属性和方法 。
注意:这个方法是JavaScript中最常用的继承模式。
2.12.8、垃圾回收
垃圾回收(GC):就像人生活的时间长了会产生垃圾一样,程序运行过程中也会产生垃圾,这些垃圾积攒过多以后,会导致程序运行的速度过慢,所以我们需要一个垃圾回收的机制,来处理程序运行过程中产生垃圾。
当一个对象没有任何的变量或属性对它进行引用,此时我们将永远无法操作该对象,此时这种对象就是一个垃圾,这种对象过多会占用大量的内存空间,导致程序运行变慢,所以这种垃圾必须进行清理。
在JS中拥有自动的垃圾回收机制,会自动将这些垃圾对象从内存中销毁,我们不需要也不能进行垃圾回收的操作,我们需要做的只是要将不再使用的对象设置null即可。
案例演示:
// 使用构造函数来创建对象 function Person(name, age) { // 设置对象的属性 this.name = name; this.age = age; }
var person1 = new Person(“孙悟空”, 18);
var person2 = new Person(“猪八戒”, 19);
var person3 = new Person(“沙和尚”, 20);person1 = null;
person2 = null;
person3 = null;- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
2.13、作用域
作用域指一个变量的作用的范围,在JS中一共有两种作用域:
- 全局作用域
- 函数作用域
2.13.1、声明提前
- 变量的声明提前:使用var关键字声明的变量,会在所有的代码执行之前被声明(但是不会赋值),但是如果声明变量时不使用var关键字,则变量不会被声明提前
- 函数的声明提前:使用函数声明形式创建的函数 function 函数名(){} ,它会在所有的代码执行之前就被创建,所以我们可以在函数声明前来调用函数。使用函数表达式创建的函数,不会被声明提前,所以不能在声明前调用
2.13.2、作用域
2.13.2.1、全局作用域
- 直接编写在script标签中的JavaScript代码,都在全局作用域
- 全局作用域在页面打开时创建,在页面关闭时销毁
- 在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口,它由浏览器创建,我们可以直接使用
- 在全局作用域中:
- 创建的变量都会作为window对象的属性保存
- 创建的函数都会作为window对象的方法保存
- 全局作用域中的变量都是全局变量,在页面的任意的部分都可以访问的到
2.13.2.2、函数作用域
- 调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁
- 每调用一次函数就会创建一个新的函数作用域,它们之间是互相独立的
- 在函数作用域中可以访问到全局作用域的变量,在全局作用域中无法访问到函数作用域的变量
- 在函数中要访问全局变量可以使用window对象
- 作用域链:当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用,如果没有则向上一级作用域中寻找,直到找到全局作用域,如果全局作用域中依然没有找到,则会报错ReferenceError
2.13.3、作用域链
多个上下级关系的作用域形成的链,它的方向是从下向上的(从内到外),查找变量时就是沿着作用域链来查找的。
查找一个变量的查找规则:
- 在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入2
- 在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入3
- 再次执行2的相同操作,直到全局作用域,如果还找不到就抛出找不到的ReferenceError异常
第三章 JavaScript常用对象
3.1、数组对象
3.1.1、概述
数组也是对象的一种,数组是一种用于表达有顺序关系的值的集合的语言结构,也就是同类数据元素的有序集合。
数组的存储性能比普通对象要好,在开发中我们经常使用数组来存储一些数据。但是在JavaScript中是支持数组可以是不同的元素,这跟JavaScript的弱类型有关,此处不用纠结,我们大多数时候都是相同类型元素的集合。数组内的各个值被称作元素,每一个元素都可以通过索引(下标)来快速读取,索引是从零开始的整数。
使用typeof检查一个数组对象时,会返回object。
3.1.2、创建数组
3.1.2.1、使用对象创建
-
同类型有序数组创建:
var arr = new Array(); arr[0] = 1; arr[1] = 2; arr[2] = 3; arr[3] = 4; arr[4] = 5; arr[5] = 6; arr[6] = 7; arr[7] = 8; arr[8] = 9;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
-
不同类型有序数组创建:
var arr = new Array(); arr[0] = 1; arr[1] = "2"; arr[2] = 3; arr[3] = "4"; arr[4] = 5; arr[5] = "6"; arr[6] = 7; arr[7] = "8"; arr[8] = 9;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
3.1.2.2、使用字面量创建
-
同类型有序数组创建:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
- 1
-
不同类型有序数组创建:
var arr = [1, "2", 3, "4", 5, "6", 7, "8", 9];
- 1
3.1.3、遍历数组
for (var i = 0; i < arr.length; i++) { console.log(arr[i]); }
- 1
- 2
- 3
3.1.4、数组属性
constructor属性演示:返回创建数组对象的原型函数
var arr = [1,2,3,4]; console.log(arr.constructor);
- 1
- 2
length属性演示:设置或返回数组元素的个数
var arr = [1,2,3,4]; console.log(arr.length);
- 1
- 2
3.1.5、数组方法
push()方法演示:该方法可以向数组的末尾添加一个或多个元素,并返回数组的新的长度
var arr = ["孙悟空", "猪八戒", "沙和尚"]; var result = arr.push("唐僧", "蜘蛛精", "白骨精", "玉兔精"); console.log(arr); console.log(result);
- 1
- 2
- 3
- 4
pop()方法演示:该方法可以删除数组的最后一个元素,并将被删除的元素作为返回值返回
var arr = ["孙悟空", "猪八戒", "沙和尚"]; var result = arr.pop(); console.log(arr); console.log(result);
- 1
- 2
- 3
- 4
unshift()方法演示:该方法向数组开头添加一个或多个元素,并返回新的数组长度
var arr = ["孙悟空", "猪八戒", "沙和尚"]; var result = arr.unshift("牛魔王", "二郎神"); console.log(arr); console.log(result);
- 1
- 2
- 3
- 4
shift()方法演示:该方法可以删除数组的第一个元素,并将被删除的元素作为返回值返回
var arr = ["孙悟空", "猪八戒", "沙和尚"]; var result = arr.shift(); console.log(arr); console.log(result);
- 1
- 2
- 3
- 4
forEach()方法演示:该方法可以用来遍历数组
forEach()方法需要一个函数作为参数,像这种函数,由我们创建但是不由我们调用的,我们称为回调函数。数组中有几个元素函数就会执行几次,每次执行时,浏览器会将遍历到的元素,以实参的形式传递进来,我们可以来定义形参,来读取这些内容,浏览器会在回调函数中传递三个参数:
- 第一个参数:就是当前正在遍历的元素
- 第二个参数:就是当前正在遍历的元素的索引
- 第三个参数:就是正在遍历的数组
注意:这个方法只支持IE8以上的浏览器,IE8及以下的浏览器均不支持该方法,所以如果需要兼容IE8,则不要使用forEach(),还是使用for循环来遍历数组。
var arr = ["孙悟空", "猪八戒", "沙和尚"]; arr.forEach(function (value, index, obj) { console.log(value + " #### " + index + " #### " + obj); });
- 1
- 2
- 3
- 4
slice()方法演示:该方法可以用来从数组提取指定元素,该方法不会改变元素数组,而是将截取到的元素封装到一个新数组中返回
参数:
- 第一个参数:截取开始的位置的索引,包含开始索引
- 第二个参数:截取结束的位置的索引,不包含结束索引,第二个参数可以省略不写,此时会截取从开始索引往后的所有元素
注意:索引可以传递一个负值,如果传递一个负值,则从后往前计算,-1代表倒数第一个,-2代表倒数第二个。
var arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧", "白骨精"]; var result = arr.slice(1, 4); console.log(result); result = arr.slice(3); console.log(result); result = arr.slice(1, -2); console.log(result);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
splice()方法演示:该方法可以用于删除数组中的指定元素,该方法会影响到原数组,会将指定元素从原数组中删除,并将被删除的元素作为返回值返回
参数:
- 第一个参数:表示开始位置的索引
- 第二个参数:表示要删除的元素数量
- 第三个参数及以后参数:可以传递一些新的元素,这些元素将会自动插入到开始位置索引前边
var arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧", "白骨精"]; var result = arr.splice(3, 2); console.log(arr); console.log(result); result = arr.splice(1, 0, "牛魔王", "铁扇公主", "红孩儿"); console.log(arr); console.log(result);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
concat()方法演示:该方法可以连接两个或多个数组,并将新的数组返回,该方法不会对原数组产生影响
var arr = ["孙悟空", "猪八戒", "沙和尚"]; var arr2 = ["白骨精", "玉兔精", "蜘蛛精"]; var arr3 = ["二郎神", "太上老君", "玉皇大帝"]; var result = arr.concat(arr2, arr3, "牛魔王", "铁扇公主"); console.log(result);
- 1
- 2
- 3
- 4
- 5
join()方法演示:该方法可以将数组转换为一个字符串,该方法不会对原数组产生影响,而是将转换后的字符串作为结果返回,在join()中可以指定一个字符串作为参数,这个字符串将会成为数组中元素的连接符,如果不指定连接符,则默认使用,作为连接符