JavaScript&jQuery
JavaScript基础
JavaScript简介
什么是语言
- 计算机就是一个由人控制的机器,人让他干嘛,它就得干嘛。
- 我们学习的语言就是人和计算机交流的工具,人类通过语言来控制、操作计算机。
- 编程语言和我们说的中文、英文本质上没有区别,只是语法比较特殊。
- 语言的发展:
纸带机:机器语言
汇编语言:符号语言
现代语言:高级语言
- 官方概念:JavaScript是一个跨平台的脚本语言。
平台:就是运行环境,这里一般指操作系统,js代码由浏览器在各个平台运行
跨平台:就是在各个环境下都可以运行
脚本语言:不能独立运行,要依赖于网页。HTML网页的运行离不开浏览器,js程序的运行离不开HTML网页
起源
- 1995年,NetScape公司,一位叫做布兰登艾奇的程序员,发明了一种运行在浏览器网页里的脚步语言,名字叫做Livescript。
- 这个脚本语言的作用就是为了验证表单信息的,这在当时来说是一个前所未有的壮举
- 布兰登艾奇现任火狐浏览器CEO
- LiveScript打开了浏览器市场,各大互联网巨头,都嗅到了蛋糕的味道,都想着能够从中分一杯羹,其中首当其中的就是微软的IE浏览器。
- IE当年为了跟NetScape争夺市场份额。不甘落后很快也推出了适用于IE浏览器的JScript脚本语言
- 由于缺乏相关统一的标准,各个浏览器发明的脚步语言,在用法上和规范上不一致,这简直成了网页开发人员的噩梦
- 直到1997年,大家找到了ECMA(欧洲计算机制造协商会),ECMA成立了一个叫做TC39的委员会,委员会的成员,基本都是来自各大浏览器厂商。
- 当然这里面最重要的成员就是当年如日中天的SUN公司,大家坐下来讨论之后,仅仅过了大概一个月的时间,就很快的制定了浏览器脚本语言的第一个全球标准。官方名称为ECMAScript,编号为ECMA-262.
- JavaScript诞生于1995年,它的出现主要是用于处理网页总的前端验证。
- 所谓的前端验证,就是指检查用户输入的内容是否符合一定的规则。
- 比如:用户名的长度,密码的长度,邮箱的格式等。
简史
- JavaScript是有网景公司发明,起初命名为LiveScript,后来由于SUN公司的介入更名为了JavaScript。
- 1996年微软公司在其最新的IE3浏览器中引入了自己对JavaScript的实现JScript。
- 于是市面上存在两个版本的JavaScript,一个是网景公司的JavaScript,一个是微弱的JScript。
- 为了确保不同的浏览器上运行的JavaScript标准一致,所以几个公司共同制定了JS的标准命名为ECMAScript。
- 由于,众所周知的原因,大家还是没忍住,蹭了一把java的热度,最终将其命名为JavaScript。至此JavaScript正式诞生。
- 如果非要说Java和JavaScript有什么关系的话,那就是,JavaScript语法及很多API设计上,直接借鉴甚至抄袭了Java,在内存结构设计上,也直接借鉴和抄袭了Java
- 一个有趣的网站web.archive.org网页时光机,它记录了众多明星网站多年前的模样。
时间表
年份 | 事件 |
---|---|
1995 | 网景公司开发了JavaScript |
1996 | 微软发布了和JavaScript兼容的JScript |
1997 | ECMAScript第一版(ECMA-262) |
1998 | ECMAScript第二版 |
1998 | DOMLevel1的制定 |
1998 | 新型语言DHTML登场 |
1999 | ECMAScript第三版 |
2000 | DOM Level2的制定 |
2002 | ISO/IEC 16262:2002的确立 |
2004 | DOM Level3的制定 |
2005 | 新型语言AJAX登场 |
2009 | ECMAScript第五版 |
2009 | 新型语言HTML5登场 |
实现
- ECMAScript是一个标准,而这个标准需要由各个厂商去实现。
- 不同的浏览器厂商对该标准会有不同的实现
浏览器 | JavaScript实现方式 |
---|---|
FireFox | SpiderMonkey |
Internet Explorer | JScript/Chakra |
Safari | JavaScriptCore |
Chrome | V8 |
Carakan | Carakan |
- 我们已经知道ECMAScript是JavaScript标准,所以一般情况下这两个词我们认为是一个意思。
- 但是实际上JavaScript的含义却要更大一些。
- 一个完整的JavaScript实现应该由以下三个部分构成
ECMAScript 3 4 5 6 7
DOM:html标签开始到html标签结束
BOM:一个浏览器窗口
- 我们已经知道了一个完整的JavaScript实现包含了三个部分:ECMAScript、DOM和BOM。
- 由此我们也知道了我们所要学习的内容就是这三部分
特点
- JS的特点
解释型语言
类似于C和Java的语法结构
动态语言
基于原型的面向对象
JavaScript基础语法
基础语法
- JS中严格区分大小写
- JS中每条语句以封号(;)结尾。
- 如果不写封号,浏览器会自动添加。但是会消耗一些系统资源
- 有些时候,浏览器会加错封号,所以要求每条语句都手动添加封号
- JS中会忽略多个空格和换行。我们可以利用空格和换行进行代码格式调整优化
JavaScript编写位置
- JavaScript代码需要编写到script标签中,script标签自带两个属性
type = “text/javascript” 声明当前标签内写的文本格式(可以省略)
src = “demo.js” 引入外部的.js文件
- 可以有多个script标签
多个script标签自上而下顺序执行
一个script标签只能专心做一个事情,直接写js代码或者外部引入
在引入外部.js文件的script标签中不能写js代码,执行不到
- js代码的书写位置除了写在外部引入和HTML中的script标签,还可以写在HTML标签中
<button onclick="alert('点我干嘛~~~');">点我一下</button>
- 一般写在HTML标签中的方式不推荐,不方便后期维护。内部script中的代码虽然与结构分开了,但是代码复用性低,常用外部引入的方式。外部引入的js文件可以在多处引用,复用性高。而且可以利用到浏览器的缓存机制。推荐使用
输出内容:
- document.write(‘这是一些内容’);向当前页面body中写入内容
内容是直接输出到当前页面的
如果内容中有HTML标签,会直接解析
如果需要原样输出,需要转义,否则会解析标签
转义符<左括号,>右括号,可以实现标签原样输出
- alert(‘这是一些内容’);在页面上弹出警告框
- console.log(‘这是一些内容’);在浏览器调试面板控制台输出内容
一般情况下用于代码调试
小彩蛋,网站招聘信息,如百度
JavaScript注释
- 单行注释//
- 多行注释/**/
- 在编写JS代码的时候,要在每一行代码结束的位置加;(分号),后期代码压缩不会出问题
- 代码压缩:去掉编写代码的时候,有的所有空格、tab键和换行。
JavaScript基础知识
字面量/常量和变量
- 常量:值不可以改变的量就是常量。number所表示的最大值Number.MAX_VALUE
- 字面量:可以直接使用,如1,2等。但是我们一般不会直接使用字面量。
- 变量:变量可以保存字面量。而且变量的值可以改变。变量更加方便我们使用,所以开发中都是通过一个变量保存一个常量,而很少直接使用一个字面量。可以通过变量对一个字面量进行说明,如var age=20;则这个字面量20表示年龄
- 声明变量(声明后正常使用)
- 关键字(系统征用的有特殊含义的单词)var关键字
- 初始化:声明变量的时候直接赋值
- 声明好变量以后没有赋值直接使用,默认值就是undefined
- 变量声明以后如果没有赋值,则默认就是undefined,此时再次赋值,则需要取出原值undefined,再进行新值赋值操作,如果声明时赋值null,则表示里面没有值,进行新值赋值,是直接赋值进去。效率较高
标识符
- 所有我们用户自定义的自主命名的都是标识符。例如变量名、属性名、函数名都是标识符
- 命名一个标识符需要遵守以下规则
标识符中可以含有字母,数字,下划线_,美元符$
标识符不能以数字开头
标识符不能是ES中的关键字或保留字
标识符一般都是采用驼峰命名法
JS底层保存标识符实际采用的是Unicode编码,所以理论上所有UTF-8中的字符都可以作为标识符。包括汉字,但是一般不使用
- 变量:弱引用类型,赋值成什么数据类型就是什么数据类型
- 不建议改变变量的数据类型。容易引起歧义
- typeof 变量/常量:判断变量或者常量的类型
var num = 100;
console.log(typeof 20);
console.log(typeof num);
console.log(typeof true);
console.log(typeof "hello");
console.log(typeof undefined);
数据类型
- 基本数据类型
- 数字:
类型:number
例子:100 -20 3.14 - 布尔值:
类型:boolean
例子:true false - 字符串:
类型:所有带单引号或者双引号的都叫字符串。(单引号和双引号在JS中效果一样。但是都必须成对出现)
例子:“hello” ‘world’ “100” ‘3.14’
- 复合/引用数据类型(后面讲解)
- 特殊数据类型
null:空
undefined:未定义的
NaN:不是一个数字
String
在JS中使用引号引起来
使用双引号或单引号都可以,但不要混着用
引号不能嵌套使用,单引号中不能再使用单引号,双引号中再不能使用双引号
可以在单引号中使用双引号,也可以换过来双引号中使用单引号
对于特殊字符,可以使用转义符斜杠进行转义,如
\"就表示一个引号、\t一个制表符、\n换行、\一个斜杠,自己转义自己,与java类似
也可以通过Unicode获取特殊
String 类型拥有length属性,用来获取字符串的长度
String类型的拼接与java中一样
var str = "今天天气不错";
console.log(str);
str = '我说:"今天天气真不错"';
console.log(str);
str = "我说:\"今天天气真不错\"";
console.log(str);
str = "我说:\n\"今天天气不错\"\n";
console.log(str);
str = "my name is andy";
console.log(str.length);//15
Number
在JS中所有的数值都是number类型,包括整数和浮点数
JS中可以表示数字的最大时为Number.MAX_VALUE,为1.7976931348623157e+308
JS中的Number.MIN_VALUE表示0以上最小值,为5-e324,也就是0.000000000005
负数最小值就是-Number.MAX_VALUE
如果使用Number表示的数字超过了最大值,则会返回infinity,表示正无穷
infinity表示正无穷、-infinity表示负无穷。都是字面量。可以直接使用
如果两个非数字进行运算,则会出现NaN,即Not a Number,不是一个数字
NaN也是一个字面量,可以直接使用,属于Number类型
JS中的Number进行运算时一般都可以得到正确结果,但是对于小数运算可能结果不精确,如果0.1+0.2
所以尽量不要用JS进行要求高精确度的运算
var num = 123;//声明变量并赋值123
console.log(num);
num = 456.789;
console.log(num);
var b = "123";//声明变量b,赋值字符串123
console.log(b);
console.log(Number.MAX_VALUE);//输出最大值
var c = Number.MAX_VALUE * Number.MAX_VALUE;
console.log(c);//结果输出infinity
var d = Number.MAX_VALUE;//是一个常量,可以直接给变量赋值使用
console.log(d);
var e = infinity;//infinity是一个字面量,可以直接使用,属于number类型
console.log(e);//输出结果依然是infinity
console.log(typeof e);//输出结果为number
var f = "abc" * "abc";
console.log(f);//输出结果为NaN
console.log(typeof f);//输出结果为Number
var g = 0.1+0.2;
console.log(g);//结果为0.300000000004
Boolean
JS中的Boolean是指布尔类型,只有两个值,true和false
true:表示逻辑上的真
false:表示逻辑上的假
用typeof判断其变量返回boolean
var bool = true;
console.log(bool);//返回true
console.log(typeof bool);//返回结果为boolean
Null和Undefined
Null 只有一个值,就是null
null这个值专门用来表示一个为空的对象
使用typeof检查null的类型,返回结果为object
var a = null;
console.log(a);//返回结果就是null
console.log(typeof a);//返回结果为object
Undefined类型只有一个值,就是undefined
当声明一个变量,但并不给变量赋值时,它的值就是undefined
使用typeof检查一个undefined时,返回的就是undefined类型
var a;
console.log(a);//输出结果为undefined
console.log(typeof a);//输出结果为undefined
类型转换
- 强制类型转换是指将一个数据类型强制转换为其他数据类型
- 类型转换主要是指将其他数据类型转换为:String、Number、Boolean,转为Null和Undefined没有意义
将其他类型转为String类型
- 方式一:
调用被转换数据类型的toString()方法
该方法不会影响到原变量,它会将转换的结果返回
但是注意,null和undefined这两个值没有toString()方法,调用会报错
var a = 123;
var b = a.toString();
console.log(b);//输出结果为“123”
console.log(typeof b);//输出结果为String
//如果想改变原变量的类型,则可以继续赋值给a变量
a = a.toString();
console.log(a);//输出结果为"123"
console.log(typeof a);//输出结果为String
a = null;
a.toString();//报错
a = undefined;
a.toString();//报错
- 方式二:
调用String()函数,并将要转换的变量作为参数传递给函数
使用String()函数做强制类型转换时:对于Number、Boolean类型,实际上底层调用的还是toString()方法
但是对于null和undefined,就不会调用toString()了,它是直接将null转为"null",将undefined转为"undefined"
var a = 123;
a = String(a);
console.log(a);//输出结果为"123"
console.log(typeof a);//输出结果为string
var b = null;
b = String(b);
console.log(b);//输出结果为"null"
console.log(typeof b);//输出结果为string
var c = undefined;
c = String(c);
console.log(c);//输出结果为"undefined"
console.log(typeof c);//输出结果为string
将其他类型转为Number
- 方式一:
使用Number()函数
字符串转数字:
1. 如果是纯数字,则直接转为number类型的数字
2. 如果字符串中含有非数字的内容,则转为number类型的NaN
3. 如果是一个空字符串或者只有空格制表符等的,则转为number类型的0布尔转数字:
1. 如果是true,则转为number类型的1
2. 如果是false,则转为number类型的0null转为数字:
null转为number类型的0
undefined转为数字:
undefined转为number类型的NaN
方式一有局限性,如果是从页面读取到的值,比如123px,如果是使用这种方式转,则会遇到NaN
var a = "123";
a = Number(a);
console.log(a);//123
console.log(typeof a);//number
var b = "abc";
b = Number(b);
console.log(b);//NaN
console.log(typeof b);//number
var c = true;
c = Number(c);
console.log(c);//1
console.log(typeof c);
var d = false;
d = Number(d);
console.log(d);//0
console.log(typeof d);//number
var e = null;
e = Number(e);
console.log(e);//0
console.log(typeof e);//number
var f = undefined;
f = Number(f);
console.log(f);//NaN
console.log(typeof f);//number
- 方式二:这种方式主要针对字符串。可以提取字符串中的数字,然后进行转换
使用parseInt()函数转为整数
对于数字开头的字符串,从开头读,到第一个非数字字符结束,然后进行转换
对于非数字开头的,依然转为NaN
如果是有小数点的,认为是非数字,如123.32,只会转为123使用parseFloat()函数取小数,它与parseInt函数类似,唯一不同就是可以取出小数,遇到的第一个小数点会取出,其他一致
如果给parseInt或者parseFloat函数传入的参数是非字符串类型的,它会先将参数转为字符串,然后再进行转换
利用减号、乘号、除号隐式转换,字符串进行减、乘、除法运算,会自动进行转换,转换规则类似Number函数
var a = "123px";
a = parseInt(a);
console.log(typeof a);//number
console.log(a);//123
var b = "123ab456px";
b = parseInt(b);
console.log(b);//123
console.log(typeof b);//number
var c = "a1223px";
c = parseInt(c);
console.log(c);//NaN
console.log(typeof c);//number
var d = "123.456px";
d = parseInt(d);
console.log(d);//123
console.log(typeof d);//number
var e = "123.456px";
e= parseFloat(e);
console.log(e);//123.456
console.log(typeof e);//number
var f = "123.456.789px";
f = parseFloat(f);
console.log(f);//123.456
console.log(typeof f);//number
var g = true;
g = parseInt(g);
console.log(g);//NaN
console.log(typeof g);//number
console.log('123' - '120');//3
console.log('123' * 1);//123
- isNaN()函数
可以使用isNaN函数判断一个变量是否是number类型
如果是number类型,则返回false,如果不是number类型,则返回true
其他类型转boolean类型
使用Boolean()函数
如果是代表空的值,如’ ',0,null,NaN,undefined都会转为false
其余值都会转为true,包括字符串空格,制表符等
对象也会转换为true
还可以使用两次非运算,隐式转换
console.log(Boolean(''));//false
console.log(Boolean(' '));//true
console.log(Boolean(0));//false
console.log(Boolean(null));//false
console.log(Boolean(NaN));//false
console.log(Boolean(undefined));//false
console.log(Boolean('你好'));//true
console.log(Boolean(12));//true
console.log(Boolean(infinity));
进制转换
- 进制有二进制、八进制、十进制、十六进制
二进制:用0b或0B表示
八进制:用0表示
十六进制:用0x表示
- 十进制转其他进制:
除以进制数取余
从下往上合并余数即可
52转二进制,为110100
- 可以先将十进制转二进制,然后再转其他进制
先将十进制转为二进制
然后要转八进制的话从右往左,每三位数一组,不足的补0,每组转为十进制,合并即可
110100 -> 110 100 -> 6 4 -> 结果为64
要转十六进制的话从右往左,每四位数一组,不足的补0,每组转为十进制,合并即可
110100 -> 0011 0100 -> 3 4 ->结果为34
- 其他进制转十进制:
从右往左算
每位数一次乘以对应进制的位数-1次方
在将没位数计算结果相加即可
110100 -> 02^0 + 02^1 + 12^2 + 02^3 + 12^4 + 12^5 -> 0+0+4+0+16+32 -> 52
console.log(0b110100);
console.log(064);
console.log(0x34);
console.log(52);
//输出结果都是52
运算符
运算符(operator)也被称为操作符,是用于实现赋值、比较和执行算数运算等功能的符号。比如typeof就是运算符,可以获取一个值的类型,并将该值的类型以字符串的形式返回JavaScript中有以下常用运算符:
算术运算符
概述
- 概念:算术运算使用的符号,用于执行两个变量或值的算术运算。
- 分类:
运算符 描述 实例 + 加 20 + 10 = 30 - 减 20 - 10 = 10 * 乘 20 * 10 = 200 / 除 20 / 10 = 2 % 模(余) 9 % 4 = 1 - 浮点数精度问题:浮点数值的最高精度是17位小数,但在进行算术计算时其精确度远远不如整数。所以不要直接判断两个浮点数是否相等。
- 注意点
对于除字符串拼接外的其他类型的运算,都会转为Number类型之后运算,如果计算true+false的结果为1
任何值和NaN计算结果都为NaN
对字符串进行加法运算,会进行字符串拼接
任意类型数据与字符串进行加法运算,都会进行字符串拼接 - 示例:
console.log(10 + 10);//20 console.log(2 + true);//3 console.log(true + false);//1 console.log(2 + NaN);//NaN console.log(2 + undefined);//NaN console.log(2 + null);//2 console.log('12' + 5);//125 console.log(1 + 2 + '3');//33 console.log(12 - 2);//10 console.log('10' - 2);//8 console.log(true - false);//1 console.log(2 * 5);//10 console.log(2 * undefined);//NaN console.log(2 * null);//0 console.log(10 / 5);2 console.log(9 % 4);//1 console.log(4 % );//0
表达式与返回值
- 表达式:是由数字、运算符、变量等以能求得数值的有意义排列方法组合,简单的理解为是由数字、运算符、变量等组成的式子。
- 返回值:表达式最终都会有一个结果返回给我们,我们称为返回值。
- 在我们程序中,与数学中不同,需要在赋值符号右边进行运算,左边赋值。也就是结果在等号左边。
一元运算符
概念:
只需要一个操作数的运算符。如typeof
正负号
- +:加号,在二元运算符中,做加法运算,将加号左右两个操作数相加。单独与一个操作数使用表示正数。
- -:减号,在二元运算中。做减法运算,将减号左右两个操作数做减法。单独与一个操作数使用表示负数。
- 对于非number的值,先进行类型转换,转为number之后再进行运算。可以对一个其他类型,通过一个正号转为num变为类型。原理与Number()函数一样。
var a = 123; a = +a; console.log(a); //123 a = -a; console.log(a); //-123 a = "12"; a = +a; console.log(typeof a); //number console.log(a); //18 a = true; a = -a; console.log(typeof a); //number console.log(a); //-1 var result = 1 + +"2" +3; console.log(result); //6 console.log(typeof result); //number
自增自减
- 概述:
如果需要反复给数字变量添加或者减去1,可以使用自增(++)和自减(–)运算符来完成。
在JavaScript中,自增和自减既可以放在变量前面,也可以放在变量后面。放在变量前面时,我们可以称为前置自增(自减),放在变量后面时,我们称为后置自增(自减)。
需要注意:自增和自减运算符必须和变量配合使用。 - 自增
- 前置自增和后置自增运算可以简化代码的编写,让变量的值+1,比以前的写法更简单
- 单独使用时,运行结果相同
- 与其他代码联用时,执行结果会不同
- 后置自增是先原值运算,后自增
- 前置自增是先自增,后运算
- 开发时,大多使用后置自增,并且代码独占一行,例如num++;
- 自减
- 通过自减可以使变量在自身的值基础上减1
- 自减也分两种,后置和前置
- 单独使用时也是没有区别
- 与其他代码一起联用时,规则与自增一样
var a = 10; ++a; // a = 11 var b = ++a + 2; //a = 12 ++a = 12 console.log(b); //14 var c = 10; c++; // c = 11 var d = c++ + 2; // c++ = 11 c = 12 console.log(d); // 13 var e = 10; var f = e++ + ++e; // e++ = 10 e = 11 e = 12 ++e = 12 console.log(f); //22 var n = 20; n--; // 19 var m = --n; // 18 console.log(m); //18
关系运算符
概念:
比较运算符(关系运算符)是两个数据进行比较时所使用的运算符,比较运算后,会返回一个布尔值(true/false)作为比较运算的结果。
注意:对于非数字比较时,会转换为数字进行比较
任何值和NaN比较,结果都为false
**特殊情况:**如果两边都是字符串进行比较,则不会将字符串转为数字,而是比较两个字符串的Unicode编码,而且是一位一位进行比较的。
分类:
运算符 | 说明 | 案例 | 结果 |
---|---|---|---|
< | 小于号 | 1< 2 | true |
> | 大于号 | 1 > 2 | false |
>= | 大于等于号(大于或者等于) | 2 >= 2 | true |
<= | 小于等于号(小于或者等于) | 3 <=2 | false |
== | 判等号 (会转型) | 37 == ‘37’ | true |
!= | 不等号 | 37 != 37 | false |
===、!== | 全等,要求值和数据类型都一致、不全等于 | 37 === ‘37’ | false |
示例:
console.log(3 >= 5); // false
console.log(2 <= 4); // true
console.log(3 == 5); // false
console.log('大哥' == '二哥'); // false
console.log(18 == 18); // true
console.log(18 == '18'); // true
console.log(18 != 18); // false
console.log(18 === 18); // true
console.log(18 === '18'); // false
// 对于非数字进行比较,则会转换为数字进行比较
console.log(1 > true); // false
console.log(1 >= false); // true
console.log(10 > null); // true
// 任何值和NaN比较,结果都为false
console.log(10 >= 'hello'); //false
console.log(10 <= 'hello'); //false
// 两个字符串进行比较,不会转为数字进行比较,而是分别比较Unicode的
console.log('1' < '5'); // true 虽然结果为true,但是实际比较的不是只,而是编码,实际是一位一位比较,也就是先拿第一个1跟后面的5进行比较,小于
console.log('11' < '5'); //true 比较Unicode的结果为true
console.log(11 < '5'); // false 如果一边是数字,一边是字符串,则会转为数字比较
console.log(null == 0); // false
// undefined衍生自null,所以这两个值进行相等判断时,会返回true
console.log(undefined == null); // true
// NaN不和任何值相等,包括本身,所以检查一个值是否是NaN,通过isNaN函数判断
console.log(NaN == 1); // false
console.log(NaN == null); // false
console.log(NaN == undefined); // false
console.log(NaN == NaN); // false
条件运算符
概念:
条件运算符,也叫三元运算符
语法及执行流程:
语法:
条件表达式?语句1:语句2;
执行流程:
条件运算符在执行时,首先对条件表达式进行求值,
如果该值为true,则执行语句1,并返回执行结果
如果该值为false,则执行语句2,并返回执行结果
如果条件表达式的结果是一个非boolean值,会转换为Boolean后运算
示例:
false ? alert("语句1") : alert("语句2");
var a = 30;
var b = 20;
a > b ? alert("a比较大") : alert("b比较大");
var max = a > b ? a : b;
console.log("max = " + max);
"hello" ? alert("语句1") : alert("语句2");
null ? alert("语句1") : alert("语句2");
逻辑运算符
概念:
逻辑运算符是用来进行布尔值运算的运算符。其返回值也是布尔值。后面开发中经常用于多个条件的判断。
分类:
逻辑运算符 | 说明 | 案例 |
---|---|---|
&& | “逻辑与”简称与,and | true && false |
|| | “逻辑或”简称或,or | true || false |
! | “逻辑非”简称非,not | ! true |
详情分析
- 逻辑与
- 可以对符号两侧的值进行与运算并返回结果
- 两个值中只要有一个值为false就返回false
- 只有两个值都为true,才会返回true
- js中的与属于短路与,如果第一个值为false,则不会判断第二个值
- **短路运算的原理:**当有多个表达式(值)时,左边的表达式值可以确定结果时,就不再继续运算右边的表达式的值
- 当参与运算的不是布尔,而是值或者表达式,如表达式1 && 表达式2
- 如果第一个表达式的值为真,则返回表达式2
- 如果第一个表达式的值为假,则返回表达式1
- 逻辑或
- 可以对符号两侧的值进行或运算并返回结果
- 两个值中只要有一个true,就返回true
- 如果两个值都为false,才返回false
- js中的或属于短路或,如果第一个值为true,则不会判断第二个值
- 逻辑非
- 可以用来对一个值进行非运算
- 所谓非运算就是指对一个布尔值进行取反操作
- 如果一个值进行两次取反,它不会变化
- 如果对非布尔值进行运算,则会将其转换为布尔值,然后再取反
- 我们可以利用第四点特性,来将一个其他的数据类型转换为布尔值
- 可以为一个任意数据类型取反两次,来将其转换为布尔值,原理和Boolean()函数一样。
- **短路运算原理:**当多个表达式(值)时,左边的表达式值为true,则返回左边的值
- 如果左边的结果为false,则返回右边的值
- 案例:
var a = true; a = !a; console.log(a); //falst var b = true; b = !!a; //两次取反 console.log(b); //true var c = 10; c = !c; console.log(typeof c); //boolean console.log(c); //false var result = true; result = true && false; console.log(result); //false result = true && true; console.log(result); // true result = false && true; console.log(result); // false result = false && false; console.log(result); //false // 第一个值为true,会检查第二个值 false && alert("应该会出现"); //第一个值为false,不会检查第二个值,短路 true && alert ("不会出现吧"); result = true || false; console.log(result); //true result = false || true; console.log(result); //true result = true || true; console.log(result); //true result = false || false; console.log(result); //false // 第一个为false,才会判断第二个 false || alert("会检查第二个"); // 第一个为true,不会判断第二个 true || alert("不判断第二个"); var num = 7; var str = "我爱你~中国~"; console.log(num > 5 && str.length >= num); // true console.log(num < 5 && str.length >= num); //false console.log(!(num < 10)); // false console.log(!(num < 10 || str.length == num)); // false // 逻辑中断(短路与运算) //表达式1的值为true,则直接返回表达式2 console.log(123 && 456); //456 //表达式1的值为假,则返回表达式1 console.log(0 && 456); //0 console.log(0 && 1 + 2 && 456 * 56789); //0 //逻辑中断(短路或运算) // 表达式1的值为true,则直接返回表达式1 console.log(123 || 456);//123 console.log(123||456||456+123); //123 console.log(0 || 456 || 456+123); //456 var n = 0; //判断表达式1的结果为true,直接返回123,n++不会运行 console.log(123 || n++); //123 //由于n++没有运行,所以n的值依然是0 console.log(n);//0
赋值运算符
概念:
用来吧数据赋值给变量的运算符。
将符号右边的值赋值给左边的变量
分类:
赋值运算符 | 说明 | 案例 |
---|---|---|
= | 直接赋值 | var usrName = ‘我是值’; |
+=、-= | 加、减一个数后再赋值 | var age = 10; age+=5; //15 |
*=、/=、%= | 乘、除、模后再赋值 | var age = 2; age*=5; //10 |
示例
var age = 10;
age += 5; //相当于 age = age + 5;
age -= 5; //相当于 age = age - 5;
age *= 5; //相当于 age = age * 5;
age %= 5; //相当于 age = age % 5;
运算符优先级
逗号运算符
可以利用逗号同时声明多个变量:var a, b, c;
可以利用逗号同时声明并赋值多个变量:var a = 1, b = 2, c = 3;
- 运算符优先级表
优先级 | 运算符 | 顺序 |
---|---|---|
1 | 小括号 | () |
2 | 一元运算符 | ++ – ! |
3 | 算术运算符 | 先* / % 后 + - |
4 | 关系运算符 | > >= < <= |
5 | 相等运算符 | == != === !== |
6 | 逻辑运算符 | 先 && 后 || |
7 | 赋值运算符 | == |
8 | 逗号运算符 | , |
- 一元运算符里面的逻辑非优先级很高
- 逻辑与比逻辑或优先级高
- 如果不能确定优先级,可以使用小括号进行隔离操作
- 案例:
console.log( 4 >= 6 || '人' != '阿凡达' && !(12 * 2 == 144) && true ); // true var num = 10; console.log( 5 == num / 2 && (2 + 2 * num).toString() == '22'); // true var a = 3 > 5 && 2 < 7 && 3 == 4; console.log(a); // false var b = 3 <= 4 || 3 > 1 || 3 != 2; console.log(b); // true var c = 2 === '2'; console.log(c); // false var d = !c || b && a; console.log(d); // true
流程控制与条件分支
语句与代码块
语句
- 前边我们所说的表达式和运算符等内容可以理解成我们一门语句中的单词,短语
- 而语句(statement)就是我们这个语句中一句一句完整的话了。
- 语句是一个程序的基本单位,js的程序就是由一条一条的语句构成的。每一条语句使用;结尾
- js中的语句默认是由上至下顺序执行的。但是我们可以通过一些流程控制语句来控制语句的执行顺序
代码块
- 在JS中可以使用{}来为语句进行分组,同一个{}中的语句我们称为一组语句
- 它们要么都执行,要么都不执行
- 一个{}中的语句我们称为一个代码块,在代码块的后边就不用再编写;了
- JS中的代码块,只具有分组的作用,没有其他的用途
- 代码块中内容,在外面是完全可见的,如下,在代码块中声明变量,外部可以访问到
{ var a = 10; alert("hello"); console.log("你好"); document.write("语句"); } console.log("a = " + a);
流程控制
- 在一个程序执行的过程中,各条代码的执行顺序对程序的结果是有直接影响的。很多时候我们要通过控制代码的执行顺序来实现我们要完成的功能。
- 简单理解:流程控制就是我们来控制我们代码的按照什么结构顺序来执行
- 流程控制主要有三种结构,分别是顺序结构、分支结构和循环结构,这三种结构代表三种代码执行顺序。
- 顺序结构是程序中最简单、最基本的流程控制,它没有特定的语句结构,程序会按照代码的先后顺序,依次执行,程序中大多数的代码都是这样执行的。
分支结构
- 由上到下执行代码的过程中,根据不同的条件,执行不同的代码(执行代码多选一的过程),从而得到不同的结果
- JS中提供了两种分支结构语句,分别为if语句和switch语句
if分支语句
- 语法1
if (条件表达式) { 执行语句 }
- 执行思路:if语句在执行时,会先对条件表达式进行求值判断,如果条件表达式的值为true,则执行if后的语句,如果条件表达式的值为false,则不会执行if后的语句
// 条件不成立,alert语句不会执行 if (3 > 5) { alert("三大于五"); } // 可以写在一行,且不需要大括号 if (true) alert("我会出来的");
- if语句只能控制紧随其后的那个语句
- 如果希望if语句可以控制多条语句,可以将这些语句统一放到代码块中
- if语句后的代码块不是必须的,如只有一条语句时,if就可以自己控制,但是在开发中都写上,结构完整。
- 语句2
if (条件表达式) { //语句1 } else { //语句2 }
- 执行思路:当该语句执行时,先对if后面的条件表达式进行求值判断,如果该值为true,则执行if后面的语句,如果该值为false,则执行else后面的语句
var age = prompt("请输入你的 年龄:"); if ( age >= 18 ) { alert('我想带你去网吧玩游戏'); } else { alert('滚回去写作业去'); } var year = prompt("请输入年份"); if ( year % 4 == 0 && year % 100 !=0 || year % 400 ==0 ) { alert(year + "是闰年"); } else { alert(year + "是平年"); } // 使用三元表达式与if。。else对比 // 键盘输入数字,如果是小于10的数字,在前面补0,如01、02等 var time = prompt("请输入一个0~59之间的数字"); var result = time < 10 ? 0 + time : time; alert(result);
- if里面的语句1和else里面的语句2,最终只能执行一个语句
- 语句3
if (条件表达式) { //语句1 } else if (条件表达式) { //语句2 } else { //语句3 }
- 执行思路:当该语句执行时,会从上到下依次对条件表达式进行求值判断,如果值为true,则执行当前语句,如果值为false,则继续向下判断,如果所有的条件都不满足,则执行最后的else语句
- 多分支语句还是多选一,最后只能有一个语句执行
- else if里面的条件理论上是可以任意多个的
- 最后一个else可以不写
- else if中间有个空格,不写会报错
var score = prompt("请输入分数:"); if (score == 100 ) { alert("奖励一辆车"); } else if (score > 80) { alert("奖励一台手机"); } else if (score > 60) { alert("奖励一本书"); } else { alert("打一顿"); }
switch分支语句
- 语法结构
switch语句也是多分支语句,它用于基于不同的条件来执行不同的代码。当要针对变量设置一系列的特定值的选项时,就可以使用switch。
- 语法
switch(表达式) { case 值1: 执行语句1; break; case 值2: 执行语句2; break; case 值3: 执行语句4; break; default: 执行最后的语句; }
- 执行思路:
在执行时会依次将case后的表达式的值和switch后的表达式的值进行全等比较,也就是===比较。
如果比较结果为true,则从当前case处开始执行代码。
当前case后的所有代码都会执行,我们可以在case的后边跟着一个break关键字,这样可以确保只会执行当前case后的语句,而不会执行其他的case,break表示退出当前switch语句。
如果比较的结果为false,则继续向下比较
如果所有的case的比较结果都为false,则只执行default后的语句,相当于if语句最后一个else,可以不写。
如果有default,则后面的的break一般不用写 - 示例:
// 接受键盘输入的值,如果是1 ~ 7则输出对应的星期数,否则输出非法字符 // 需要注意键盘输入的都是String类型的,需要转为number,可以在prompt前加一个正号 var day = +prompt("请输入一个数字:"); switch (day) { 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("非法的字符输入"); } // 给定一个分值,如果是大于60分,则输出合格,否则输出不合格 var score = 65; // 对给定的分数除以10,再取整,合并多个相同的情况,如果6到10 switch(parseInt(score/10)) { case 6: case 7: case 8: case 9: case 10: console.log("合格"); break; default: console.log("不合格"); } // 对于上面的案例,可以用下面这种方式,更加简洁 var socre = 80; switch (true) { case score >= 60: sonsole.log("合格"); break; default: console.log("不合格"); }
switch语句和if else if语句的区别
- 一般情况下,它们两个语句可以互相替换
- switch…case语句通常处理case为比较确定值的情况,而if…else…语句更加灵活,常用于范围判断
- switch语句进行条件判断后直接执行到程序的条件语句,效率更高,而if…else语句有几种条件,就得判断几次
- 当分支比较少时,if…else语句的执行效率比switch语句高
- 当分支比较多时,switch语句的执行效率比较高,而且结构更清晰
循环结构
在程序中,一组被重复执行的语句被称之为循环体,能否继续重复执行,取决于循环的终止条件。
由循环体及循环的终止条件组成的语句,被称之为循环语句。
while循环
while语句可以在条件表达式为真的前提下,循环执行指定的一段代码,直到表达式不为真时结束循环。
- while循环语法
while (条件表达式) { // 循环体代码 }
- 执行思路
先执行条件表达式,如果结果为true,则执行循环体代码;如果为false,则退出循环,执行后面的代码
执行循环体代码
循环体代码执行完毕后,程序会继续判断执行条件表达式,如果条件依然为true,则会继续执行循环体,知道循环条件为false时,整个循环过程才会结束
如果条件恒为true,则该循环就是一个死循环 - 代码示例
var i = 0; while (i < 10) { alert(i); i++; }
do…while循环
do…while语句其实是while语句的一个变体。该循环会先执行一次循环体,然后对条件表达式进行判断,如果条件为真,就会重复执行循环体,否则退出循环。
- 语法结构
do { //循环体 } while(条件表达式);
- 执行思路
跟while不同的地方在于do while先执行一次循环体,再进行条件表达式判断
如果条件表达式结果为真,则继续执行循环体,否则退出循环。 - 代码示例
// 加入投资的年利率为5%,1000增到5000需要几年 var year = 0; var mouny = 1000; do { mouny*=1.05; year++; } while (mouny<5000); alert("需要"+year+"年才能增长到5000");
- do…while和while 区别
实际上这两个语句功能类似,不同的是while是先判断后执行,do while是先执行后判断
do while可以保证循环体至少执行一次,而while不能
for循环
for循环主要用于把某些代码执行若干次,通常跟计数有关
- 语法结构
for (初始化变量;条件表达式;更新表达式) { // 循环体 }
- 执行思路
执行初始化表达式,初始化变量(初始化表达式只会执行一次)
执行条件表达式,判断是否执行循环体,如果为true,则执行循环体,如果为false,终止循环
执行循环体
执行更新表达式,更新变量,更新表达式完毕继续执行条件表达式 - 代码示例
for ( var i = 0 ; i < 10 ; i++ ) { document.write(i + '<br />'); }
- 注意:
for 循环中的三个部分都可以省略,也可以写在外面
两个封号不能省略
如果在for循环中不写条件表达式,默认是true,为死循环
如果在for循环中不写任何的表达式,只写两个封号,此时也是一个死循环 - 双重for 循环打印图形
continue和break
- continue
continue关键字用于 立即 跳出本次循环,继续下一次循环
本次循环体中continue之后的代码 就会 少 执行一次 - break
break关键字用于立即跳出整个循环(循环结束)
也可以用于跳出switch语句 - 不能在if中使用break和continue,if嵌套在for中时,写在if中的关键字实际作用用循环。
- break会立即终止离他最近的那个循环,对外层循环没有影响
- 可以为循环语句创建一个label,来标识当前的循环,与java中break锚点类似
数组
相关概念
- 数组:
数组是一个对象,它和我们普通对象的功能类似,用来存储一些数据。
可以说数组就是一组数据的集合,其中的每个数据被称为元素,在数组中可以存放任意类型的元素。数组是一种将一组数据存储在单个变量名下的优雅方式
数组的存储性能比普通对象好,在开发中我们经常使用数组来存储一些数据 - 元素:
数组中的数据我们称为元素。一个数据称为一个元素,与普通对象中的属性值类似
- 索引(下标):
在普通对象中,我们通过对象的属性来访问值,而在数组中,我们通过索引来访问数组中的元素。索引从0开始的整数
创建数组
- 方式一:利用new关键字创建
var 数组名 = new Array(); //创建了一个空的新数组,数组名为arr var arr = new Array(); // 使用typeof检查一个数组时,会返回object console.log(typeof arr); // 使用构造函数创建带有初始值的数组 var arr = new Array(11,22,33,44);
- 方式二:利用数组字面量创建
// 使用数组字面量创建一个空数组,里面可以直接给定元素,任意类型 var 数组名 = []; // 使用数组字面量创建带初始值的数组,个元素之间用逗号隔开 var 数组名 = ['小白','小黑',1,2,true,false,3.14];
- 注意:
// 可以看出,通过字面量创建数组与使用构造函数创建类似 // 如果只有一个元素的情况下,会有不同 var arr1 = [10]; // 创建一个数组,且数组中国只有一个元素10 var arr2 = new Array(10); // 创建一个长度为10的空数组 // 数组中的元素可以是任意类型,包括对象 arr = ['hello',1,true,null,nudefined]; // 数组中也可以存放函数,通过小括号调用 arr = [function(){},function(){}]; arr[1](); // 调用数组中第二个函数
操作数组
- 数组赋值(向数组添加元素)
// 通过索引赋值或者向数组中添加元素 数组名[索引] = 值; arr[0] = 10; arr[1] = 33; arr[2] = 22;
- 获取元素
// 通过下标获取或者读取元素 数组名[索引]; // 输出22 console.log(arr[2]); // 如果获取一个不存在的索引,不会报错,会返回undefined console.log(arr[3]);
- 数组长度
// 通过length属性获取数组的长度 数组.length;获取 // 输出结果为3 console.log(arr.length); //对于连续的数组,使用length会获取到数组的长度(元素的个数) // 连续数组指按照索引依次赋值,非连续数组指跳过某些索引进行赋值,如索引0,1,2,10这样 //对于非连续数组,使用length会获取到数组的长度
- 修改长度
// 输出原数组的长度为4 console.log(arr.length); // 输出数组,元素依次为10,33,22,44 console.log(arr); // 设置数组长度超过原长度 arr.length = 10; // 再次打印数组显示多出的部分会空出来,只是没有元素值而已,获取值会输出undefined console.log(arr);// 10,33,22,44,,,,,, // 设置数组长度小于原数组 arr.length = 2; // 再次打印数组,显示多出部分元素会被删除 console.log(arr);// 10,33
- 向数组最后一个位置添加元素
数组[数组.length] = 值; arr[arr.length] = 60; arr[arr.length] = 70; // 这样会永远往数组最后追加元素
遍历数组
- 遍历:就是把数组中的每个元素从头到尾都访问一次
- 使用for循环进行遍历
var arr = ['张三','李四','王五','赵六','孙七']; for ( var i = 0 ; i < arr.length ; i++ ) { console.log(arr[i]); }
- js还提供了方法用来遍历数组,forEach()方法
这个方法必须是IE9及以上才支持
forEach() 方法需要一个函数作为参数
这个传入的函数,作为回调函数,由浏览器调用
数组中有几个元素,函数会执行几次,每次执行时,浏览器会将遍历到的元素,以实参的形式,传递给函数,我们可以定义形参,来读取这些内容
浏览器会在回调函数中总共传入三个参数第一个参数就是当前正在遍历的元素
第二个参数就是当前正在遍历的元素的索引
第三个参数就是当前正在遍历的数组三个参数可以任意定义
var arr = ['张三','李四','王五','赵六']; // 回调函数中没有定义形参,控制台输入四次abc,可以看出函数被调用了4次 arr.forEach(function (){ console.log('abc'); }); // 控制台输出所有的元素,可以看出第一个参数就是当前遍历的参数 arr.forEach(function(value){ console.log(value); }); // 控制台输出了所有的元素以及对应的索引,第二个参数就是索引 arr.forEach(function(value,index){ console.log(value); console.log(index); }); // 控制台输出了4次所有元素,obj表示arr数组 arr.forEach(function(value,index,obj){ console.log(obj); });
数组方法
- push():向数组末尾添加一个或多个元素,并返回数组的新长度
var arr = ['张三','李四','王五']; arr.push('赵六'); // 向数组后追加一个元素 console.log(arr); //'张三','李四','王五','赵六' var result = arr.push('田七','孙八'); // 向数组后追加多个元素 console.log(arr); //'张三','李四','王五','赵六','田七','孙八' console.log(result); // 6,返回的是新数组的长度
- pop():删除数组最后一个元素,并且返回被删除的元素
var arr = ['张三','李四','王五']; var result = arr.pop(); console.log(arr); //'张三','李四' console.log(resutl); // '王五'
- unshift():向数组开头添加一个或多个元素,并且返回新数组的长度,与push对应
var arr = ['张三','李四','王五']; // 向数组开头添加一个元素 var result = arr.unshift('罗翔'); console.log(arr); // '罗翔','张三','李四','王五' console.log(result); // 4 // 向数组开头添加多个元素 var result = arr.unshift('李白','杜甫','苏轼'); console.log(arr); //'李白','杜甫','苏轼','罗翔','张三','李四','王五' console.log(result); // 7
- shift():删除数组第一个元素,并且返回被删除的元素
var arr = ['张三','李四','王五']; // 删除数组第一个元素 var result = arr.shift(); console.log(arr); //'李四','王五' console.log(result); //'张三'
- join():把数组的所有元素放入一个字符串,元素通过指定的分隔符进行分隔
var arr = ['张三','李四','王五']; var result = arr.join(); // 没有指定分隔符,默认使用逗号分隔 console.log(result); 张三,李四,王五 result = arr.join('|'); // 使用给定的符号作为分隔符 console.log(result); // 张三|李四|王五 // 原数组没有影响 console.log(arr); //'张三','李四','王五' // 通过toString()也可以转为字符串,逗号隔开元素的 console.log(arr.toString());
- reverse():颠倒数组中元素的顺序
var arr = ['张三','李四','王五']; var result = arr.reverse(); // 原数组中元素的顺序会颠倒 console.log(arr); //["王五", "李四", "张三"] // 返回值也是一个颠倒顺序的数组 console.log(result); //["王五", "李四", "张三"]
- sort():对数组元素进行排序,但是默认使用Unicode编码进行排序,即时使用纯数字数组,也是按照Unicode编码进行排序,所以对数字进行排序可能出现错误答案,但是可以指定排序规则
我们可以在sort()添加一个回调函数,来指定排序规则
回调函数中需要定义两个形参
浏览器会分别使用数组中的元素作为实参去调用回调函数
使用哪两个元素不确定,但是肯定是相邻两个元素,第一个参数一定是前面一个元素浏览器会根据回调函数的返回值来决定元素的顺序
如果返回一个大于0的值,则元素会交换位置
如果返回一个小于0的值,则元素位置不变
如果返回一个0,则认为两个元素相等,也不交换位置var arr = [3,5,1,8,2,7,6,9]; var result = arr.sort(); // 元素数组中元素顺序会按照从小到大排列 console.log(arr); //[1, 2, 3, 5, 6, 7, 8, 9] // 返回值类型也是一个排好序的数组 console.log(result); //[1, 2, 3, 5, 6, 7, 8, 9] var brr = [1,5,3,11,2,4]; brr.sort(); // 即时纯数字,依然使用Unicode排序,所以,11在最前面 console.log(brr);// [11,1,2,3,4,5] // 通过制定排序规则实现排序 brr.sort(function (a,b){ return a-b; }); console.log(brr);//[1,2,3,4,5,11]
- slice():从某个已有的数组返回选定的元素,从数组提取特定元素,需要两个参数,第一个参数是开始元素的索引,第二个参数表示结束元素的索引,该方法不会改变原数组,会返回新数组
var arr = ['张三','李四','王五','赵六']; // 截取0~2下标的元素,包含起始索引,不包含结束索引。返回到新数组中 var result = arr.slice(0,2); // 原数组没有影响:['张三','李四','王五','赵六'] console.log(arr); // 截取出两个元素,组成新数组:['张三','李四'] console.log(result); // 第二个参数可以不写 result = arr.slice(1); // 表示从给定下标到结束:['李四','王五','赵六'] console.log(result); // 索引可以传一个负数,表示从后往前数,-1表示最后一个,-2表示倒数第二个 result = arr.slice(0,-2); // 截取下标为1至倒数第二个元素:['张三','李四'] console.log(result);
- splice():删除元素,并行数组添加新元素,删除指定的元素,两个参数,第一个参数表示需要删除元素的起始索引,第二个参数表示要删除元素的个数,该方法会修改原数组,并且将被删除的元素作为新数组返回,第三个及之后的参数,作为元素,被插入到起始索引的前面
var arr = ['张三','李四','王五','赵六']; // 删除从1开始的两个元素 var result = arr.splice(1,2); // 原数组已被修改:['张三','赵六'] console.log(arr); // 被删除元素组建新数组:['李四','王五'] console.log(result); // 删除从1开始的1个元素,并且在被删除元素前添加两个元素 var brr = ['张三','李四','王五','赵六']; result = brr.splice(1,1,'张飞','赵云'); // 下标为1的一个元素被删除,且在前面添加两个元素 console.log(brr); // ['张三','张飞','赵云','王五','赵六']
- concat():对两个或多个数组进行合并,且不会对原数组产生影响
var arr = ['张三','李四','王五','赵六','孙七']; var brr = ['关羽','张飞','赵云','马超','黄忠']; // 合并两个或多个数组,也可以传入元素,也会同时合并 var result = concat(arr,brr,'李云龙','楚云飞'); // ['张三','李四','王五','赵六','孙七'] console.log(arr); // ['关羽','张飞','赵云','马超','黄忠'] console.log(brr); // ['张三','李四','王五','赵六','孙七','关羽','张飞','赵云','马超','黄忠','李云龙','楚云飞'] console.log(result);
- 查看是否为数组
var arr = new Array(); var obj = new Object(); // 方式一:利用instanceof操作符 console.log(arr instanceof Array); // true // 方式二:利用Array里面的方法,H5新增 console.log(Array.isArray(obj)); // false
- 数组索引的方法
var arr = ['RED','BLUE','GREEN']; // 返回元素在数组中的索引,只返回第一个满足条件的元素的索引,如果没有满足条件的元素,返回-1 console.log(arr.indexOf('GREEN')); // 2 // 道理同上,从后往前找,类似找到满足条件的最后一个元素的下标 console.log(arr.lastIndexOf('BlUE')); // 1
数组排序
- 冒泡排序:
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把它们交换过来,走访数列个过程是重复进行直到没有再需要交换,也就是说该数列已经排序完成。
这个算法的名字由来是因为越小的元素会经由交换慢慢浮到数列顶端 - 代码示例:
var arr = [5,3,4,8,6,2,1]; for (var i = 0; i < arr.length - 1; i++) { for (var j = 0; j < arr.length - i - 1; j++) { if (arr[j] > arr[j+1]) { var temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } console.log(arr);
函数与作用域
函数
概念:
在JS里面,可能会定义非常多的相同代码或者功能相似的代码,这些代码可能需要大量重复使用
虽然for循环也能实现一些简单重复操作,但是比较具有局限性,此时我们可以使用JS中的函数
函数就是封装了一段可被重复调用执行的代码块。通过此代码块可以实现大量代码的重复使用
函数也是一个对象,普通对象就是一个容器,可以存放一些数据,函数可以存放一些功能或逻辑
使用typeof检查一个函数对象的类型,会返回function
使用
- 声明函数
使用构造方法声明函数
// 函数本身就是一个对象,所以可以使用构造方法声明 var fun = new Function(); // 会打印函数结构 console.log(fun); // 输出function console.log(typeof fun); // 函数中需要封装的代码,以字符串的形式,传入构造中 var fun = new Function("console.log('这是我的第一个函数')");
使用函数声明创建函数
// 语法 function 函数名 (形参1,形参2) { 语句。。。 } // 示例 function fun() { console.log("这是我的第二个函数"); }
使用函数表达式创建一个函数,实际就是创建了一个匿名函数,但是单独创建匿名函数没有意义,所以给一个变量接收该函数
// 语法 var 变量名 = function(参数) { 语句。。。 } // 示例,这里需要注意,fun不是函数名,是变量名,改函数是一个匿名函数 var fun = function () { console.log("我是一个用函数表达式声明的匿名函数"); }
- 调用函数(函数名跟上小括号)
对应构造方法的方式调用
// 构造方法声明的函数是一个对象,调用时直接跟上小括号即可 fun();// 前面什么的fun函数会被调用
函数声明方式的调用
fun();//函数被调用
匿名函数的调用(对象跟上括号)
fun(); //函数被调用
- 函数封装
函数封装是把一个或者多个功能通过函数的方式封装起来,对外只提供一个简单的函数接口
- 代码示例
/*利用函数求1~100 的值*/ // 函数声明的方式 function fun1() { var num = 0; for (var i = 1; i <= 100; i++) { num+=i; } console.log(num); } fun1(); // 函数表达式的方式 var fun2 = function () { var num = 0; for (var i = 1; i <= 100; i++) { num+=i; } console.log(num); } fun2();
- 注意:
1.函数在使用时分为两步:声明函数和调用函数
2.函数不调用,自己不执行,调用一般是函数名加小括号
3.function声明函数的关键字,全部小写
4.函数是做某件事的,函数名一般是动词
函数的参数
在声明函数时,可以在函数名后面的小括号中添加一些参数,这些参数被称为形参,而在调用该函数时,同样也需要传递相应的参数,这些参数被称为实参,形参可以定义任意个
参数 | 说明 |
---|---|
形参 | 形式上的参数,函数定义的时候传递的参数,当前不知道具体值 |
实参 | 实际上的参数,函数调用的时候传递的参数,实际是传给形参的 |
参数的作用:就是在函数内部某些值不能固定,我们可以通过参数在调用函数时传递不同的值进去
- 代码示例
// 求两数的和 function fun(a,b) { console.log(a+b); } // 正常调用 fun(4,8); //输出12 // 无参调用 fun(); //输出NaN // 字符串调用 fun('hello','world'); // 输出helloworld // 多参数调用 fun(10,15,'abc',true); // 输出25 // 少参数调用 fun(6); // 输出NaN
- 注意
1.如果定义函数时声明了形参,而在调用时没有实参,则会给形参赋值undefined。
2.调用函数时解析器不会检查实参的类型,可以传入任意类型,所以要注意是否会接受到非法参数类型
3.调用函数时解析器也不会检查实参的数量,多余的实参不会被赋值使用,如果实参的数量少于形参数量。则没有对应实参的形参一律为undefined
4.函数的实参可以是任意数据类型 - 特殊参数类型
普通对象作为参数
// 当函数的参数个数过多时,我们可以将其封装到一个对象中 function fun(o) { console.log("我是"+o.name+",今年我"+o.age+"岁了,我是一个"+o.gender+"人,我家住在"+o.adress); } // 声明一个对象 var obj = { name:"赵子龙", age:18, gender:"男", address:"常山" }; // 调用函数,将声明好的对象传入 fun(obj);
函数对象作为参数
function fun1() { alert("我是函数1"); } function fun2(a) { // 控制台输出a,当传入一个函数时,输出函数结构 console.log(a); // 弹出提示文字 alert("我是函数2"); // 调用传入的函数 a(); } // 调用函数2,传入函数1的对象,注意函数1后面没有括号,不是调用,而是函数对象 fun2(fun1); // 调用函数2,传入函数1的执行结果,注意函数1后面有括号,所以是调用函数1运行的结果作为参数 fun2(fun1()); // 也可以传入一个匿名函数 fun2(function(){alert("我是你们函数");});
函数的返回值
- 概念:
有时候,我们希望函数将运行的结果返回给调用者,此时可以通过使用return语句就可以实现
- 注意:
return后面的值将会作为函数的执行结果返回
调用者可以定义一个变量,来接受该结果
在函数中,return后的语句不会执行
如果return后面没有值,相当于返回一个undefined,如果函数中没有return,则也回返回undefined
函数的返回值可以是任意类型
return只能返回一个值,如果以逗号隔开多个值,以最后一个为准 - 特殊返回值类型
普通对象作为返回值
// 声明一个函数 function fun() { // 声明一个对象 var obj = {name:"李云龙"}; // 将对象作为函数的返回值返回 return obj; } // 调用函数,用变量接收函数的返回值 var a = fun(); // 控制台打印对象的属性值 console.log(a.name); // 也可以直接返回不用声明对象 function fun() { // 直接返回一个对象 return {name,"楚云飞"}; }
函数作为返回值
function fun1() { // 在函数内部再声明一个函数 function fun2() { alert("我是fun2"); } // 调用fun2函数 fun2(); } // 调用fun1函数 fun1(); // 将函数对象作为返回值 function fun3() { // 在函数fun3内部声明fun4函数 function fun4() { alert("我是fun4"); } // 将函数fun4返回,后面没有括号,如果有括号,返回的就是函数fun4的运行结果 return fun4; } // 调用函数fun3,接收返回值 var fun = fun3(); // 打印返回值控制台输出函数结构 console.log(fun); // 直接调用fun3的返回值,内部的fun4被执行 fun(); // 对应上面的情况,可以看出,调用fun3之后需要一个变量接收,可以不用接收,直接调用结果 fun3()(); // 调用fun3函数得到的结果继续调用,与上面的写法一样
立即执行函数
- 函数可以声明一个匿名函数,且用一个变量接收
// 声明一个匿名函数 var fun = function() { alert("我是一个匿名函数"); } // 调用这个你们函数,变量名加上括号 fun();
- 对于你们函数,如果单独声明,且不用变量接收,会报错
// 这样的写法是错误的,会报错,它会认为后面的大括号是一个整体,前面的function跟小括号就不认识了 function() { alert("我是一个匿名函数"); }
- 针对某些情况下,我们声明的函数只会调用一次,但是用第一种方式,用变量接收麻烦,可以用立即执行函数
// 将声明好的函数用一个小括号括起来,就是一个单独声明的你们函数了,但是没有被调用,不会执行 (function() { alert("我是一个匿名函数"); })
- 对应上面的情况,我们可以单独声明一个匿名函数,但是没有调用,对于函数的调用使用变量名加上小括号,所以只需要后面跟上小括号即可,如果函数需要参数,在后面括号中传入即可
// 无参立即执行函数 (function() { alert("我是一个立即执行函数"); })(); // 有参立即执行函数 (function(a,b) { console.log(a+b); })(4,5);
函数的方法
- call():是函数对象的方法,需要通过函数对象来调用,单独使用时与直接调用函数没有区别
- apply():是函数对象方法,需要通过函数对象来调用,单独使用时与直接调用函数没有区别
- 不同的是当调用call()和apply()时可以将一个对象指定为函数的第一个参数,此时这个指定的对象将会成为函数执行时的this
// 什么一个函数 function fun() { alert("我是fun函数"); } // 正常调用函数 fun(); // 通过函数对象的call()方法调用 fun.call(); // 通过函数对象的apply()方法调用 fun.apply(); // 上面三种方式都会是函数运行,没有区别 // 但是如果指定一个对象,就是修改this function fun() { console.log(this); } // 声明两个对象,作为call和apply的指定对象 var obj1 = {name:"李云龙"}; var obj2 = {name:"楚云飞"}; // 正常方式调用函数 fun(); // 控制台输出的this对象是window // 调用函数对象的call()方法 fun.call(obj1); // 控制台输出的this对象是object,如果输出this.name可以看到是obj1 // 调用函数对象的apply()方法 fun.apply(obj2);// 控制台输出的this对象是object,如果输出this.name可以看到是obj2
- call()与apply()区别
function fun(a,b) { console.log(this); console.log(a); console.log(b); }; var obj = {name:"张三"}; // 调用call方法,指定this为obj对象,但是还有两个参数,此时可以依次传入 fun.call(obj,5,7); // 控制台输出this为obj,a为5,b为7 // 如果对apply方法使用同样的方法传参,会报错 fun.apply(obj,8,6); // 提示参数中的类型异常,因为apply要求对于函数的参数,必须封装在数组中传入 fun.apply(obj,[8,6]); // 控制台输出this为obj,a为8,b为6
- this的情况
1.以函数的形式调用时,this永远都是window
2.以方法的形式调用时,this是调用方法的对象
3.以构造函数的形式调用时,this是新创建的那个对象
4.使用call()和apply()调用时,this是指定的那个对象
arguments
- 概念:
在调用函数时,浏览器每次都会传递进来两个隐含的参数:
1.第一个是函数的上下文对象,也就是this
2.第二个是封装实参的对象arguments
当我们不确定有多少个实参传递的时候,可以用arguments来获取,在JS中,arguments实际上是当前函数的一个内置对象,所有函数都内置了一个arguments对象。 - 注意:
arguments是一个类数组对象,它也可以通过索引来操作数据,也可以获取长度
在调用函数时,我们所传递的实参都会在arguments中保存,与形参没有任何关系,即时不定义形参
arguments.length属性可以用来获取实参的个数,与是否定义形参无关
我们即时不定义形参,也可以通过arguments来使用实参:arguments[0] 表示第一个实参
arguments[1] 表示第二个实参。。。它里面有一个属性叫做callee,这个属性对应一个函数对象,就是当前正在指向的函数对象
- 代码:
function fun(o) { // 控制台判断arguments是否是数组,输出false,只是类数组对象 console.log(arguments instanceof Array); //false // 通过Array.isArray()方法判断也不是数组 console.log(Array.isArray(arguments)); // false // 通过length属性获取实参个数 console.log(arguments.length); // 2 // 通过索引获取实参的值 console.log(arguments[0]); // 3 console.log(arguments[1]); // abc // 输出callee属性,得到的就是函数对象 console.log(arguments.callee); } fun(3,"abc");
作用域(scope)
概念
- 就是代码变量在某个范围内起作用和效果,也就是变量的作用范围
- 目的是提供代码的可靠性,重要的是减少命名冲突
分类
- 全局作用域:作用于整个script标签或者是一个单独的js文件。
直接写在script中的代码,都是全局作用域
在页面打开时创建,在页面关闭时销毁
在全局作用域中有一个全局对象window,我们可以直接使用它代表的是一个浏览器窗口
它由浏览器创建,我们可以直接使用在全局作用域中:
创建的变量都会作为window对象的属性保存
创建的函数都会作为window对象的方法保存
在全局作用域中声明的变量都是全局变量,在页面的任意部分都可以访问到
变量声明提前:
在js中正常声明一个变量使用var关键字,比如var a=100
如果没有使用关键字,相当于默认用window属性,比如window.a = 100
但是如果在变量声明之前就进行调用,则效果不同:
如果是用var声明的变量,调用会输出undefined,不会报错,因为使用var声明的变量,会在所有代码之前执行,但是只是声明了变量,没有赋值,会在执行到声明赋值时才会赋值,所有不会报错。这就是声明提前
如果没有var关键字,则不会声明提前,如果在声明之前调用,会报错函数声明提前:
使用函数声明形式创建的函数function() 函数名{},它会在所有代码执行之前就被创建,我们可以在函数声明前调用
使用函数表达式声明的函数var fun = function(){}不会被提前,所以不能在声明前调用<script> var a = 10; // 正常打印 console.log(a); // 输出结果10 // 输出window的属性,与直接打印的区别是如果没有定义这个变量,如果直接打印会报错,如果用属性的方式,会输出undefined console.log(window.a); // 也会输出10 console.log(window.b); // 没有定义变量b,但不会报错,输出undefined // 定义一个函数 function fun() { console.log("我是fun函数"); } // 正常调用函数 fun(); // 正常输出结果 // 使用window调用 window.fun(); // 正常输出结果 >/script>
- 局部作用域:在函数内部创建使用,也叫函数作用域
在调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁
没调用一次函数就会创建一个新的 函数作用域,它们之间是独立的
在函数作用域中可以访问到全局作用域中的变量
在全局作用域中不能访问到函数作用域中的变量
当在函数作用域中操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用,如果没有则向上一级作用域中寻找,直到找到全局作用域,如果全局作用域中依然没有找到,则会报错
如果在局部作用域中想直接访问全局变量,可以使用window对象调用
在函数作用域中,也有声明提前的特性,使用var关键字声明变量,会在函数中所有的代码执行之前被声明 - 注意:
在函数内部没有声明直接赋值的变量,也就是没有var关键字的变量,也是全局变量
函数的形参也可以看做是局变量
从执行效率来看:全局变量只有浏览器关闭的时候才会销毁,比较占内存资源
局部变量是当我们程序执行完毕就会销毁,比较节约内存资源
作用域链
- 只要是代码,就至少有一个作用域
- 写在函数内部的局部作用域
- 如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域
- 根据在内部函数可以访问外部函数变量的这种机制,用链式查找决定哪些数据能被内部函数访问,就称为作用域链
- 也就是就近原则
预解析
- JavaScript代码是由浏览器中的JavaScript解析器来执行的。
- JavaScript解析器在运行JavaScript代码的时候分为两步:预解析和代码执行
- 预解析:JavaScript引擎会把js里面所有的var还有function提升到当前作用域最前面
- 代码执行:按照代码的书写顺序从上往下执行
- 预解析分为变量预解析(变量声明提升)和函数预解析(函数声明提升)
- 变量声明提升,就是把所有的变量声明提升到当前作用域的最前面,不提升赋值操作
- 函数提升,就是把所有的函数声明提升到当前作用域的最前面,不提升函数调用
对象
对象简介
JavaScript中的数据类型
- 基本数据类型
String
Number
Boolean
Null
undefined - 引用数据类型
Object对象:以后看到的值,只要不是上面五中基本数据类型,都属于对象
对象概念
- 什么是对象
现实生活中,万物皆对象,对象是一个具体的事物,看得见摸得着的实物
例如一本书,一辆汽车,一个人可以是对象,一个数据库,一张网页,一个远程服务器的连接也可以是对象
在JavaScript中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等 - 对象的组成:由属性和方法组成的
属性:事物的特征,在对象中用属性来表示(常用名词)
方法:事物的行为,在对象中用方法来表示(常用动词)
对象分类
- 内建对象
由ES标准中定义的对象,在任何的ES的实现中都可以使用
比如Math String Number Boolean Function Object等 - 宿主对象
由JS的运行环境提供的对象,目前来讲主要指由浏览器提供的对象
比如BOM对象组合DOM对象组 - 自定义对象
由开发人员自定义创建的对象
对象的使用
对象的创建
- 利用字面量创建对象
对象的字面量:就是花括号{}里面包含了表达这个具体事物(对象)的属性和方法
花括号中写属性或者方法,我们采用的是键值对的形式,键就是属性名,值就是属性值
多个属性或者方法中间用逗号隔开
方法冒号后面跟的是一个匿名函数
对象字面量的属性名可以加引号,也可以不加,建议不加,但是如果要一些特殊的字符,必须加双引号// 利用对象字面量创建一个空对象 var obj = {}; // 利用字面量创建一个带属性和方法的对象 var persion = { name:"李云龙", age:18, sex:'男', sayHi:function(){ console.log("Hi~"); }, test:{name:"abc"} }
- 利用new Object创建对象
// 利用new Object创建一个空对象 var obj = new Object(); // 给obj对象添加属性 obj.name = "楚云飞"; obj.age = '男'; // 给obj对象添加方法 obj.sayName = funtion () { console.log(obj.name); };
- 利用构造函数创建对象
属性的使用
- 添加属性:相当于直接赋值,会自动添加属性
// 方式一:语法为:对象.属性名 = 属性值 obj.name = "楚云飞"; obj.age = 18; // 如果没有该属性,就添加,如果有,则修改值 // 方式二:语法为对象['属性名'] = 属性值 obj['nihao'] = '你好';
- 删除属性:固定语法,delete语句删除
// 语法:delete 对象.属性名 delete obj.name; // 删除对象obj的name属性
- 修改属性:与属性赋值一样,相当于重新赋新值
// 语法与添加属性一样:对象.属性名 = 新值 obj.name = 'tom'; obj.age = 35;
- 获取属性:如果读取对象中没有的属性,不会报错,而是返回undefined
// 方式一:对象名.属性名 console.log(obj.name); // 获取obj对象的name属性并打印 // 方式二:对象名['属性名'],属性名必须加引号 console.log(obj['age']); // 获取obj对象的age属性并打印
属性名和属性值
- 属性名
对象的属性名不强制要求遵循标识符发规范
什么乱七八糟的名字都可以使用
但是我们使用时还是尽量遵循标识符规范规则
对于使用特殊的属性名的,可以使用方括号的方式进行
实际上方括号中就是一个字符串,所以可以用一个变量代替,修改变量的值,相当于修改属性名,更加灵活var obj = new Object(); obj.name = "山本"; // 正常起名,没有问题 console.log(obj.name); // 正常的属性名没有问题 obj.var = 15; // 上面说了可以使用任意名字,关键字也可以 console.log(obj.var); // 可以正常打印属性值,没有问题 // 如果是一些特殊名字,可能会报错 obj.123 = "abc"; console.log(obj.123); // 使用123这种特殊名字,会报错 /* 如果要使用特殊的属性名,不能采用对象.属性名的方式,需要另外一种 */ obj['123'] = 456; // 使用方括号的方式,没有问题 console.log(obj['123']); // 获取属性时也必须使用同样的方式,不能使用点的方式 /* 使用[]的方式操作属性,更加灵活,在[]找那个可以直接传递一个变量,通过修改变量值可以访问不同属性 */ var n = "123"; // 定义一个变量,变量值可以任意修改,达到访问任意属性的目的 obj[n] = 369; // 通过变量修改属性值 console.log(obj[n]); // 通过变量获取属性值
- 属性值
JS对象的属性值,可以是任意的数据类型,也可以是一个对象
var obj = new Object(); obj.test = true; obj.test = null; obj.test = undefined; obj.test = 123; obj.test = "abc"; consoel.log(obj.test); var obj2 = new Object(); obj2.name = "丁伟"; // 将obj2对象设置为obj的属性 obj.ob = obj2; console.log(obj); console.log(obj.ob); console.log(obj.ob.name);
- in 运算符
通过该运算符可以检查一个对象中是否含有指定的属性
如果有则返回true,没有则返回false
语法:“属性名” in 对象// 很多时候,我们使用的对象并不是我们自己创建的,这时候要想获取属性,就需要先判断有没有该属性 console.log("test" in obj); // 检查obj对象是否含有test属性并打印检查结果 console.log("name" in obj); // 检查obj对象是否含有name属性并打印检查结果 console.log("age" in obj); // 检查obj对象是否含有age属性并打印检查结果
- 枚举对象属性:使用for in语句
语法:
for(var 变量 in 对象) {
}
对象有几个属性,循环体执行几次,每次执行时,会将对象中的属性的名字赋值该变量var obj = { name:"赵云", age:28, gender:'男', address:"常山" } for (var n in obj) { console.log("属性名:"+n); console.log("属性值:"+obj[n]); }
基本概念
基本和引用数据类型
JS中的变量都是保存到栈内存中的:
- 基本数据类型:基本数据类型的值是直接在栈内存中存储的,值与值之间是独立存在,修改一个变量不会影响其他变量。当两个基本数据类型的变量比较时,比较的就是两个基本数据类型的值
- 引用数据类型:对象是保存到堆内存中的,每创建一个新的对象,就会在堆内存中开辟出一个新的空间,而变量保存的是对象在内存中的地址(对象的引用),如果两个变量保存的是同一个对象的引用,当一个通过一个变量修改属性时,另一个也会受到影响。当两个变量进行比时,比较的是两个对象的地址,如果两个对象是一模一样的,但是地址不同,会返回false,除非就是同一个对象
变量、属性、函数和方法
- 变量和属性
相同点:都是用来存储数据的
不同点:变量:单独声明并赋值,使用的时候直接写变量名,单独存在
属性:在对象里面的不需要声明的,使用的时候必须是对象调用 - 函数和方法
相同点:都是实现某种功能,做某件事
不同点:函数:单独声明,并且调用的时候用函数名()的方式,单独存在
方法:在对象里面,调用的时候用对象.方法()的方式
构造函数
前面我们看到的两种创建对象的方式,每次都只能创建一个对象,当我们想创建类似的多个对象时,则会出现大量重复代码,所以需要批量创建对象,就需要用工厂或者构造函数来实现
利用工厂创建对象
function createObj(name,age,gender) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.gender = gender;
obj.sayHello = function(){
alert("hello");
}
return obj;
}
var obj1 = createObj("李逵",37,"男");
var obj2 = createObj("李鬼",35,"男");
console.log(obj1);
console.log(obj2);
发现这种方式可以实现批量创建对象,也就是对于创建对象封装了函数
但是问题也会有,当我们需要创建一个狗的对象时,需要另外封装一个对应的函数
而当我们利用两个函数创建对象后会发现,两个对象都是Object类型的
原因在于我们利用的都是new Object()创建的对象,所以都是Object类型的
我们如果希望,创建的人就是Person类型,而狗就是Dog类型的,那就需要构造函数
构造函数
- 概念:
是一种特殊的函数,创建方法和普通函数没有区别
主要用来初始化对象,即为对象成员变量赋初始值
它总与new运算符一起使用
我们可以把对象中的一些公共的属性和方法抽象出来,然后封装到这个函数中 - 语法:
// 定义构造函数 function 构造函数名() { this.属性 = 值; this.方法 = function(){} } // 使用构造函数 new 构造函数名();
- 注意:
1.构造函数名字首字母要大写
2.构造函数不需要return就可以返回结果
3.我们调用构造函数,必须使用new,普通函数可以直接调用
4.如果没有new,直接调用构造函数,它就作为一个普通函数
5.只要我们new了一个构造函数,就会创建一个对象
6.我们的属性和方法前面必须添加this
7.使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类
8.我们将通过一个构造函数创建的对象,称为该类的实例 - 构造函数的执行流程
1.立刻创建一个新的对象
2.将新建的对象设置为函数中的this
3.逐行执行函数中的代码
4.将新建的对象作为返回值返回 - 代码:
function Person() { this.name = name; this.age = age; this.gender = gender; this.sayName = function() { alert(this.name); } } var per = new Person("李云龙",37,"男"); console.log(per); per.sayName();
- instanceof运算符
使用instanceof可以检查一个对象是否是一个类的实例
语法:对象 instanceof 构造函数
如果是,则返回true,否则返回false
所有的对象都是Object的后代,所以任何对象和Object做instanceof检查时都会返回trueconsole.log(per instanceof Person);
- 问题:
// 创建一个构造函数 function Person() { this.name = name; this.age = age; this.gender = gender; this.sayName = function() { alert(this.name); } } // 利用创建好的构造函数创建一个对象 var per1 = new Person("李云龙",37,"男"); console.log(per); per.sayName(); // 再创建一个对象 var per2 = new Person("楚云飞",39,"男"); console.log(per); per.sayName(); // 此时会发现每次调用一次构造,就会创建一个sayName方法 console.log(per1.sayName==per2.sayName);// 返回false // 两个对象的方法对象不想等,表示每个对象都是独立的方法,这显然是没有必要的。 // 完全可以所有的对象共享同一个方法 // 将方法提取到全局中,在全局中定义后再赋值给构造中的属性 function fun () { alert(this.name); } function Person(name,age,gender) { this.name = name; this.age = age; this.gender = gender; // 将全局函数赋值给当前对象作为方法,实现所有方法共享同一方法 this.syaName = fun; } /* 但是现在的问题是,将函数定义在全局作用域,污染了全局作用域的命名空间 而且定义在全局作用域中也很不安全 */
其他概念
new的执行过程
- 在内存中创建一个新的空对象
- 让this指向这个新的对象
- 执行构造函数里的代码,给这个新对象添加属性和方法
- 返回这个新对象(所以构造函数里面不需要return)
原型对象
- 概念:
我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype
这个就是原型,这个属性对应着一个对象,就是我们所谓的原型对象
每个函数都对应一个原型,属于自己的原型,不同的函数的原型对象也不相等
如果函数作为普通函数调用prototype没有任何作用
当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性,指向的就是该构造函数的原型对象。我们可以通过__proto__来访问该属性,前后各两个下划线
通过下面的代码可以看出,原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象
我们可以将对象中共有的内容,统一设置到原型对象中
当我们访问对象的一个属性或者方法时,会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用
以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中去,这样不用分别为每一个对象添加,也不会影响到全局作用域。就可以使每个对象都具有这些属性和方法了 - 代码:
// 定义一个函数 function MyClass() { } // 可以获取函数对象的原型,查看是一个object console.log(MyClass.prototype); // 使用new调用函数(构造函数) var mc = new MyClass(); // 访问mc的隐含属性(原型对象属性) console.log(mc.__proto__); // 判断发现mc对象的属性原型对象就是构造函数的原型对象 console.log(mc.__proto__ == MyClass.prototype); // 返回true // // // 向原型对象中添加一个属性 MyClass.prototype.a = 123; // 通过mc对象访问a属性可以访问到 console.log(mc.a); // // // 向MyClass的原型中添加sayName方法 MyClass.prototype.sayName = function () { alert(this.name); } // mc对象调用方法 mc.sayName();
- 原型链初识:
/* * 创建一个构造函数 */ function MyClass() { } // 向MyClass的原型中添加一个name属性 MyClass.prototype.name = "我是原型中的名字"; // 通过构造函数创建一个对象 var mc = new MyClass(); // 打印name属性正常显示 console.log(mc.name); // 使用in检查对象中是否含有某个属性时,如果对象中没有,但是原型中有,也会返回true console.log("name" in mc); // true // 可以使用对象的hasOwnProperty() 方法来检查对象自身中是否含有该属性 // 使用该方法只有当对象自身中含有属性时,才会返回true console.log(mc.hasOwnProperty("name"); // false // 但是上面这个方法我们没有定义,如果用该方法检查自己是否存在,会返回false console.log(mc.hasOwnProperty("hasOwnProperty")); //false // 此时我们猜测该方法是我们对象的原型中的,但是原型中我们也没有添加,我们可以检查一下 console.log(mc.__proto__.hasOwnProperty("hasOwnProperty")); // false // 通过上面的检查我们看出,mc的原型中也没有该方法 /* * 原型对象也是对象,所以它也有原型, * 当我们使用一个对象的属性或者方法时,会先在自身中寻找, * 自身中如果有,则直接使用, * 如果没有则去原型对象中寻找,如果原型对象中有,则直接使用, * 如果没有则去原型的原型中寻找,直到找到Object对象的原型, * Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回undefined */ // 检查mc的原型的原型中有么有hasOwnProperty方法,发现存在 console.log(mc.__proto__.__proto__.hasOwnProperty("hasOwnProperty")); //true
- toString():
// 创建Person构造方法 function Person(name,age,gender) { this.name = name; this.age = age; this.gender = gender; } // 创建一个Person实例 var per = new Person("段鹏",21,"男"); // 当我们直接在页面中打印一个对象时,实际上是输出对象的toString()方法的返回值 console.log(per); //[object Object] // 查看得到toString() 方法就是Object的原型方法 console.log(per.__proto__.__proto__.hasOwnProperty("toString")); //true // 如果我们希望在输出对象时不输出[object Object],可以为对象添加一个toString()方法 per.toString = function() { return "我是重写之后的toString"; } // 再次打印对象,会发现就是重新之后的toString()方法体 console.log(per); // 我是重写之后的toString // 会发现上面的方式只是给per对象添加了toString,如果是一个新对象,调用的还是object的toString方法 // 所以我们想要给一个类重写toString,最好就是添加给原型对象 Person.prototype.toString = function (){ return "Person [name="+this.name+",age="+this.age+",gender="+this.gender+"]"; } // 这样所有Person的实例在打印时都会调用重写之后的toString方法
垃圾回收
就像人生活的时间长了会产生垃圾一样,程序运行过程中也会产生垃圾
这些垃圾积攒过多以后,会导致程序运行的速度过慢
所以我们需要一个垃圾回收机制,来处理程序运行过程中产生的垃圾当一个对象没有任何的变量或属性对它进行引用,此时我们将永远无法操作该对象,
此时这种对象就是一个垃圾,这种对象过多会占用大量的内存空间,导致程序运行变慢
所以这种垃圾必须进行清理在JS中拥有自动的垃圾回收机制,会自动将这些垃圾对象从内存中销毁
我们不需要也不能进行垃圾回收的操作
我们需要做的只是将不再使用的对象设置null即可
内置对象及正则表达式
- JavaScript中的对象分为3种对象:自定义对象、内置对象和浏览器对象
- 前面两种对象是JS基础内容,属于ECMAScript;第三个浏览器对象属于我们JS独有的
- 内置对象就是指JS语言自带的一些对象,这些对象供开发者使用,并提供了一些常用的或是最基本而必要的功能(属性和方法)
- JavaScript提供了多个内置对象,常用的有:Math、Date、Array、String等
- 学习一个内置对象的使用,只要学会其常用成员的使用即可,我们可以通过查文档学习,也可以通过MDN/W3C来查询。
- Mozila开发者网络(MDN)提供了有关开放网络技术(OPEN Web)的信息,包括HTML、CSS和万维网及HTML5应用的API。
- MDN网站入口
Date
概念:
Date()日期对象是一个构造函数
必须使用new来调用
创建日期对象实例进行操作
常用方法
// 创建一个Date对象
// 如果直接使用构造函数创建一个Date对象,则会封装为当前代码执行的时间
var d = new Date();
console.log(d);
/*
* 创建一个指定的时间对象
* 需要在构造函数中传递一个表示时间的字符串作为参数
* 日期的格式1:月份/日期/年份 时:分:秒
* 日期的格式2:年-月-日 时:分:秒
*/
var d2 = new Date("04/26/2021 14:36:28");
console.log(d2);
/*
* getDate()
* - 获取当前日期对象是几日
*/
var date = d2.getDate();
console.log(date); // 26 指26号
/*
* getDay()
* - 获取当前日期对象是周几
* - 会返回0~6的值,0表示周日
*/
var day = d2.getDay();
console.log(day); // 1 指周一
/*
* getMonth()
* - 获取当前时间对象的月份
* - 会返回0~11的值,11表示12月
*/
var month = d2.getMonth();
console.log(month); // 3 指四月
/*
* getFullYear()
* - 获取当前时间对象的年份
* - 四位数表示的年份,完整的表示
* - getYear()会返回简写年份,已经弃用
*/
var year = d2.getFullYear();
console.log(year); // 2021
/*
* getHours()
* - 获取时间日期对象的小时
* - 会返回0~23的值
* getMinutes()
* - 获取时间日期对象的分钟
* - 会返回0~59的值
* getSeconds()
* - 获取时间日期对象的秒数
* - 会返回0~59的值
* getMilliseconds()
* - 获取时间日期对象的毫秒数
* - 会返回0~999的值
*/
var hours = d2.getHours();
console.log(hours); // 14
var minutes = d2.getMinutes();
console.log(minutes); // 36
var sceonds = d2.getSeconds();
console.log(sceonds); // 28
var milliseconds = d2.getMilliseconds();
console.log(milliseconds); // 0
/*
* getTime()
* - 获取当前时间日期对象的时间戳
* - 时间戳,指的是从格林威治标准时间的1970年1月1日,0时0分0秒到当前日期时间话费的毫秒数
* - 计算机底层再保存时间时使用的都是时间戳
* valueOf()
* - 获取当前时间日期对象的时间戳
*/
var time = d2.getTime();
console.log(time);
var time = d2.valueOf();
console.log(time);
var d3 = new Date("1/1/1970 0:0:0");
time = d3.getTime();
// 这里不是0,原因是我们写入以后计算机按照系统,时间算 是北京时间,而不是格林威治标准时间,相差8小时
console.log(time); // -28800000
/*
* Date.now()
* - 直接通过构造函数对象获取当前时间戳
* - H5新增,IE9以上支持
* +new Date()
* - 直接返回当前时间戳
*/
time = Date.now();
console.log(time);
time = +new Date();
console.log(time);
Math
概念:
Math和其他的对象不同,它不是一个构造函数,所以不需要通过new来创建对象
它属于一个工具类,里面封装了数学运算相关的属性和方法
直接使用里面的属性和方法即可
常量属性
// 返回圆周率
console.log(Math.PI);
// 返回自然对数的底数
console.log(Math.E);
// 返回2的自然对数
console.log(Math.LN2);
// 返回10的自然对数
console.log(Math.LN10);
// 返回2的平方根
console.log(Math.SORT2);
常用方法
// 返回数的绝对值,如果给的是字符串,则尝试转换,无法转换则为NaN
console.log(Math.abs(-1)); // 1
// 返回天花板数
console.log(Math.ceil(1.1)); // 2
// 返回地板数
console.log(Math.floor(1.9)); // 1
// 返回多个参数中的最大值,如果包含不能转为数字的参数,则直接返回NaN,如果不写参数,返回-infinity
console.log(Math.max(1,96,78)); // 96
// 返回多个参数中的最小值,同上
console.log(Math.min(10,65,12)); // 10
// 四舍五入,唯一不同的是.5,它会向大的一方取,比如1.5会取值2,但是-1.5会取值-1
console.log(Math.round(-1.5)); // -1
// 返回参数1的参数2次幂
console.log(Math.pow(2,3)); // 8
// 求平方根
console.log(Math.sqrt(16)); // 16
/*
* random可以用来生成一个[0,1)的随机数
* 可以利用其生成[0,n]的随机整数:Math.round(Math.random()*n));
* 生成[m,n]的随机数:Math.round(Math.random()*(n-m)+m);
*/
for (var i = 0 ; i < 100 ; i++ ) {
console.log(Math.round(Math.random()*15));
}
String
基本包装类型
- 为了方便操作基本数据类型,JavaScript还提供了三个特殊的引用类型:String、Number和Boolean
- 基本包装类型就是把简单数据类型包装成为复杂数据类型,也就是对象
- 这样基本数据类型就有了属性和方法
- 注意:我们在实际应用中不会使用基本数据类型的对象,如果使用基本数据类型的对象,可能会带来一些不可预期的结果
// 创建一个Boolean类型的对象 var b = new Boolean(false); /* * 运行会发现无论Boolean构造中的参数是true还是false * 下面的if都会执行,也就是说if判断中的b的值始终是true * 原因就是if做判断时,需要将b这个对象转为boolean类型的值 * 而对象只要不为null,转换为boolean之后都为true * 所以需要注意,我们不会使用基本数据类型的对象 * 除了这种情况还有判断等值时,Number类型的两个对象,虽然值相等,但是 * 因为是两个对象,所以使用等号判断的结果为不相等 */ if (b){ alert("我执行了!!!"); }
- 通过上面的实例可以看出,基本数据类型的对象我们一般不会使用,而是JS内部使用,可以理解为java中的自动拆装箱操作
- 方法和属性只能添加给对象,不能添加给基本数据类型
- 当我们对一些基本数据类型的值去调用属性和方法时,浏览器会临时使用包装类将其转换为对象,然后再调用对象的属性和方法
- 浏览器的这种转换是临时的,不会修改变量。所以如果我们使用基本类型调用属性时,我们的变量始终是基本数据类型,只是在调用方法时会有个临时的对象存在
字符串不可变
指的是字符串里面的值不可变,虽然看上去可以改变内容
但其实是地址变了,内存中开辟了新的空间,变量重新指向
所以尽量不要做大量的字符串拼接
字符串常用方法
字符串所有的方法,都不会修改字符串本身(字符串是不可变的),操作完成会返回新的字符串
字符串在底层是以字符数组的形式保存的,所以一些操作方法与数组的类似
- 获取指定位置的字符
String str = "hello world"; /* * charAt() * - 可以返回字符串中指定位置的字符 * - 接收一个参数,作为指定位置 * - 根据索引获取指定的字符,索引从0开始 * 直接通过数组的形式,通过索引获取 */ var result = str.charAt(6); console.log(result); // 'w' result = str[8]; console.log(result); // 'r' /* * charCodeAt() * - 获取指定位置字符的字符编码(Unicode编码) * - A的编码为65,B的为66 */ result = str.charCodeAt(1); console.log(result); // 101 e的编码为101 /* * fromCharCode() * - 根据字符编码获取字符 * - 给定一个Unicode字符的编码 * - 这个方法不是通过String对象调用 * - 该方法通过String构造调用 * - 可以通过Unicode表查看特殊字符的编码获取字符,但是要注意编码表中是16进制 */ result = String.fromCharCode(101); console.log(result); // e result = String.fromCharCode(72); console.log(result); // H // Unicode查询编码为2682的字符,因为是16进制,所以我们可以直接给16进制 result = String.fromCharCode(0x2682);
- 字符串拼接
/* * concat() * - 可以用来连接两个或多个字符串 * - 作用和加号+一样 * - 参数可以为一个或多个 */ String str = "hello"; var result = str.concat(" world"); console.log(result); // hello world result = str.concat(" 你好"," 萨瓦迪卡"); console.log(result); // hello 你好 萨瓦迪卡
- 字符串检索
/* * indexOf() * - 该方法可以检索一个字符串中是否包含指定内容 * - 如果字符串中含有该内容,则会返回其第一次出现的索引 * - 如果没有找到指定的内容,则返回-1 * - 当有两个从参数时,第二个参数表示查找的其实索引,是包含的 */ String str = "hello world"; // 查询str中是否包含e字符 var result = str.indexOf('e'); // 1 // 尽管有多个内容符合,只会返回第一次出现的索引 result = str.indexOf('l'); // 2 // 在str中查找l字符,并且从索引3开始 result = str.indexOf('l',3); // 3 /* * lastIndexOf() * - 该方法的用法和indexOf一样 * - 不同的是indexOf是从前往后找 * - lastIndexOf是从后往前找 * - 也可以指定第二个参数,作为开始查找的起始位置 */ result = str.lastIndexOf('l'); console.log(result); // 9
- 字符串截取
/* * slice() * - 可以从字符串中截取指定的内容 * - 不会影响原字符串,而是将截取的内容返回 * - 两个参数: * 第一个,开始位置的索引(包括开始位置) * 第二个,结束位置的索引(不包括结束位置) * 第二个参数可以省略,将会截取后面所有的内容 * 第二个参数也可以是负数,-1表示倒数第一个,-2倒数第二,依次类推 */ String str = "abcdefghilmn"; var result = str.slice(1,5); console.log(result); // bcde result = str.slice(7); console.log(result); // hilmn result = str.slice(3,-2); console.log(result); // defghil /* * substring() * - 该方法也可以用来截取一个字符串,和slice类似 * - 参数: * 第一个:开始位置索引(包括开始位置) * 第二个:结束位置索引(不包括结束位置) * 不同的是这个方法不能接收负数作为参数,如果传递了一个负数,则默认为0 * 而且它还自动调整参数的位置,如果第二个参数小于第一个,则自动交换,将小的作为第一个参数 */ // 负数默认为0,且自动换位,相当于(0,1) result = str.substring(1,-5); console.log(result); // a /* * substr() * - 该方法也用来截取字符串 * - 参数: * 第一个:开始位置的索引 * 第二个:截取的长度 * - ECMAscript没有实现该标准,但是浏览器基本支持,可以使用 */ result = str.substr(3,3); console.log(result); // def
正则表达式
概念
正则表达式用于定义一些字符串的规则
计算机可以根据正则表达式,来检查一个字符串是否符合规则
或者将字符串中符合规则的内容提取出来
正则表达式创建
- 使用构造函数创建
/* * 使用构造函数创建一个正则表达式对象 * 语法: * - var 变量 = new RegExp("正则表达式","匹配模式"); * - 参数(两个字符串参数): * 参数1:正则表达式,也就是规则 * 参数2:匹配模式,可以是i和g * - i:忽略大小写 * - g:全局匹配模式 * 使用typeof检查正则对象reg,会返回object */ // 这个正则表达式可以检查一个字符串中是否含有a var reg = new RegExp("a"); // 这个正则表达式可以检查一个字符串中是否含有a或者A reg = new RegExp("a","i");
- 使用字面量创建
/* * 使用字面量创建正则表达式 * 语法: * - var 变量 = /正则表达式/匹配模式 * - 参数不是字符串,不需要加引号 */ // 这个正则表达式可以检查一个字符串中是否含有a或者A var reg = /a/i;
- 注意:
两种创建方式都可以使用
使用字面量的方式创建更加简单
使用构造函数的方式更加灵活
构造函数中接收两个字符串作为参数,那么可以写成对象,必要是进行替换
字面量中的两个不是字符串,所以一次就写死了
正则表达式使用
- 主要方法
/* * 正则表达式的方法 * test() * - 使用这个方法可以检查一个字符串是否符合正则表达式的规则 * - 如果符合则返回true,否则返回false * 参数: * 接收一个需要检查的字符串 */ // 创建正则对象,只是检查字符串中是否含有a var reg = new RegExp("a"); var str = "a"; var result = reg.test(str); console.log(result); // true result = reg.test("bcdabnc"); // true
- 括号
[abc]:匹配括号里面的任意一个字符,指包含a或b或c
[^abc]:匹配除了括号里面的任意字符,指不能包含a或b或c
[a-z]:匹配a到z,指包含小写字母
[A-Z]:匹配A到Z,指包含大小字母
[A-z]:匹配A到在,指包含字母
[0-9]:匹配数字
[A-z0-9]:匹配字母和数字
| :或的意思,比如匹配abc可以是[abc],也可以是a|b|c - 量词
通过量词可以设置一个内容出现的次数
量词只对前面的一个内容起作用,如果有多个字符为一个整体,可以使用小括号
{n}指正好出现n次
{m,n}指出现m到n次
{m,}指m次以上
+至少一个,相当于{1,}
*0个至多个,相等于{0,}
?0个或1个,相当于{0,1}// 是否包含连续的3个a var reg = /a{3}/; console.log(reg.test("aabc")); // false console.log(reg.test("aaabc")); // true console.log(reg.test("bdaaehaaa")); // true // 是否包含连续的3个ab reg = /(ab){3}/; console.log(reg.test("ababab")); // true console.log(reg.test("ababacab")); // false // 是否包含a和c且中间有连续3个b reg = /ab{3}c/; console.log(reg.test("abc")); // false console.log(reg.test("abbc")); // false console.log(reg.test("abbbc")); // true console.log(reg.test("bbbca")); // false // 是否包含a和c,且中间有1个或两个或3个b reg = /ab{1,3}c/; console.log(reg.test("abc")); // true console.log(reg.test("abbc")); // true console.log(reg.test("abbbc")); // true console.log(reg.test("edabbced")); // true // 是否包含a和c,且中间至少有1个b reg = /ab{1,}c/; console.log(reg.test("abc")); // true console.log(reg.test("ac")); // false // 是否包含有a和c,且中间至少有一个b reg = /ab+c/; console.log(reg.test("abc")); // true console.log(reg.test("abbc")); // true // 是否包含a和c,且中间可以存在任意个b reg = /ab*c/; console.log(reg.test("ac")); // true console.log(reg.test("abc")); // true console.log(reg.test("abbc")); //true // 是否包含a和c,且中间至多存在一个b reg = /ab?c/; console.log(reg.test("ac")); // true console.log(reg.test("abc")); // true console.log(reg.test("abbc")); // false
- 开头结尾
匹配以某些内容开头或者结尾
^表示开头,作用于后面的一个内容
表示结尾,作用于前面的一个内容如果在正则中同时使 用 和 表示结尾,作用于前面的一个内容 如果在正则中同时使用^和 表示结尾,作用于前面的一个内容如果在正则中同时使用和,则要求字符串必须完全符号正则表达式
如果只写了开头,要求字符串只要开头匹配即可,后面不管
如果只写了结尾,要求字符串只要结尾匹配即可,前面不管
如果开头和结尾都没有写,则字符串只要含有正则即可// 检查一个字符串是否以a开头 var reg = /^a/; console.log(reg.test("abc")); // true console.log(reg.test("acd")); // true console.log(reg.test("a")); // true console.log(reg.test("cab")); // false // 检查一个字符串是否以a结尾 reg = /a$/; console.log(reg.test("bcea")); // true console.log(reg.test("sdfljljaa")); // true console.log(reg.test("aaaaab")); // false // 一个字符串以a开头并且立刻以a结尾,也就是字符串只能是一个a reg = /^a$/; console.log(reg.test("a")); // true console.log(reg.test("aa")); // false console.log(reg.test("aba")); // false // 一个字符串以a为开头或者以a为结尾 reg = /^a|a$/; console.log(reg.test("bbca")); // true console.log(reg.test("abcd")); // true console.log(reg.test("abca")); // true
- 元字符(拥有特殊含义的字符)
.(点):匹配单个的任意字符,除了换行符和行结束符
\w:任意的字母、数字、_ [A-z0-9_]
\W:除了字母、数字、_ [^A-z0-9_]
\d:任意的数字 [0-9]
\D:除了数字 [^0-9]
\s:空格
\S:除了空格
\b:单词边界有时候需求我们匹配的是单词,不是字母
比如我们想看一下一个字符串中是否包含单词child
如果/child/这样写,那么对于"hello children"这样的。也会匹配成功
上面这种只是匹配是否包含了对象的字符,并没有考虑是一个单词,此时我们可以在对应单词前后加上单词边界,表示一个单词,前后都不能有其他字符
/\bchild\b/,这个正则就可以实现只匹配字符串中含有child单词的需求\B:除了单词边界
字符串和正则相关的方法
var str = "1a2b3c4d5e6f7";
/*
* split()
* - 可以将一个字符串拆分为一个数组
* - 方法中可以传递一个正则表达式作为参数,这样方法会根据符合规则的字符去切割字符串
* - 正则表达式即时不设置全局匹配,也会将所有的都拆分
*/
// 根据任意字母将字符串切割
var result = str.split(/[A-z/]);
console.log(result); // 1,2,3,4,5,6,7
/*
* search()
* - 可以搜索字符串中是否含有指定内容
* - 如果搜索到指定内容,则会返回第一次出现的索引
* - 如果没有搜索到则返回-1
* - 以上功能与indexOf一样
* - 但是它可以接受一个正则表达式作为参数,然后会根据正则表达式取检索字符串
* - 正则表达式即时设置了全局匹配,也只是返回第一个的索引
*/
str = "hello hello aec afc abcd";
// 搜索字符串中是否含有abc或aec或afc
result = str.search(/a[bef]c/);
console.log(result); // 12
/*
* match()
* - 可以根据正则表达式,从一个字符串中将符合条件的内容提取出来
* - 默认情况下match只会第一个符合要求的内容,找到后返回之后就停止检索
* - 通过type检查发现返回的结果是一个object,使用Array.isArray检查发现是一个数组
* - 我们可以设置正则表达式为全局匹配模式,这样就会匹配到所有的内容
* - 可以为一个正则表达式设置多个匹配模式,如gi或者ig,且顺序无所谓
*/
str = "1a2b3c4d5e6F7G";
// 从字符串中提取所有的小写字母
result = str.match(/[a-z]/);
console.log(result); //a
// 设置正则为全局匹配模式,且忽略大小写
result = str.match(/[a-z]/gi); // a,b,c,d,e,F,G
console.log(result);
/*
* replace()
* - 可以将字符串中的指定内容替换为新的内容
* - 参数:
* 1.被替换的内容,可以接受一个正则作为参数
* 2.新的内容
* - 默认只会替换第一个,我们可以利用正则的全局匹配模式
*/
str = "1a2b3c4D5E6F7G8";
// 使用&替换所有的字母
result = str.replace(/[a-z]/gi,"&");
console.log(result); // 1&2&3&4&5&6&7&8
// 删除所有的字母,就是使用空字符串替换字母
result = str.replace(/[a-z]/gi,"");
console.log(result); // 12345678
Web APIs
- JS的组成
JavaScript分为三部分:
ECMAScript:JavaScript基础语法
DOM:文档对象模型
BOM:浏览器对象模型我们前面JavaScript基础部分就是学习ECMAScript
后面的DOM和BOM都是属于Web APIs部分 - JS基础和Web APIs
JS基础阶段
ECMAScript规定的基本语法
JS基础语法的掌握
只是为后面部分的基础Web APIs阶段
Web APIs是W3C组织的标准
Web APIs我们主要学习DOM和BOM
Web APIs是JavaScript独有的部分
实现和页面的交互 - API和Web API
API
API(Application Programming Interface应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。
简单理解,API是给程序员提供的一种工具,以便能更轻松的实现想要完成的功能。Web API
是浏览器提供的一套操作浏览器功能和页面元素的API(BOM和DOM)
DOM
DOM简介
概念:
文档对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展标记语言(HTML或者XML)的标准编程接口。
DOM树
- 文档:一个页面就是一个文档,DOM中使用document表示
- 元素:页面中的所有标签都是元素,DOM中使用element表示
- 节点:网页中的所有内容都是节点(标签、属性、文本、注释等),DOM中使用node表示
- DOM把以上内容都看做是对象
- 模型:使用模型来表示对象之间的关系,这样方便我们获取对象
- 节点的属性:
节点类型\节点属性 nodeName nodeType nodeValue 文档节点 #document 9 null 元素节点 标签名 1 null 属性节点 属性名 2 属性值 文本节点 #text 3 文本内容
事件简介
概念:
- 事件:就是文档或浏览器窗口中发生的一些特定的交互瞬间,就是用户和浏览器之间的交互行为
- JavaScript与HTML之间的交互是通过事件实现的。
- 对于Web应用来说,有下面这些代表性的事件:点击某个元素、将鼠标移动至某个元素上分、按下键盘上某个键等等
- 事件是由三部分组成的,分别是事件源、事件类型和事件处理程序。我们也称为事件三要素
- 我们可以为元素对象的对应事件绑定处理函数的形式来响应事件,这样当事件被触发时,其对应的函数将会被调用
- 像这样的函数我们称为响应函数,比如单击事件的函数为单击响应函数
常见的事件句柄
鼠标事件 | 触发条件 |
---|---|
onclick | 鼠标点击左键触发 |
onmouseover | 鼠标经过触发 |
onmouseout | 鼠标离开触发 |
onfouces | 获得鼠标焦点触发 |
onblur | 失去鼠标焦点触发 |
onmousemove | 鼠标移动触发 |
onmouseup | 鼠标弹起触发 |
onmousedown | 鼠标按下触发 |
onchange | 域内容被改变触发 |
ondblclick | 鼠标双击左键触发 |
onkeydown | 键盘某个键被摁下 |
onkeypress | 键盘的某个键被摁下或摁住 |
onkeyup | 键盘的某个键被松开 |
onreset | 重置按钮被点击 |
onresize | 窗口或框架被调整大小 |
onselect | 文本被选中 |
onsubmit | 确认按钮被点击 |
onunload | 退出页面 |
onload | 一张页面或一幅图像完成加载 |
onerror | 加载文档或图像时发生错误 |
onabort | 图像的加载被中断 |
注册事件
给元素添加事件,称为注册事件或者绑定事件
注册事件有两种方式:传统注册方式和方法监听注册方式
- 传统注册方式
1.利用on开头的事件,如onclick
2.<button οnclick=“alert(‘hi~’)”></button>
3.btn.onclick = function(){}
4.特点:注册事件的唯一性
同一个元素同一个事件只能设置一个处理函数,最后注册的处理函数会覆盖前面注册的处理函数 - 方法监听注册方式
1.w3c标准,推荐的方式
2.addEventListener()它是一个方法
3.IE9之前的IE不支持,可以使用attachEvent()代替
4.特点:同一个元素同一个事件可以注册多个监听器
按注册顺序依次执行使用element.addEventListener(type,listener[,useCapture]);
该方法将指定的监听器注册到前面的元素上,当该对象触发指定的事件时,就会执行事件处理函数
type:事件类型字符串,比如click、mouseover、注意这里不要带on
listener:事件处理函数,事件发生时,会调用该监听函数
useCapture:可选参数,是一个布尔值,默认是false,
该方法中的this就是事件绑定的对象attachEvent事件监听方式element.attachEvent(eventName,callback);
该方法将监听器注册到元素上,当该对象触发指定的事件时,指定的回调函数就会被执行。与addEventListener方法不同的是注册的多个事件执行顺序相反,后注册的先执行
eventName:事件类型字符串,比如onclick、onmouseover、这里带有on
callback:事件处理函数,当目标触发事件时回调函数被调用
该方法中的this是window对象 - 代码示例
<body> <button>传统方式按钮</button> <button>方法监听方式按钮</button> </body> <script> var btns = document.querySelectorAll('button'); btns[0].onclick = function() { alert('你好吗?'); } /* * 使用传统方式给按钮1注册两个点击事件 * 会发现后面一个会覆盖前面一个 * 当点击按钮1时只有第二个函数运行了 */ btns[0].onclick = function(){ alert('我不好'); } // 事件监听方式注册事件 btns[1].addEventListener('click',function(){ alert('我出来了啊喂'); }); /* * 点击第二个按钮会发现两个弹出框依次弹出 */ btns[1].addEventListener('click',function(){ alert('我也出来了呢'); }); </script>
删除事件
对于一些按钮,我们如果想让其事件只运行一次,那么运行以后禁用即可
但是对于像div,我们无法禁用,此时如果我们只想让事件运行一次,就需要删除(解绑)事件
<head>
<style>
div {
width:100px;
height:100px;
background-color:'pink';
}
</style>
</head>
<body>
<div>1</div>
<div>2</div>
<div>3</div>
</body>
<script>
var divs = document.querySelectorAll('div');
// 给div1使用传统方式绑定事件且点击触发一次后解绑
divs[0].onclick = function() {
alert(11);
// 运行至此,事件已触发,立即解绑事件,直接赋值null即可
divs[0].onclick = null;
}
// 给div2使用addEventListener注册事件且点击触发一次后解绑
function fn() { // 先定义好回调函数,不能使用匿名的方式,解绑需要
alert(22);
// 运行至此,事件已触发,立即解绑事件
// 调用removeEventListener,第一个参数为事件,第二个为需要解绑的函数
divs[1].removeEventListener('click',fn); // 注意,只写函数名,不需要括号
}
// 注意,如果单独定义了函数,这里绑定也不需要小括号调用,直接写函数对象,回调函数系统会调用
divs[1].addEventListener('click',fn);
// 给div3使用attachEvent方式注册事件且点击触发一次后解绑
divs[2].attachEvent('onclick',fn1);
function fn1() {
alert(33);
// 运行至此,事件已触发,立即解绑
// 调用detachEvent,用法与removeEventListener类似
divs[2].detachEvent('onclick',fn1);
}
</script>
DOM事件流
事件流描述的是从页面中接手事件的顺序。
事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即DOM事件流
比如我们给一个div注册了点击事件:
- 注意:
1.JS代码只能执行捕获或者冒泡其中的一个阶段。
2.onclick和attachEvent只能得到冒泡阶段
3.addEventlistener(type,listener[,useCapture]),第三个参数如果是true,表示在事件捕获阶段调用事件处理程序;如果是false(不写默认是false),表示在事件冒泡阶段调用处理程序
4.实际开发中我们很少使用事件捕获,我们更关注事件冒泡
5.有些事件是没有冒泡的,比如onblur、onfocus、onmouseenter、onmouseleave - 代码示例
<head> <style> .father { width:100px; height:100px; background-color:'red'; margin: 50px auto; } .son { width:50px; height:50px; background-color:'green'; margin:25px auto; } </style> </head> <body> <div class='father'> <div class='son'>son盒子</div> </div> </body> <script> // 1.JS代码中只能执行捕获或者冒泡其中的一个阶段 var son = document.querySelectory('son'); // 2.onclick和attachEvent(ie)只能得到冒泡阶段 var father = document.querySelectory('father'); // 3.捕获阶段,如果addEventListener第三个参数是true,则处于捕获阶段 son.addEventListener('click',function(){alter('son');},true); // 捕获阶段顺序 documen -> html -> body -> father -> son // 此时因为father和son都绑定了单击事件,点击son后,会先弹出father,再弹出son father.addEventListener('click',function(){alter('father');},true); // 4.冒泡阶段,如果addEventListener第三个参数是false或者不写,则处于冒泡阶段 son.addEventListener('click',function(){alter('son');}); // 冒泡阶段顺序 son -> father -> body -> html -> document // 此时因为father和son都绑定了单击事件,点击son后,会先弹出son,再弹出father father.addEventListener('click',function(){alter('father');}); </script>
事件对象
- 概念
当事件的响应函数被触发时,浏览器每次都会将一个事件对象作为实参传递给响应函数
eventTaget.onclick = function(event){}
这个对象就是事件对象,event,我们也喜欢写e或者evt
事件对象代表事件状态,比如键盘按键的状态,鼠标的按钮状态,鼠标的位置
简单的理解当事件发生后,跟事件相关的一系列信息数据的集合,都封装在这个对象中,这个对象就是事件对象event,它有很多的属性和方法
但是在IE8及以下浏览器中,不支持event形参的方式,它是以window的属性出现的,windex.event;
所以需要解决兼容性问题,可以按照下面的方式进行,也是公司最常用的方式 - 示例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> div { border: 1px solid red; width: 300px; } #areaDiv { height: 100px; } #showMsg { height: 60px; } </style> </head> <body> <div id="areaDiv"></div> <div id="showMsg"></div> </body> <script> var areaDiv = document.getElementById('areaDiv'); var showMsg = document.getElementById('showMsg'); areaDiv.onmousemove = function (event) { // IE8及以下兼容性处理 event = event || window.event; showMsg.innerHTML = 'x = ' + event.clientX + ',y = ' + event.clientY; } </script> </html>
- 案例(div跟随鼠标移动pageX和pageY)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> div { width: 100px; height: 100px; background-color: red; position: absolute; } </style> </head> <body> <div></div> </body> <script> // 设置body的高度或宽度,验证pageX和pageY /*document.body.style.height = '1000px';*/ var div = document.querySelector('div'); document.onmousemove = function (event) { // event 做兼容性处理 event = event || window.event; // 正常获取鼠标偏移量 var x = event.clientX; var y = event.clientY; // 给div设置偏移量,且div一定要有定位,后面的px单位不能少 div.style.left = x + 'px'; div.style.top = y + 'px'; // 但是如果给body设置长宽超过窗口时,会出现滚动条 // 此时如果拉动滚动条之后,会发现div与鼠标直接会出现偏差 // 原因是通过clientX和clientY获取到的是鼠标相对窗口的偏移量 // 也就是无论body滚动条如何,鼠标始终认为浏览器的显示窗口的左上角是原点 // 但是此时div的原点是body的左上角,所以我们给div的偏移量是相对body的原点移动 var left = event.pageX; var top = event.pageY; div.style.left = left + 'px'; div.style.top = top + 'px'; // pageX和pageY获取到的是相对于文档的偏移量,可以解决上面的问题,但是有兼容性 // 上面两个属性在IE8及以下不支持 // 对于不支持pageX和pageY的情况,我们需要自己获取滚动条的进度计算差值 // // 但是不同的浏览器获取进度条不同 // 谷歌浏览器认为body的滚动条就是body的scollTop var st = document.body.scollTop; // 但是其他浏览器不是,它们认为body出现滚动条是因为HTML放不下导致的 // 所以需要获取HTML的scollTop。所以为了兼容性,可以这样写 // var st = document.documentElement.scollTop || document.body.scollTop; div.style.top = top + st + 'px'; } </script> </html>
事件冒泡
- 概念
在事件流中我们学习过了事件冒泡、事件捕获和目标阶段
所谓事件冒泡就是事件的向上传导,当后代元素上的事件被触发时,其祖先元素的相同事件也会被触发
在开发中大部分情况冒泡都是有用的,如果不希望发生事件冒泡可以通过事件对象阻止冒泡 - 阻止
// 通过事件对象的cancelBubble属性,cancel为取消之意 enent.cancelBubble = true; // 上面的方式所有的l浏览器都支持 //下面的方式比较新,有兼容性问题,IE浏览器不支持,其他浏览器支持 event.stopPropagation(); // 通过该方法也可以实现,propagation为传播之意 // 第二种方式在开发中用的较多
- 案例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> #box { width: 300px; height: 200px; background-color: pink; } #sn { background-color: yellow; } </style> </head> <body> <div id="box"> 我是div标签 <span id="sn"> 我是span标签 </span> </div> </body> <script> var box = document.getElementById('box'); var sn = document.getElementById('sn'); box.onclick = function(event) { alert('我是div标签'); // 设置以后点击div,只有div事件触发,如果span没有设置 // 且点击span之后会冒泡,但是到div截止,也就是span与div触发 event.cancelBubble = true; } sn.onclick = function(event) { alert('我是span标签'); // 可以看到span设置以后点击只有span事件触发,div与body都没有再触发 // 该方法IE不支持,需要做兼容性处理 event = event || window.event; event.stopPropagation() || event.cancelBubble = true; event.stopPropagation(); } document.body.onclick = function() { alert('我是body标签'); } </script> </html>
事件委托
- 概念
概念:事件委托也称事件代理,在jQuery里面称为事件委派。
原理:不是每个子节点单独设置事件监听,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点
理解:将事件统一绑定给元素的共同的祖先元素,这样当后代元素上的事件触发时,会一直冒泡到祖先元素,从而通过祖先元素的响应函数处理事件 - 问题
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script> window.onload = function() { /* * 为每个超链接都绑定一个单击响应函数 */ // 获取所有的a标签 var allA = document.getElementsByTagName('a'); // 遍历,依次给所有a绑定点击响应函数 for(var i = 0 ; i < allA.length ; i++ ) { allA[i].addEventListener('click',function() { alert('我是超链接函数'); }); } /* * 此时发现没有问题,只是这样利于循环给 * 所有的超链接添加比较麻烦且如果超链接较多的 * 情况下效率较低,但是如果加上一个按钮,新添加 * 超链接之后,会发现新添加的超链接没有被绑定 * 响应函数,原因是我们新的超链接是在按钮的事件触发之后才 * 添加的,而超链接的事件是页面加载完之后就绑定的 * 所以新添加的超链接就没有响应函数了 */ // 获取按钮 var btn01 = document.querySelector('#btn01'); // 获取ul对象 var ul = document.getElementById('u1'); // 给按钮绑定单击函数用于添加新的超链接 btn01.addEventListener('click',function() { // 创建一个li标签 var li = document.createElement('li'); // 给li标签添加超链接 li.innerHTML = '<a href="javascript:;">新添加的超链接</a>'; // 将创建好的li添加给ul ul.appendChild(li); }); }; </script> </head> <body> <button id="btn01">添加超链接</button> <ul id="u1"> <li><a href="javascript:;">超链接一</a></li> <li><a href="javascript:;">超链接二</a></li> <li><a href="javascript:;">超链接三</a></li> </ul> </body> </html>
会发现之前的方式存在很多问题
首先就是我们需要循环给所有的a标签绑定事件,比较复杂且效率低
其次如果有新添加的超链接,则没办法绑定事件
所以我们可以用事件委派,将响应函数绑定给所有的父元素ul,因为li是独有的,只能绑给li - 案例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script> window.onload = function() { // 获取按钮 var btn01 = document.querySelector('#btn01'); // 获取ul对象 var ul = document.getElementById('u1'); // 给按钮绑定单击函数用于添加新的超链接 btn01.addEventListener('click',function() { // 创建一个li标签 var li = document.createElement('li'); // 给li标签添加超链接 li.innerHTML = '<a href="javascript:;">新添加的超链接</a>'; // 将创建好的li添加给ul ul.appendChild(li); }); /* * 我们使用事件委派,将事件绑定给ul */ ul.addEventListener('click',function(event) { // 此时发现所有的a依然会有点击事件 // 包括新添加的超链接 // 此时我们可以利用e.target得到当前被点击的元素 alert('我是ul的响应函数'); event = event || window.event; // 利用target得到当前被点击的元素 event.target.style.backgroundColor = 'pink'; }); }; </script> </head> <body> <button id="btn01">添加超链接</button> <ul id="u1"> <li><a href="javascript:;">超链接一</a></li> <li><a href="javascript:;">超链接二</a></li> <li><a href="javascript:;">超链接三</a></li> </ul> </body> </html>
鼠标事件
- 拖拽
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index</title> <style> #box { width: 100px; height: 100px; background-color: red; position: absolute; } </style> </head> <body> <div id="box"></div> </body> <script> /* * 整个拖拽分为三部分: * 1. 当鼠标在被拖拽元素上按下时,开始拖拽onmousedown * 2. 当鼠标移动时被拖拽元素跟随鼠标移动onmousemove * 3. 当鼠标松开时,被拖拽元素固定在当前位置onmouseup */ var box = document.querySelector('div'); // 给box绑定鼠标按下事件 box.addEventListener('mousedown',function (event) { /* * 这里对应后面的return false,对于IE8的兼容性设置 * 我们可以通过给box设置鼠标点击的捕获行为 * 该方法会将后面的一次单击强制捕获到自己身上,也就是 * 设置以后,无论鼠标点击哪里,甚至是 * 电脑桌面,它都会捕获到box的点击事件上来 * 该方法设置是一次性的,只会起作用一次 * * * 该方法只有IE有用,但是在火狐中不会报错 * 而如果在Chrome中调用,会报错,所以 * 需要进行判断 */ if(box.setCapture) { box.setCapture(); } var lt = event.clientX - box.offsetLeft; var ot = event.clientY - box.offsetTop; // 当鼠标按下以后,鼠标移动事件需要给document,因为是在整个网页中移动 document.onmousemove = function (event) { event = event || window.event; // 获取鼠标当前的位置 var x = event.clientX - lt; var y = event.clientY - ot; // 修改元素的位置 box.style.left = x + 'px'; box.style.top = y + 'px'; } document.onmouseup = function () { // 当鼠标松开时,被拖拽元素固定在当前位置 document.onmousemove = null; document.onmouseup = null; /* * 接上面的setCapture()方法 * 虽然上面说了这个方法是一次性的,但是 * 因为是放在onmousedown事件里面的 * 所以每次点击事件触发后都会被捕获到box上 * 所以需要在鼠标松开后取消捕获 * 下面的写法与上面的判断一样 * 防止在Chrome中报错 */ box.releaseCapture && box.releaseCapture(); } /* * 当我们拖拽网页中的内容时,浏览器会默认 * 去搜索引擎中搜索内容,此时会导致拖拽功能的 * 异常,这个是浏览器提供的默认行为,如果 * 不希望发生这个行为,可以 * 通过return false来取消这个默认行为 * 但是这招对IE8不起作用 */ return false; }); </script> </html>
- 上面的拖拽可以封装伪一个函数
<body> </body> <script> function fun(obj) { obj.addEventListener('mousedown',function (event) { /* * 这里对应后面的return false,对于IE8的兼容性设置 * 我们可以通过给box设置鼠标点击的捕获行为 * 该方法会将后面的一次单击强制捕获到自己身上,也就是 * 设置以后,无论鼠标点击哪里,甚至是 * 电脑桌面,它都会捕获到box的点击事件上来 * 该方法设置是一次性的,只会起作用一次 * * * 该方法只有IE有用,但是在火狐中不会报错 * 而如果在Chrome中调用,会报错,所以 * 需要进行判断 */ if(obj.setCapture) { obj.setCapture(); } var lt = event.clientX - obj.offsetLeft; var ot = event.clientY - obj.offsetTop; // 当鼠标按下以后,鼠标移动事件需要给document,因为是在整个网页中移动 document.onmousemove = function (event) { event = event || window.event; // 获取鼠标当前的位置 var x = event.clientX - lt; var y = event.clientY - ot; // 修改元素的位置 obj.style.left = x + 'px'; obj.style.top = y + 'px'; } document.onmouseup = function () { // 当鼠标松开时,被拖拽元素固定在当前位置 document.onmousemove = null; document.onmouseup = null; /* * 接上面的setCapture()方法 * 虽然上面说了这个方法是一次性的,但是 * 因为是放在onmousedown事件里面的 * 所以每次点击事件触发后都会被捕获到box上 * 所以需要在鼠标松开后取消捕获 * 下面的写法与上面的判断一样 * 防止在Chrome中报错 */ obj.releaseCapture && obj.releaseCapture(); } /* * 当我们拖拽网页中的内容时,浏览器会默认 * 去搜索引擎中搜索内容,此时会导致拖拽功能的 * 异常,这个是浏览器提供的默认行为,如果 * 不希望发生这个行为,可以 * 通过return false来取消这个默认行为 * 但是这招对IE8不起作用 */ return false; }); } </script>
键盘事件
文档加载
- 浏览器在加载一个页面时,是按照自上向下的顺序加载的
- 读取到一行就运行一行,如果将script标签写到页面的上边,在代码执行时,页面还没有加载,会导致无法获取到DOM对象
- 将js代码写到页面的下部就是为了可以在页面加载完毕以后再执行JS代码
- onload事件会在整个页面加载完成之后才触发
- 为window绑定一个onload事件,该事件对应的响应函数将会在页面加载完毕之后执行,这样可以确保我们的代码执行时所有的DOM对象已经加载完毕了
- onload虽然是后执行,但是加载依然是先加载
DOM操作
获取节点
- 获取元素节点:通过document对象调用, 浏览器已经为我们提供了文档节点对象,这个对象是window属性,可以在页面中直接使用,文档节点代表的是整个网页
getElementById():
通过元素id属性获取一个元素节点对象
参数:接收一个字符串类型的id,大小写敏感
返回值:返回的是一个元素对象
如果页面中没有指定id的元素存在,会返回null
获取到元素节点对象之后,如果想要获取元素的属性,直接使用元素对象点属性名的方式
获取元素的属性之间使用元素对象点属性名,但是对于class属性来说。由于class是JS的关键字,所以要使用className来获取getElementsByTagName():
通过元素标签名获取一组元素节点对象
参数:接收一个字符串类型的标签名
返回值:返回一个包含多个同名标签的伪数组,有长度,有索引,通过索引以及for遍历得到所有获取的元素
如果页面中只有一个该名字的元素,返回的依然是一个伪数组,如果页面中没有对应名字的元素,返回的不是null,而是一个空的伪数组
还可以获取某个父元素内部的所有指定标签名的子元素:element.getElementsByTagName();element就是父元素对象,父元素必须是单个对象,获取的时候不包括父元素自己getElementsByName():
通过元素name属性获取一组元素节点对象
获取到的也是一个类数组对象getElementsByClassName();
通过元素的class属性获取一组元素
获取到的是一个类数组对象
该方法在IE8及以下浏览器不支持<!doctype html> <html lang="en"> <body> <div id="time">我是一个div标签</div> <ul> <li>我是一只小鸭子,咿呀咿呀哟!</li> <li>我是一只小鸭子,咿呀咿呀哟!</li> <li>我是一只小鸭子,咿呀咿呀哟!</li> <li>我是一只小鸭子,咿呀咿呀哟!</li> <li>我是一只小鸭子,咿呀咿呀哟!</li> </ul> </body> <script type="text/javascript"> var li1 = document.getElementsByTagName('li'); console.log(li1); // 打印出得到的数据,是一个伪数组 var timer = document.getElementById('time'); console.log(timer); // 显示获取的到内容 console.log(typeof timer); // 打印获取到的数据类型,为object类型 console.dir(timer); // 该方法会打印我们返回的元素对象,更好的查看里面的方法和属性 </script> </html>
- 获取元素节点的子节点:通过具体的元素节点调用
getElementsByTagName()
方法。返回当前节点的指定标签名的后代节点
childNodes
属性。表示当前节点的所有子节点
所有子节点包括文本节点都会获取到。比如换行。也是子节点
使用时需要注意,它会将子元素一节元素之间的换行都获取到。换行作为文本节点
但是在IE8及以下浏览器中。不会将空白换行当成子节点。只会返回子元素children
属性。可以获取当前元素的子元素
firstChild
属性。表示当前节点的第一个子节点
包括空白换行firstElementChild
获取当前元素的第一个子元素
该属性在IE8及以下不支持lastChild
属性。表示当前节点的最后一个子节点
- 获取父节点和兄弟节点:通过具体节点调用
parentNode
属性。表示当前节点的父节点
父元素,不会出现空白换行节点previousSibling
属性。表示当前节点的前一个兄弟节点
如果前面是空格或者换行,也会算的previousElementSinling
属性。表示当前节点的前一个兄弟元素
只会找到前面的一个元素,不会出现空白换行
但是IE8及以下浏览器不支持nextSibling
属性。表示当前节点的后一个兄弟节点
- document中一些属性及方法
document.body:获取页面的body元素
document.documentElement:获取页面的HTML元素
document.all:获取页面中的所有元素
document.querySelector():需要一个选择器的字符串作为参数
可以根据一个CSS选择器来查询一个元素节点对象
该方法在IE8找那个也支持,但是IE7及以下不支持
使用该方法只会返回唯一的一个元素,如果满足条件的元素又多个,只会返回满足条件的第一个元素document.querySelectorAll():
该方法和querySelector()一样
不同的是它会将符合条件的元素封装到一个数组中返回
即时满足条件的元素只有一个,也会返回一个数组var body = document.body; console.log(bod); // 显示为body元素 var html = document.documentElement; console.log(html); // 显示为页面中的HTML元素 var all = document.all; console.log(all); // 显示为undefined console.log(all.length); // 显示为页面中所有的元素个数 for (var i = 0 ; i < all.length ; i++) { // 循环打印会打印页面中所有元素 console.log(all[i]); } all = document.getElementByTagName("*"); // 打印发现也是页面中所有元素个数,两种范式都可以获取页面中的所有元素 console.log(all.length);
操作元素(增/删/改)
JavaScript的DOM操作可以改变网页内容、结构和样式,我们可以利用DOM操作元素来改变元素里面的内容、属性等
- 改变元素内容
element.innerText:
从起始位置到终止位置的内容,但它去除HTML标签,同时空格和换行也会去掉
不识别HTML标签,用innerText修改元素内容时,写入的标签会以文本显示出来
非标准格式element.innerHTML:
起始位置到终止位置的全部内容,包括HTML标签,同时保留空格和换行
识别HTML标签,用innerHTML修改元素内容时,写入的标签会正常格式显示
W3C标准<body> <div></div> <p> 我是文字 <span>1245</span> </p> </body> <script> var div = document.querySelector('div'); // 原样显示了,加粗标签以文本显示出来 div.innerText = '<strong>今天是:</strong>2021年'; // 加粗标签中的文字正常加粗显示,标签被识别解释 div.innerHTML = '<strong>今天是:</strong>2021年'; // 显示内容为:p标签中的文字和里面span里面的文字,但是没有标签和换行 console.log(document.querySelector('p').innerText); // 整个p标签里面的所有内容原样显示出来,包括p的文字内容和里面的子标签 console.log(document.getElementsByTagName('p')[0].innerHTML); </script>
- 修改常见元素属性
innerText、innerHTML
src、href
id、alt、title<!--修改元素的src属性--> <body> <button>点击更换图片</button> <img src="https://img.xjh.me/desktop/img/58974224_p0.jpg" alt="努力加载" width="300px" height="300px"> </body> <script> var btn = document.querySelector('button'); var img = document.querySelector('img'); btn.onclick = function () { img.src = 'https://api.ixiaowai.cn/api/api.php'; img.title = '一张美女图片'; } </script>
- 表单元素属性修改
type、value、checked、selected、disabled
<body> <button>点击按钮</button> <input type="text" value="初始信息"> </body> <script> var btn = document.getElementsByTagName('button')[0]; var inp = document.getElementsByTagName('input')[0]; btn.onclick = function () { // 点击按钮后修改输入框中的值,此时如果使用innerHTML是没有作用的 inp.value = '我是新的信息'; // 按钮被点击后禁用 btn.disabled = true; } </script> /* * 使用其完成密码框点击眼睛图片切换明文及密文状态 */ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> #pwd { width: 370px; height: 30px; border: 0; outline: none; } .box { width: 400px; margin: 50px auto; border-bottom: 1px solid red; position: relative; } img { width: 24px; height: 24px; position: absolute; right: 10px; } </style> </head> <body> <div class="box"> <img src="../img/眼睛_闭.png"> <input type="password" id="pwd"> </div> </body> <script> var img = document.querySelector('img'); var ipt = document.querySelector('#pwd'); var flag = true; img.onclick = function () { if (flag) { ipt.type = 'text'; img.src = '../img/眼睛-开.png'; flag = false; } else { ipt.type = 'password'; img.src = '../img/眼睛_闭.png'; flag = true; } } </script>
- 元素样式修改
1.element.style.属性
行内样式操作。
会发现通过JS修改的属性直接加载表示上用行内style
因为是直接生成在元素的行内样式,权重比较高
但是如果在CSS样式中写了!important,则CSS中的样式权重高,JS修改无用
JS里面的样式采用驼峰命名法,比如font-size就是fontSize
如果样式比较少或者功能比较简单的情况下选择使用
通过该语法可以设置和读入样式,但是设置和读取的都是内联样式,CSS中的样式读取不到,可以使用element.currentStyle.样式名,这种语法可以读取当前元素显示的样式,它既不是读取内联样式,也不是读取内部样式,读取的是当前显示的样式,但是只有IE浏览器支持
在其他浏览器中可以使用getComputedStyle()方法,该方法是window的方法,可以直接使用,接收两个参数,第一个是需要获取样式的元素,第二个参数传递一个伪元素,一般传一个null,返回一个object对象,封装了该元素的样式,可以通过对象.样式名获取,但是该方法不支持IE8及以下浏览器2.element.className 类名样式操作。
当需要修改的样式比较多或者复杂时
我们可以用类选择器在css中定义好样式
但是我们不用将选择器名给元素
在JS中当事件触发或者我们需要修改样式时
我们直接修改元素的className为我们提前写好的样式的class名字即可,不需要加点
适用于样式比较多或者功能比较复杂的样式
如果元素原有class,则会直接覆盖掉
如果要保留之前的class,则需要用手动添加比如:
element.className = ‘first change’;<head> <meta charset="UTF-8"> <title>Document</title> <style> div { width: 300px; height: 300px; background-color: aqua; } </style> </head> <body> <div></div> </body> <script> var div = document.querySelector('div'); div.onclick = function () { this.style.width = '500px'; this.style.height = '500px' this.style.backgroundColor = 'blue'; } </script> /* * 模拟京东首页搜索框 */ <head> <meta charset="UTF-8"> <title>Document</title> <style> input { color: #999; } </style> </head> <body> <input type="search" value="手机"> </body> <script> var ipt = document.querySelector('input'); ipt.onfocus = function () { if (this.value == '手机') { this.value = ''; } this.style.color = '#333'; } ipt.onblur = function () { if (this.value == '') { this.value = '手机'; } this.style.color = '#999'; } </script>
通过className修改样式
<head> <meta charset="UTF-8"> <title>Document</title> <style> /*初始样式*/ div { width: 300px; height: 300px; background-color: pink; } /*修改之后的样式,写一个class选择器*/ .change { width: 500px; height: 500px; margin: 30px auto; background-color: aqua; opacity: 0.5; } </style> </head> <body> <div></div> </body> <script> /* * div点击事件,点击后添加className,也就是添加了一个类选择器 */ var div = document.querySelector('div'); div.onmouseover = function () { // 需要注意的是这里只写类选择器名,不需要前面加点 this.className = 'change'; } </script> </html> /* * 输入框验证提示信息 */ <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <style> div { margin: 50px auto; width: 800px; text-align: center; line-height: 30px; } input { height: 20px; } p { display: inline-block; font-size: 18px; background: url("../img/提示.png") no-repeat left center; background-size: 26px 26px; padding-left: 28px; margin-left: 3px; } .seccuss { background-image: url("../img/seccuss.png"); color: greenyellow; } .wrong { color: red; background-image: url("../img/wrong.png"); } </style> </head> <body> <div> <input type="password"> <p>请输入6~16位密码!</p> </div> </body> <script> var ipt = document.querySelector('input'); var text = /^[0-9A-z](6,16)$/; var p = document.querySelector('p'); ipt.onblur = function () { if (this.value.length > 6 && this.value.length < 16) { p.className = 'seccuss'; p.innerHTML = '输入正确'; } else { p.className = 'wrong'; p.innerHTML = '输入错误,长度要求6~16位'; } } </script> </html>
综合练习实现表格中鼠标移入行变色以及全选反选案例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> input { width: 24px; height: 24px; } th { background-color: antiquewhite; } </style> </head> <body> <table border="1px" cellspacing="0" cellpadding="30px"> <thead id="thd"> <tr> <th><input type="checkbox"></th> <th>商品</th> <th>单价</th> <th>类型</th> </tr> </thead> <tbody id="tbd"> <tr> <td><input type="checkbox"></td> <td>iPhone</td> <td>8000</td> <td>手机</td> </tr> <tr> <td><input type="checkbox"></td> <td>Dell</td> <td>6500</td> <td>电脑</td> </tr> <tr> <td><input type="checkbox"></td> <td>Lining</td> <td>500</td> <td>服装</td> </tr> </tbody> </table> </body> <script> var tbd = document.getElementById('tbd'); var trs = tbd.querySelectorAll('tr'); trs.forEach(function (value) { value.onmouseover = function () { this.style.backgroundColor = 'aqua'; } value.onmouseout = function () { this.style.backgroundColor = ''; } }); document.querySelector('input').onclick = function () { tbd.querySelectorAll('input').forEach(value => value.checked=this.checked); } tbd.querySelectorAll('input').forEach(value => { value.onclick = function () { var flag = true; tbd.querySelectorAll('input').forEach(v => { if (!v.checked) { flag = false; } }); document.querySelector('input').checked = flag; } }); </script> </html>
- 其他样式相关属性
//获取当前元素的可见宽高,返回值为数值,可直接计算,包括内容区域和padding区 element.clientWidth element.clientHeight //获取当前元素的整个宽高,返回值为数值,可直接计算,包括内容去和padding区和边框区 element.offsetWidth element.offsetHeight //获取当前元素的定位父元素,会获取到离当前元素最近的开启了定位的祖先元素,如果都没有则返回body element.offsetParent //获取当前元素相对于其定位元素(offsetParent)的水平和垂直偏移量 element.offsetLeft element.offsetTop //获取元素整个滚动区域的宽高,也就是对于溢出部分也会计算 element.scrollHeight element.scrollWidth //获取滚动条滚动的距离 element.scrollLeft element.scrollTop // scrollWidth-scrollLeft==clientWidth时,滚动条到头 // scrollHeitht-scrollTop==clientHeight时,滚动条到头
- 获取元素属性值
1.element.属性
获取内置属性的值(元素本身自带的属性)
无法获取到自定义的属性值2.element.getAttribute(‘属性’);
主要获得自定义的属性值(标准),我们程序员自定义的属性
开发中经常由于页面内容较多较复杂,常常内置属性不够用,就可以用自定义属性
也可以获取到内置属性的值<body> <div id = 'demo' index='1'>我是一个div</div> </body> <script> var div = document.querySelector('div'); // 通过点属性的方式获取属性值 console.log(div.id); // 控制台打印出id的值为demo // 通过getAttribute的方式获取自定义属性 console.log(div.getAttribute('index')); // 打印出index的值为1 </script>
- 设置元素属性值
1.element.属性 = 值
设置内置属性的值(元素本身自带的属性)
无法设置到自定义的属性值2.element.setAttribute(‘属性’,‘值’);
主要设置自定义的属性值(标准),我们程序员自定义的属性
如果该属性不存在,就会添加该自定义属性
开发中经常由于页面内容较多较复杂,常常内置属性不够用,就可以用自定义属性
也可以设置内置属性的值移除元素属性element.removeAttribute(‘属性’);
可以移除任意的属性
<body> <div id = 'demo' index='1'>我是一个div</div> </body> <script> var div = document.querySelector('div'); // 通过点属性的方式设置属性值 div.id = 'test'; console.log(div.id); // 控制台打印出id的值为test // 通过getAttribute的方式设置自定义属性 div.setAttribute('index','100'); console.log(div.getAttribute('index')); // 打印出index的值为100 div.removeAttribute('index'); // 控制台查看该元素已经没有index属性了 </script>
- H5自定义属性
自定义属性的目的:是为了保存并使用数据。有些数据可以保存到页面中而不用保存到数据库中。
自定义属性获取是通过getAttribute(‘属性’)获取。
但是有些自定义属性很容易引起歧义,不容易判断是元素的内置属性还是自定义属性
自定义属性我们一般直接写在元素中或者通过setAttribute定义设置H5自定义属性
H5规定自定义属性以data-开头作为属性名并且赋值
比如<div data-index=‘1’></div>获取H5自定义属性
兼容性获取element.getAttribute(‘data-index’);
H5新增element.dataset.index或者element.dataset[‘index’]ie11才开始支持
可以看出,新增的方法是将所有自定义属性放在dataset集合中。我们或者的是集合的属性,前面讲过的两种方式获取属性
需要注意的是通过新的方式获取自定义属性时,只能获取到data-开头的自定义属性
还有注意的是新的方式获取时属性名不写前面的data-
特殊情况,如果自定义属性名比较长,比如data-index-name这种,那么使用兼容性较好的方式获取事直接写属性名即可,但是使用新的方式获取时,无论是通过点属性还是方括号的方式,都要将短线相连的单词改为驼峰方式,如
element.dataset.indexName或者element.dataset[‘indexName’]才可以正确获取
节点操作(增/删/改)
- 为什么学习节点操作
获取元素通常使用两种方式
利用DOM提供的方法获取元素document.getElementById()
document.getElementByTagName()
document.querySelecror()等
逻辑性不强,繁琐利用节点层级关系获取元素
利用父子兄弟节点关系获取元素
逻辑性强但兼容性稍差两种方式都可以获取元素节点,但是节点操作更简单
- 节点层级
- 父级节点
node.parentNode:获取到父节点 parentNode属性可返回某节点的父节点,注意是最近的一个父节点 如果指定的节点没有父节点则返回null <body> <div class='box'> <div class='ig'> </div> </div> </body> <script> var ig = document.querySelector('.ig'); // 打印父节点 console.log(ig.parentNode); </script>
- 子节点
parentNode.childNodes:获取所有的子节点 parentNode.childNodes返回包含指定节点的子节点的集合,该集合为即时更新的集合 返回值里面包含了所有的子节点,包括元素节点,文本节点 如果只想要获取里面的元素节点,则需要专门处理,所以我们一般不提倡使用childNodes <body> <ul> <li>列表1</li> <li>列表2</li> <li>列表3</li> <li>列表4</li> </ul> </body> <script> var ul = document.querySelector('ul'); for (var i = 0 ; i < ul.childNodes.length; i++) { // nodeType文本节点为3,元素节点为1 if (ul.childNodes[i].nodeType == 1 ) { console.log(ul.childNodes[i]); } } </script> parentNode.children:获取所有的子元素节点 是一个只读属性,返回所有的子元素节点,它只返回子元素节点,其余节点不返回 虽然children是一个非标准,但是得到了各个浏览器的支持,因此我们可以放心使用 for ( var i = 0; i < ul.nodeChildren ; i++ ) { console.log(ul.nodeChildren[i]; }
- 首个/最后一个子节点
<body> <ul> <li>列表1</li> <li>列表2</li> <li>列表3</li> <li>列表4</li> </ul> </body> <script> var ul = document.querySelector('ul'); // firstChild 第一个子节点,不管是不是文本节点还是换行节点 console.log(ul.firstChild); // 文本节点 // 最后一个节点 console.log(ul.lastChild); // 列表4 // firstElementChild 返回第一个子元素节点 console.log(ul.firstElementChild); // 列表1 // lastElementChild 返回最后一个元素节点 console.log(ul.lastElementChild); // 列表4 /* * 以上属性中,前面两个回出现文本节点,后面两个有兼容性问题IE9以上才可,所以我们一般使用下面的方式获取,也就是前面的Children属性的方式 */ console.log(ul.children[0]); // 列表1 console.log(ul.children[ul.children.length-1]); // 列表4 </script>
- 兄弟节点
<body> <div>我是div</div> <span>我是span</span> </body> <script> var div = document.querySelector('div'); // nextSibling 是获取下一个兄弟节点,包含文本节点,换行等 console.log(div.nextSibling); // previousSibling 是获取上一个兄弟节点,包含文本节点,如果没有,返回null console.log(div.previousSibling); // nextElementSibling 是获取下一个兄弟元素节点,IE9以上支持 console.log(div.nextElementSibling); // previousElementSibling 是获取上一个兄弟元素节点,没有返回null console.log(previousElementSibling); </script>
- 创建节点/添加节点
document.createElement(‘tagName’);
docement.createElement()方法创建由tagName指定的HTML元素。因为这些元素原先不存在,是根据我们的需求动态生成的。所以我们也称为动态创建元素节点
docement.createTextNode(‘data’)
创建一个文本节点
接收一个字符串参数,作为文本节点的内容node.appendChild(child)
node.appendChild()方法将一个节点添加到指定父节点的子节点列表末尾。类似于css里面的after伪元素。
node.insertBefore(child,指定元素)
将一个节点添加到父节点的指定子节点前面,类似于css里面的before伪元素
<body> <ul> <li>123</li> </ul> </body> <script> // 创建节点元素节点 var li = docement.createElement('li'); // 添加节点,追加,在123之后加一个空li var ul = docement.querySelector('ul'); ul.appendChild(li); // 添加元素,在123前面添加一个空li var lili = document.createElement('li'); ul.insertBefore(lili,ul.children[0]); </script>
- 删除节点
node.removeChild(child)
node.removeChild()方法从DOM中删除一个子节点,返回删除的节点
<body> <button>删除</button> <ul> <li>孙悟空</li> <li>猪八戒</li> <li>沙悟净</li> </ul> </body> <script> var ul = document.querySelector('ul'); var btn = docement.querySelector('button'); btn.onclick = function() { ul.removeChild(ul.children[0]); if (ul.children.length == 0) { this.disabled = true; } } </script>
- 复制节点
node.cloneNode();
复制当前节点,如果括号中没有参数或者是false,则为浅拷贝
如果括号中是true,则为深拷贝
浅拷贝只是复制当前节点元素,不会拷贝元素的子节点
深拷贝会拷贝当前元素以及元素的子节点<body> <ul> <li>123</li> <li>456</li> <li>789</li> </ul> </body> <script> var ul = docement.querySelector('ul'); // 浅拷贝第一个li,然后追加到ul中,可以看到页面中多了一个空的li,因为是浅拷贝 ul.appendChild(ul.children[0].cloneNode()); // 此时后面又多了一个li,且内容是123 ul.appendChild(ul.children[0].cloneNode(true)); </script>
- 三种动态创建元素区别
docement.write()
直接将内容写入页面的内容流中,追加在现有元素之后
但是如果放在事件中,等待整个文档流加载完毕后执行,会导致页面重新绘制
也就是说会清空整个页面,只写入write里面的内容element.innerHTML
是将内容写入某个DOM节点,不会导致页面重绘
相比createElement方法,创建多个元素效率更高,前提是不能使用字符串的拼接方式docement.createElement()
单纯的创建一个元素
创建多个元素效率相比innerHTML较低,但是结构更清晰 - 替换子节点
node.replaceChild(newNode,oldNode);
可以使用指定的子节点替换已有的子节点
语法:父节点.replaceChild(新节点,旧节点);
- 父级节点
BOM
JavaScript高级
作业练习
分支
- 从键盘输入小明的成绩,当 为100时,奖励一辆BMW,当成绩为80~ 99时,奖励一台iphone15s,当成绩为60~ 80时,奖励一本参考书,其他时,什么 奖励也没有
- 女方出嫁,如果男方高180以上,富1000万以上帅500以上,这三个 条件同时满足,则一定要 嫁,如果三个条件有一个真,则嫁吧,比上不足比下有余,如果三个条件都不满足,则不嫁
- 键盘输入三个数,按照 从大到小的顺序输出
循环
- 打印1~100之间所有的奇数之和
- 打印1~100之间所有7的倍数的个数及总和
- 打印所有的水仙花数(一个三位数,每个位数数字的3次幂之和等于它本身)
- 在页面中接收一个用户输入的数字,并判断该数字是否是质数(只能被1和本身整除的数,必须是大于1的自然数)
- 用户输入班级人数,之后依次输入每个学生的成绩,最后打印该班级的总成绩和平均成绩
- 打印有*组成的三角形,菱形,矩形
- 打印九九乘法表
- 打印 出1~100之间的所有质数
- 求 1~100之间,除了能被7整除之外的整数和
- 求整数1~100的 累加值,但要求跳过所有个位数为3的数
- 接受用户输入的用户名和密码,若用户名为admin,密码为123,则提示用户登录成功,否则,让用户一直输入
函数
- 使用函数求1到100的累加和
- 利用函数求任意两个数的和以及两个数之间所有数的和
- 封装函数求两个数中的最大值
- 利用函数求任意个数的最大值
- 利用函数翻转数组
- 利用函数判断闰年
- 函数封装冒泡排序
- 输入年份,判断二月份的天数
对象
- 创建一个电脑对象,该对象要有颜色、重量、品牌、型号,可以看电影、听音乐、打游戏和敲代码
- 创建一个按钮对象,该对象中需要包含宽、高、背景颜色和点击行为
- 创建一个车的对象,该对象要有重量、颜色、牌子,可以载人、拉货和耕
正则表达式
- 使用正则验证用户输入的电话号码是否合法
- 使用正则验证用户输入的邮箱是否合法
- 对于用户输入的字符串,去掉前后的空格,中间的空格不用去