HTML,CSS,JavaScript知识点
HTML篇
HTML是超文本标记语言。文件以.html结尾。
Hello,HTML。
常用的工具:
标题:
<h1>一级标题</h1> <h2>二级标题</h2> <h3>三级标题</h3> <h4>四级标题</h4>
无序列表和有序列表
<ul> <li>天气</li><li>天气1</li><li>天气2</li><li>天气3</li><li>天气4</li><li>天气5</li> </ul><ol><li>天气</li> <li>天气1</li> <li>天气2</li> <li>天气3</li> <li>天气4</li> <li>天气5</li> </ol><dl><dt>男</dt><dd>女</dd><dt>男1</dt></dl>
段落标签和文本标签
<P>这是一个段落</P><span>这是一个文本</span>
表单和按钮:
border属性为表格添加一个边框样式。
cellpacing 设置单元格间距
<form action="">Input account<input type="text" name="account"><br>Input password<input type="password" name="password"><br><input type="radio" name="sex" value="1" checked>男 <input type="radio" name="sex" value="2">女<br><input type="checkbox" name="check" value="1">1<input type="checkbox" name="check" value="2">2<input type="checkbox" name="check" value="3">3<br><select><option value="1">option1</option><option value="2">option2</option><option value="3">option3</option><option value="4">option4</option></select><br><textarea row="200" cols="50">Please input text</textarea><br><input type="submit" name="submit"><button name="save">save</button></form>
input属性中有placholder属性可以添加提示信息。
插入图片
<div style="background-image: url(../img/1wRYvu3TIo7EVQZ.jpg);" class="class1"></div><style>.class1 {background-size: cover;height: 100%;width: 100%;position: fixed;background-repeat: no-repeat;background-attachment: fixed;/* 水平向上移动 */background-position: 0 -60px;} </style>
链接:
<a href="www.baidu.com" target="_self">点击进入</a><a href="www.baidu.com" target="_blan ">点击进入</a>
布局(合并单元格):
行合并:
<tr><td rowspan="2"><b>崔颢</b></td><td>黄鹤楼</td><td><a href="">查看诗文</a></td></tr>
列合并:
<tr style="background-color: rgb(211, 207, 121);"><td colspan="3" align="right"><a href="">查看更多崔题</a></td></tr>
成品:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><div style="text-align: center;"><h2>效果图</h2><table border="1" cellpadding="10" height="50" width="800" align="center" cellspacing="0"><tr><td rowspan="2"><b>崔颢</b></td><td>黄鹤楼</td><td><a href="">查看诗文</a></td></tr><tr><td>《题沈隐侯八咏楼》</td><td><a href="">查看诗文</a></td></tr><tr style="background-color: rgb(211, 207, 121);"><td colspan="3" align="right"><a href="">查看更多崔题</a></td></tr></table></div></body></html>
CSS篇
CSS:层叠样式表。实现内容和样式分离。
选择器:
- 类选择器:
.style1{ text-align:center}"style1">这是一个段落
- id选择器:
#style1{text-align:center}"style1">这是一个段落
元素选择器:
让整个页面的相应元素采用该样式
p{text-align:center}"style1">这是一个段落
通配符选择器:
让这个页面中的所有标签都执行该样式
*{margin: 0;//外间距padding: 0;//内间距}"style1">这是一个段落
CSS中常用的修饰:
{font-family: 字体的类型(楷书...)font-size: 字体的大小font-style: 字体的形状(正,斜)font-weight: bolder 字体的加粗text-decoration: underline 下划线}
样式引入的方式有三种
内部样式
通过在html文件中写入style标签,将样式写在改标签内
a{}
外部样式
专门新建一个css文件,然后通过html文件中的link标签引入
<link rel="stylesheet" href="./css/index.css">
行内样式(标签内样式)
通过标签的style属性写入样式
<div style="height:200px;background:red"></div>
当三种方式向同一个标签写入相同的样式,但是样式值不一样,这个时候行内样式优先显示,其次内部样式,再其次外部样式
盒子模型:
布局:
浮动布局
浮动布局是使用css中提供的一个叫做float的样式来完成页面布局的一种布局方式,相较与之前的table布局,它存在的优势就是可以很好的利用到标签的语义去标记内容,不会像table中只有单元格标签这种单一出现,可以很好的帮助我们实现SEO的优化
简单来说:
float可以快速的将块元素进行左右布局的排列,需要进行左右布局的盒子每一个都需要给上一个float样式,但是float也会带来一些问题
脱流
当一个元素被赋予了float样式之后,这个元素会脱离当前文档流
文档流:
所谓文档流我们可以理解成是一个文档对于其内部的内容的表现形式的约束,而html文档的内容也就是标签(元素)的表现形式进行了约束
脱离标准文档流:
元素不再受当前文档流的表现形式的约束,也就是说,脱流的元素不会再受到标准文档流当中对于元素显示类型的约束,那么块元素脱流之后就不会再受块元素独占行的表示形式的约束,即然不再独占行,那么就只有分享行了
脱流元素的去向:
元素我们可以理解成就是一个数据,数据这个东西一定需要一个存储空间,不然数据是无法长久持续的,但是我们现在可以看到给了浮动的元素依然还是可以在页面上正常显示的,我们认为,脱离了标准文档流的元素会进入到浮动文档流当中,进入到浮动文档流后就直接分享行,后续继续进入当浮动文档流的元素就会挨个并排排放,直到一行排满另起一行
脱流会造成父元素高度崩塌
当我们的子元素受浮动影响脱流之后,原本有子元素撑起的父元素的高度会丢失,父元素高度的丢失会造成父元素的兄弟元素跟着一起发生位置,导致整体布局崩塌
清浮动
我们有一个专门用来清浮动的样式 clear:both
但是我们并不会使用这个样式来清除浮动,因为它会造成一个问题,会凭空造就一个空标签出来,所以不用
现在我们采用如下方式
.clear{overflow: auto;}
将以上样式赋予到给了浮动样式父元素上,来清除子元素上的浮动
定位布局
使用position实现定位布局,定位布局分为多种方式,我们先看以下三种:
绝对定位absolute:
决定定位会造成元素脱流,与上面的浮动一样,也会造成元素不受标准文档流的约束,也就是说会丢失块元素宽度100%的样式,但是依然可以通过样式来设置宽高,也可以通过内容来撑宽高
相对定位relative:
相对定位不会造成元素的脱流,元素依然在标准文档流内,所以不会影响布局结构,但是一般我们并不会使用相对定位来进行位置偏移,更多的时候是为了使用z-index或者去限制决定定位的定位范围
应用点:
实际工作中,我们并不太会直接使用绝对定位或者相对定位,当我们需要通过定位来移动布局元素的时候,我们会采用一种叫做父相子绝的方式来使用,通过这种方式,我们可以将绝对定位的定位原点修改成父元素的大小范围内,并且给父元素设置一个固定尺寸,来解决决定定位因为无法确定浏览器窗口宽度而造成的视觉上的偏移位置不正确的清除
固定定位fixed
固定定位对元素带来的影响与绝对定位一样都会脱流,但是固定定位的定位范围是永远都固定在浏览器窗口上的,所以随着浏览器窗口的滚动,固定定位的元素也会跟着一起滚动
当我们使用定位样式之后,可以使用如下几个样式
left:从左向右偏移
right:从右向左偏移
top:从上向下偏移
bottom:从下向上偏移
z-index:修改定位元素之间的叠加的前后关系的,取值范围9999 至 -9999
背景:
backgroud时一个复合样式。
backgroud-color 背景颜色,会默认铺满整个内容区域。
backgroud-image:url(路径) 背景图片,通过路径将一张图片作为背景图片引入。
backgroud-position 背景图片定位。
backgroud-repeat 背景图片是否铺满。
backgroud-size 控制背景图片的尺寸
JavaScript篇
变量标识符var
变量本质上来讲是一个容器,一个数据的临时储存空间,这个储存空间是由内存(ram)提供的,所以当我们的程序越大可能会需要占用的内存空间就越大,所以我们经常会发现当我们电脑打开多个程序的时候会变的越来越卡的原因之一就是这个
在ES5.1中我们使用一个叫做var关键字来顶一个变量
var 变量名;
变量的命名是有要求的,不要随便取名,整个取名过程有一个大前提就是经量表意
1、以字母或者_或者$开头,不能使用数字开头,后面可以添加任意字母或者数字,下划线,美元符号
var a;var _a;var $a;var 1a; //错误var a-b;//错误
2、变量名不能是系统的关键字或者保留字
3、变量名一定要做到一定的规范
见名知意:我们看到这个变量名就大概知道里面装的是什么
约束规范:ES的变量名采用小驼峰写法,如果你的名字是一个组合单词,第一个单词首字母小写,后面单词的首字母大写
变量是可以批量声明的
var age,name,sex;
变量的赋值
变量第一次赋值也交变量的初始化
var age;age = 10;//也可以批量声明然后挨个赋值var age,name,sex;age = 10;name = "zhangsan";sex = "女"
上面的我们完成变量的赋值
1、变量的赋值使用 =
来完成
2、变量应该先声明再赋值
变量里面储存的数据是可以被替换掉的,也就是可以进行二次赋值
var age,name,sex;age = 10;age = 20;
上面我们执行了一个二次赋值的过程,后面赋的值会把前面赋的值替换掉
现在我们是把声明+赋值分成两步来完成的,我们可以简化以下
var age = 19; //变量初始化
JavaScript中的数据类型
在js中我们通过不同的需求,可能会使用到一些不同的数据,这些数据都需要进行一个类型上的描述,不同类型的数据在处理方式和过程上都会有一些不一样
在js中数据类型可以分成两个大类
基础数据类型(原始数据类型)
- 字符串String
- 数字Number
- 布尔 Boolean
- 空类型 null
- 未定义类型 undefined
应用数据类型(复杂数据类型)
- object
字符串类型 String
字符串类型是js中最常见的数据类型,它使用引号来包裹(所有被引号包裹的我们都可以看作是一个字符串)
字符串类型的最大特征就是在执行过程中,只会按字面量来进行输出
var userName = "今天天气不错";var age = "18"
注意:
不管单引号还是双引号,只要被引号包裹住就是字符串,但是有一个情况,如果出现了单双引号嵌套的情况,单引号不能包裹双引号,双引号可以包裹单引号
var txt = "爸爸说:'你今天没有挨打吧',我说:'是感觉今天好像少了点什么'"
数字类型Number
数字类型在js中是表示数字的数据,最常见的就是我们的10进制数
var age = "18"var height = 180;var money = -2000;
在其他编程语言里面,有int、float、double 等等不同的数字类型,但是在js里面统一就一个数字类型,在js里面是没有这些概念的
数字是有进制的
var a = 100;var v = 0x100;
在16进制中,所有0x开头的都是16进制,赋值完成之后,会把后面的10进制数转换成16进制
在Number类型中我们有一个特殊的数字类型NaN
NaN是一个非常特殊的数字,它的全称 Not a Number
所有无法用阿拉伯数字表示的Number类型数字都用NaN表示
布尔类型
布尔类型的值在大多数计算机语言中都是具备的,这个类型的数据只有两个分别是true和false
var isBoy = false;var isGirl = true;
未定义类型undefined
未定义类型,在js中表示定义了变量但是不赋值,这个时候,变量里面装的就是未定义类型,该类型下只有一个值undefined
空类型null
null与undefined有一点点类型,但是它是作为描述不存在,没有,无这些概念的一个值,该类型就一个值null
var a = null;
JavaScript的弱类型机制
刚刚我们学习了5中基础数据类型,那么,一个变量如果定义的时候赋值了一个数据,而数据都有自己的类型,那么我们后面可以不可以再次改变这个数据类型
var a = "haha";a = 100;
在js当中,变量是没有固定的数据类型的,变量的数据类型会根据你赋值的数据类型的变化而变化,我们赋什么值,变量就是什么类型,这种变化就是js的弱类型机制
比如说一些强类型语言中,在定义变量的时候就需要提交数据类型的声明
int a;float b;long c;string d;boolean e;
强类型语言在定义变量的时候,必须要声明变量的数据类型,比如java、c#、c++等等
弱类型语言就不需要定义变量的时候定义数据类型,比如JavaScript中的var
即然变量的数据类型是会发生改变的,那么我们应该如何知道某一个变量内部的数据类型呢?
数据类型检测
在JavaScript中,数据类型的检测我们使用关键字 typeof
var num = 1;var str = "jaja";var boo = true;var und = undefined;var nul = null;console.log(typeof num); //numberconsole.log(typeof str); //stringconsole.log(typeof boo); //booleanconsole.log(typeof und); //undefinedconsole.log(typeof nul); //object 注意:null检测的类型为object不是null
typeof使用方式
typeof 变量名
还有另外一种方式
typeof(变量名)
数据类型转换
JS里面5种原始数据类型是可以相互之间转换的,一般情况下我们会对字符串、数字、布尔来进行转换
字符串转十进制数字
这个转换是有条件的,它必须是数字字符串
var a = "123";var b = "abc123";var c = "123abc";var d ="123.123";var e = "-123";var f = "hello";
上面我们声明了6个变量,都赋值了字符串进去,然后我们通过转换方法来看下执行结果
string转number有三种方法
1、Number()
var a1 = Number(a);var b1 = Number(b);var c1 = Number(c);var d1 = Number(d);var e1 = Number(e);var f1 = Number(f);
代码分析:
经过上面的实验,我们发现,Number方法只能转换合法的数字字符串,当字符串转换的时候字符串中包含了非数字的字符时,统一转换成 NaN
2、parseInt()
var a1 = parseInt(a);var b1 = parseInt(b);var c1 = parseInt(c);var d1 = parseInt(d);var e1 = parseInt(e);var f1 = parseInt(f);
代码分析:
经过上面的转换对比,我们得到一个结论,parseInt在转换过成中遵循以下两个情况
1、如果字符串是数字和字母的混合,在转换过程中,对字符串中的字符逐个转换,如果一开头就碰到了非数字的字符就直接放弃掉后面字符的转换直接返回一个NaN的结果,如果开始是数字,那么就逐个转换直到碰到非数字字符,停止转换把后面未转换的抛弃掉
2、如果转换的数字有小数点,直接把小数点后面的数字全部抛弃,不会做四舍五入
3、parseFloat()
这个方法和上面的parseInt的转换机制是一样的,除了针对小数点后面数字的保留以外
总结:
- Number方法的转换是一个整体转换,它必须整体是一个合法的数字字符串,而perseInt和perseFloat是逐个字符转换
- Number在转换的时候对原数据类型是没有要求的,但是parseInt和parseFloat必须是字符串才行
布尔转十进制数字
var a = true;var b = false;var a1 = Number(a); //1var b1 = Number(b); //0
null和undefined转十进制数
var a = null;var b = undefined;var a1 = Number(a);//0var b1 = Number(b);//NaN
其他类型转换字符串
在js里面,所有的数据类型都可以转换成字符串,并且转换过程最简单(直接加引号)
var a = 123;var b = true;var c = 123.123;var d = null;var e = undefined;
上面5个变量就是我们除了字符串以外的4个原始数据类型,这个数据都可以转换成字符串的,并且方法很多
1、String()
使用String方法进行转换之后,以上5种情况的结果均按照字面量来进行转换
2、通过加法计算符来进行转换(隐性转换)
var a = 5 + "";
代码分析:
这种做法是利用了 + 一个特性,当 + 的左右变量其中有一个是字符串类型的数据, + 会隐性转换另外一边的数据为字符串类型,然后 + 会作为字符串连接符,将 + 两边的字符串进行一个合并,而我们这里右边设置的是一个空字符串,根据上面我们所说的特性,+ 会把左边的数字5转换成一个字符串,然后拼接上右边的空字符串,从结果上来讲,得到了一个字符串5
3、toString()
var a = true;var a1 = a.toString();//"true"
注意:
这个方法不能转换null和undefined,控制台会报错,要转换null和undefined只能通过String()
★★★其他类型转布尔
这个转换过程在js中比较麻烦,因为布尔值在后面的语句的流程控制当中使用非常频繁
在js我们要让其他类型转换成布尔类型使用 Boolean()
var a = 1;var b = 0;var c = "";var d = "123";var e = null;var f = undefined;var g = NaN;var h = 123;var i = "true";var j = "false";
上面的变量,基本覆盖了所有的原始数据类型,和可能出现的组合,并且还有一些特殊值,通过Boolean转换一下
var a1 = Boolean(a); //truevar b1 = Boolean(b); //falsevar c1 = Boolean(c); //falsevar d1 = Boolean(d); //truevar e1 = Boolean(e); //falsevar f1 = Boolean(f); //falsevar g1 = Boolean(g); //falsevar h1 = Boolean(h); //truevar i1 = Boolean(i); //truevar j1 = Boolean(j); //true
结论:
在整个js体系中,能够被描述成false的只有6个值,这个6个值是可以明确为false的,分别是 0,空字符串,false,NaN,null,undefined
流程控制语句:
if语句
if也叫判断语句/条件语句/分支语句 ,它会根据一个条件来决定执行相应的代码块,控制代码流程的走向,它使用关键字 if else
if(判断条件){条件成立时执行的代码}else{条件不成立时执行的代码}
举例:
var money = 10;if(money == 0){console.log("我要找人蹭饭")}else{console.log("吃大餐")}
根据上面的例子我们可以得到一个结论:
if(一可以得到布尔值结果的表达式){判断条件为true时执行的语句}else{判断条件为false时执行的语句}
但是我们要注意一点,因为JS是弱类型语言,执行的是弱判断,所以if后面的条件可能并不是一个布尔值,如果不是则通过Boolean方法转换
var money = 0;if(money){console.log("吃大餐")}else{console.log("我要找人蹭饭")}
上面的代码最终输出的是 我要找人蹭饭
因为 Boolean(money)转换为false,所以条件不成立,执行else后面的语句
结论:
if(可以得到任意数据类型结果的表达式){判断条件为true时执行的语句}else{判断条件为false时执行的语句}
场景: 如果明天是晴天,我们就去郊游,如果明天下雪,我们就放假,否则我们明天来教室上课
var weather = "晴天";if(weather == "晴天"){console.log("郊游")}else if(weather == "下雪"){console.log("放假")}else{console.log("上课")}
小练习:
当时间在0-8之间(包含8点)输出上午好,8-12点的时候(包含12点)输出中午好,12-18点的时候(包含18点)输出下午好,18-24点之间的时候,输出完好
提示:结合逻辑运算编写判断条件
var time = prompt();if(time > 0 && time <= 8){console.log("上午好");}else if(time > 8 && time <= 12){console.log("中午好")}else if(time > 12 && time <= 18){console.log("下午好")}else if(time > 18 && time <= 24){console.log("晚上好");}else{console.log("请输入正确时间")}
for循环语句
for是作为启动循环功能的关键字,它可以让程序在某一个条件下不停的执行下去
场景:
现在小明同学,要帮老师去搬砖,要搬10块砖,但是小明的力气很小,只能一块一块的搬,搬完就可以去吃饭了
针对上面场景,我们可以分析出来一下几个数据
1、小明从第一块砖开始搬
2、一共要搬10块,没有达到10块不能停下来
3、小明力气比较小,只能一次搬1块
for(初始值;循环条件;自变量){//代码体}
现在通过以上的语法结合上面的场景完成我们的代码,用编程的思维来帮助小明板砖
//初始值:小明从第一块开始搬,所以我们需要设置一个初始量来记录板砖的数量//循环条件:小明需要搬10块转,不搬完不让休息//自变量:小明力气小,只能一次搬一块for(var i = 1; i <= 10; i++){console.log("小明同学正在板砖,现在搬到了第" + i + "块")}
代码分析:
上面的代码循环执行了10次,那么在循环执行得到10次里面,代码到底是怎么执行的?
通过断点分析,我们发现循环语句在执行的时候,它所循环的代码都会经过下面三个部分
1、判断循环条件是否成立
2、执行代码体
3、自变量变化
场景修改:小明给老师搬砖,要搬10块砖,之前已经搬了3块,现在从第4块砖开始,每次搬1块
for(var i = 4; i <= 10; i++){console.log("小明同学正在板砖,现在搬到了第" + i + "块")}
场景修改:小明给老师搬砖,要搬6块,从第1块开始
for(var i = 1; i <= 6; i++){console.log("小明同学正在板砖,现在搬到了第" + i + "块")}
场景修改:小明给老师搬砖,要搬10块砖,从第1块开始,小明练出来了力气大了,每次可以搬2块砖
for(var i = 1; i <= 10; i += 2){//i = i + 2;console.log("小明同学正在板砖,现在搬到了第" + i + "块")}
循环倒序
for(var i = 10; i > 0; i--){console.log("小明同学正在板砖,现在搬到了第" + i + "块")}
while循环
while也是一种循环,它与for一样都是前测试循环,它和for循环差不多,语法如下
while(循环条件){// 循环体}
注意:
所以的循环都需要基初始值,循环条件,自变量
var i = 1;while(i <= 10){console.log(i);i++;}
注意:
while循环的循环条件不一定非要是一个布尔值
var i = 10;while(i){console.log(i);i--;}
do…while循环
do…while 是一个后测试循环,先执行循环体,然后再判断循环条件是否成立
do{//循环体}while(循环条件)
运算符
一元操作符
只能操作一个值的操作符我们叫做一元运算(一元操作符),一元操作在js中是比较简单的,一个元操的结果一定是一个Number类型
递增递减运算
var a = 3;var b = 3;b++; // b = b + 1;a--; // a = a - 1;
上面的计算过程我们可以的到b为4,a为2,所以我们认为递增或者递减运算就是在原有的数据基础上执行一个+1 或者 -1 的操作
使用递增或者递减运算的时候,运算符出现的位置也会影响计算结果
**思考:**放在前面和放在后面的区别?
var a = 3;var b = 3;a++;//a = a + 1 先调用变量a,然后执行一个加法运算,然后把运算结果赋值++b;//b = b + 1 执行一个加法运算,然后把运算结果赋值,然后调用一遍变量bconsole.log(a) //4console.log(b) //4
上面的代码经过运算之后,我们发现两个变量a和b都是4,难道符号在前在后没有区别?
我们把执行过程稍微改一下
var a = 3;var b = 3;console.log(a++);//先调用一遍变量a,在控制台输出,然后执行了一个加法运算,最后再把运算结果赋值给aconsole.log(++b);//先执行了加法运算,然后把结果赋值,最后再调用一遍在控制台输出
简单理解:
符号在后,先使用这个变量,使用完之后再去改变这个变量
符号在前,先改变这个变量,再去使用这个变量
练习: 根据上面的特点来完成一个小练习
var a = 11;var b = 12;var c = 13;a++;//12++a;//13var d = a++ + ++b + c++ + ++a;//13+13 + 13+ 15;//问:a,b,c,d的值各为多少// a:15// b:13// c:14// d:54
注意:
递减运算也是一样的规则,只不过是 -1 不是 + 1
特殊类型值的递增递减运算
var a = "1";//a++ 2var b = "hello";//b++ NaNvar c = ""; //c++ 1var d = true; //d++ 2var e = false;//e++ 1var f = null; //f++ 1var g = undefined;//g++ NaNvar h = NaN;//h++ NaN
递增递减操作原则
1、一元操作符的结果一定是Number类型
2、如果是非数字类型的数据执行递增或递减运算,则先将这个值进行Number()的操作进行转换
3、NaN不参与运算,即使参与了结果也一定是NaN
4、符号在后,先使用自己,再改变自己,符号在前,先改变自己,再使用自己
一元加减运算
一元加减运算是js中比较特殊的运算,它相当于执行Number方法的类型转换,它的结果也是一定是Number类型
var a = "01";a = +a; //1var b = "1.1";b = +b; //1.1var c = "z";c = +c; //NaNvar d = false;d = +d; //0var e = 1.1e = +e; //1.1var f = NaNf = +f; //NaNvar g = null;g = +g; //0var h = undefined;h = +h; //NaN
以上是一元加法运算,一元减法其实也是一样的,只不过把转换结果变为负数
一元加减法的操作原则
1、结果一定为Number类型
2、非数字类型的数据通过Number()转换
3、NaN不参与运算
4、如果是一元减法,则是通过Number()转换,再乘以 -1
加法运算
加法操作符使用 + 来表示,加法运算的结果不一定是Number类型
因为JS是一个弱语言,所以在执行加法运算的时候,不一定是数字相加,还有可能是其他类型相加
console.log("hello" + "world");console.log(1 + 2);console.log("1" + 2);console.log(true + 1);console.log(true + "1");console.log(NaN + 1);console.log(NaN + "1");console.log(1 + undefined);console.log("1" + undefined);console.log(true + true);console.log(null + true);console.log(true + undefined)
加法运算操作原则:
1、如果执行加法的时候有字符串,则结果一定是一个拼接型的字符串
2、NaN不参与运算,如果参与,计算结果一定为NaN
3、对于非数字类型的数据进行加法运算的时候,则使用Number方法进行转换
隐藏规则:在加法运算中,字符串是老大,NaN是老二,加法字符串优先
小练习:
console.log(1 + "2" + "2");//"122"console.log('1' + 2 + 3);//"123"console.log(1 + +"2" +"2");//"32"
减法运算
减法操作符使用 – 表示,减法运算的结果一定是Number类型
2 - 1 //1"2" - 1 //1"2" - "1" //1"2" - true//12 - false //21 - null//12 - undefined //NaNnull - true //-1"a" - 1 //NaN"a" - "b" //NaN"" - 1//-1"" - NaN//NaN
减法操作原则
1、减法运算一定会得到Number类型的结果
2、NaN与任何数相减都是NaN
3、如果执行的减法不是数字,则通过Number方法做隐式转换
乘法运算
乘法运算符 * 表示,它的结果与减法操作保持一致
除法运算
除法运算符 / 表示,它的结果与减法保持一致
除法中有个特殊情况
console.log(2 / false);//Infinityconsole.log(1 / null); //Infinity
取余(取模)运算
取模运算使用 % 符号表示,它的结果也一定是number类型,也遵守和减法操作原则
console.log(2 % false);//NaN
逻辑运算
逻辑非运算
逻辑非运算使用 !作为运算符,执行的结果是把既有的运算结果进行反向输出,非真即假,非假即真,运算结果一定是一个布尔值
举例:
var a = true;var b = !a;
因为JS是弱语言,所以在逻辑运算中,不一定参与的运算的是布尔类型的数据,有可能是其他类型
逻辑非操作原则:
1、结果一定是布尔值
2、非真即假,非假即真
3、如果操作的值不是布尔类型,则通过Boolean()去转换
4、有哪些能明确表示为false的值一定要记住
逻辑与运算
逻辑运算使用 && 来表示,执行的是一假则假,全真为真的原则,它的结果不一定是布尔值
举例:
var a = true && false//falsevar b = true && true //truevar c = false && false //falsevar d = true && false && true//false
因为JS是弱语言,所以在运算过程中,参与运算不一定是一个布尔值
console.log(true && 123);//123console.log(true && "" && 123);//“”console.log(123 && "abc"); //"abc"
逻辑与操作原则:
1、它的结果不一定是布尔值
2、执行所谓的“逢假则假,全真为真”的操作
3、短路原则:当一个表达式如果得到false的结果就不再向后运算了,然后当前为false的表达式的计算结果为逻辑与运算的结果,如果没有短路,在执行过程中没有碰到任何一个表达式的执行结果可以转换为false,就以最后一个表达式的执行结果为逻辑与运算的结果
4、当逻辑与运算中的表达式的运算结果不为布尔值的时候,我们执行Boolean方法来转换决定是否继续执行
5、明确哪些是可以作为false条件的数据
逻辑或运算
逻辑或运算的操作符 || 表示,执行的是一真全真,跟逻辑与正好相反,结果也不一定是布尔值
var a = true || false
逻辑或操作原则:
1、结果不一定是布尔值
2、执行所谓“一真全真”
3、短路原则:与逻辑与运算相反
4、当逻辑或运算中的表达式的运算结果不为布尔值的时候,我们执行Boolean方法来转换决定是否继续执行
5、明确哪些是可以作为false条件的数据
小练习:
true || "" && NaN//true123 || !"" || false//123123 && NaN || null//null"abc" || 123 && NaN //"abc"null || 123 && undefined//undefined
当逻辑运算进行混合运算的时候,就需要考虑类似四则运算中先乘除后加减的情况
注意:
逻辑运算,先非,再与、最后或
比较运算
比较运算的运算结果一定是布尔值
因为JS的弱语言特性,在比较运算中,不一定运算的过程中只有数字
运算符
> 大于=大于等于<=小于等于==等于=== 完全等于!=不等于!== 完全不等于
比较运算的一个特殊情况:
运算双方不是数字,这个时候比较运算会根据ASCII码表的值进行大小比较
函数:
函数的定义
函数的定义方式非常多,目前我们只看一种标准方式,语法如下
function 函数名(参数,...){函数体}
注意点:
1、关于函数名,参照标识符的规范
2、函数体,函数的代码体,里面可以是任意的多条语句
3、后面的大括号不能省,声明一个函数出来就必须要有函数体
function sayHello(){console.log("hello");}
上面就是一个函数的声明,这个过程我们也叫封装了一个方法
函数的其他声明方式
var 函数名 = function(){}
具体写法
var sayHello = function(){console.log("hello");}
注意:
上面两种声明方式看似一样,实际是有区别的
abc();function abc(){ console.log("hello abc")}def();//这个会报错var def = function(){ console.log("hello def");}
通过第一种方式声明的函数,可以在定义之前调用,而通过var定义的函数在定义之前调用会报错
匿名函数:
var multiply = function(x, y) {return x * y;};
递归函数:
function abc(){abc();}
函数的调用
函数定义好之后不会自己执行,它需要经过调用才会被执行,函数的调用时通过函数名来完成的,语法如下
函数名()
注意:
函数的实际调用过程中,我们需要注意,函数名与后面的小括号实际上分别指代两个操作
function test(){console.log("a")}test()//调出函数体中所包含的代码,并执行test//调出函数,但是不执行
代码分析:
如果只写函数名不写后面的小括号,我们会发现,函数体内的代码并没有被执行,也没有报错,原因在于没有写后面的小括号,我们可以把小括号理解成一个函数的立即执行符,前面的函数名负责调出函数体中的代码,后面的小括号负责执行
也就是说,我们可以把调用函数的语法理解成由两个部分组成,分别是调(函数名)和用(小括号)两个部分来看
函数的参数
现在有如下情况
var userName;function sayHello(){console.log("大家好,我叫" + userName);}userName = "张三"sayHello();userName = "李四"sayHello();userName = "王五"sayHello();
当我们调用sayHello的时候,我们可以根据变量userName去动态改变打印的结果
这个时候userName的变量定义在函数体外,如果定义在函数体内,我们无法多次赋值,同时定义在函数体内的变量我们叫做局部变量,这种局部变量无法跨域使用,声明在函数体外的变量我们可以叫做全局变量,全局变量可以在任意的函数中调用
引入参数使用让函数变的更灵活
function sayHello(name){console.log("大家好,我叫" + name);}// userName = "张三"// sayHello();// userName = "李四"// sayHello();// userName = "王五"// sayHello();sayHello("张三");//name参数的值被赋值为张三sayHello("李四");sayHello("王五");
有函数的引入,函数的作用的灵活性会有很大的提升
形参和实参
形参:形式参数,指的就是在函数声明的时候在小括号里面写的参数名
实参:实际参数,调用函数的时候,小括号里面写的值
function add(a,b){console.log(a + b);}//上面的函数中,a和b就是形参,没有具体的值,也没有具体的类型add(4,5)//在调用过程中,给a和b赋值了实参,现在的4和5就是a和b的实参
也可以是以下情况
var x = 5;var y = 7;function add(a,b){console.log(a + b);}add(x,y);
这个时候,x,y就是add函数的实参,因为x,y中是有实际的值
通过这个调用add的时候,实参x赋值给了a,实参y赋值给b
函数的形参与实参是没有必要形参一一对应的关系
add(100);add(10,20,30)
函数的重载
函数的重载指的是在定义的时候,它的函数名相同,而函数的参数个数不同同时参数的类型也不同,这个时候,同名的函数就叫做函数重载(overload)
javascript函数中不存在重载,只有强语言才有重载
function add(a,b){console.log(a + b)}function add(a,b,c){console.log(a + b + c)}
这个时候我们看到函数名相同,但是的函数的参数个数不同,我们的重载是根据参数的个数和参数的类型来决定的,但是在JavaScript当中,这两点都无法实现
1、js是弱类型语言,变量在没有赋值之前都是undefined,所以不能通过类型区别
2、js的函数中的形参和实参没有必要形成一一对应的关系,这个也无法通过参数的个数去区别
所以JS当中不存在重载,那么当出现同名的时候,会发生覆盖
返回值
函数的返回值是指函数将内部某些值返回到函数体外的一个操作,使用 return 关键字实现,js中的每个函数都可以有返回值
举例:
function abc(){return "hello"}var x = abc();
这个时候,abc函数会一个返回值 “hello” ,然后变量x接收了这个abc函数的返回值,所以x的值就是“hello”
function add(a,b){var c = a + b;return c;}var y = add(10,20);
这个时候变量y接收add函数的返回值,结果为30
关于return关键字
return是把函数内的值返回到函数外使用,同时,一个函数碰到了return就是跳出当前函数,return后面的代码不再执行
function add(){var a = 10 + 20;return ;console.log(a);}
return可以打断函数的执行,所以后面的console.log(a) 无法被执行
注意:
如果不需要返回值,但是依然使用了return,这个时候return就单纯当作一个函数打断的操作来执行,如果有返回值,那么return在把值返回到函数外同时还会打断函数执行
递归函数
如果一个函数在内部又调用了自己,那么这个函数我们就叫做递归函数
递归函数是可以通过循环来演示的,所以我们先看一个循环
//假设我们要求1-10之间整数求和var num = 0;for(var a = 1; a <= 10; a++){num += a;//num = num + a;}console.log(num);
如果我们需要把上面的循环转换成递归来表示,首先我们需要弄情况三个东西,初始值,循环条件、自变量
var num = 0;var i = 1;function add(){num += i;i++;if(i <= 10){add();}}add();
练习:斐波拉契数列
现在有一个数列,排列是这个样子的,1,1,2,3,5,8,13,21,34 … 问第N位应该是多少?
var a = 1;//前两项var b = 1;//前一项var c;//当前项function list(n){for(var i = 1; i <= n; i++){if(i == 1 || i == 2){c = 1}else{c = a + b;//完全当前项的计算a = b;//前一项变成前两项b = c;//当前项变成前一项}}console.log(c);}list(20)
递归方式:
function abc(n){if(n == 1 || n == 2){return 1;}else{return abc(n - 1) + abc(n - 2)//abc(4) + abc(3)// abc(3) + abc(2) +abc(2) + abc(1)// abc(2) + abc(1) + 1 + 1 + 1// 1+ 1+ 1 + 1 + 1}}
变量作用域
我们之前学习的时候通过 var 声明变量,这个var定义的变量是没有所谓的”块级作用域“的
var a = 123; //全局变量if(true){console.log(a);}//------------------------------------{var b = 123;}console.log(b);//-------------------------------------for(var i = 1; i <= 10; i++){var c = "haha";}console.log(c);
这几个变量其实全都是全局变量,这里的大括号并没有形成块级作用域,这一点是与其他编程语言不太一样的,但是注意一点,function的大括号会形成作用域
function abc(){var a = 1; //这是一个局部变量,只能在大括号内部调用,出了大括号就不能使用了}console.log(a);//这里会报错,a找不到
javascript函数(二)
arguments实参数组
我们讲过形参和实参没有必要形成一一对应的关系,形参是不介意你传多个实参的,也不在乎你传递的实参是什么类型,也就是说我定义两个形参,但是我们在调用函数的适合可以传递一个实参,或者三个,四个更多都是没有问题的
function abc(a,b){console.log(a);console.log(arguments[0]);console.log(arguments[2]);}abc(1,2,5)
arguments解析
这个arguments是一个类数组的概念
function abc(){console.log(Array.isArray(arguments));}abc()//false
代码分析:
通过isArray方法我们判断得到arguments并不是一个数组,但是它又可以使用数组的语法
数组与类数组有什么区别?
1、数组Array会有数组的方法,而arguments是没有的
2、它们都有langth属性和索引,而索引和length属性是数组才有的特性
总结:
在JavaScript中,当一个对象具备数组的特征(索引和length),但是不具备数组的方法,这种对象我们叫做类数组
注意:
arguments只能在函数体内使用,出了函数体就没用了
立即执行函数
当我们定义好一个函数之后立即执行,我们可以把这个函数看做是一个立即执行函数
var userName = "张三";var x = !function abc(){userName = "李四";console.log(userName);return userName;}();console.log(x);
代码分析:
上面的立即执行有一个问题,它不能返回内部的值到外面
前面的 !可以换成 +
函数表达式
var abc = function(){var userName = "张三";console.log(userName);return 123;}();console.log(abc);
代码分析:
这个时候,就不再是一个单纯的函数表达式了,因为这个表达式的等号右边的匿名函数现在是一个立即执行的状态
这个时候,赋值给abc的就不再是函数体本身了,而是函数的返回值
闭包函数
闭包函数,简单来说就是函数声明的嵌套,也就是在一个函数声明的里面再声明一个函数,要理解闭包要先了解变量作用域和JS垃圾回收机制
function a(){function b(){}}
JS垃圾回收机制
声明再函数体内的变量会在函数执行完毕的瞬间销毁掉
闭包例子:
function f(x){var a = x;//局部变量a = 5;var b = function(){ //声明函数breturn a; //在函数b内部调用a}a++; //递增运算 a = 6;return b; //返回函数b的函数体}var c = f(5);// function b(){return 6}
声明在函数体内的变量是局部变量,布局变量只能在函数提调用的时候才会被声明出来并且只能在函数体内调用,现在我们在外层函数f中声明一个局部变量a,又声明了一个函数b,并且在其内部调用了外层函数的局部变量a,现在会形成一个闭包表现
总结下来,要形成闭包:
在内部函数里面return外层函数的局部变量,在外层函数里面return内层函数的函数体
闭包函数的组用
闭包函数其实就是为了让内部函数调用外部函数的变量,然后让外部函数将内部函数的函数体返回出去,从而达到可以调用局部变量的目的
为了数据安全性
首先我们先明确一点,变量是用来储存数据的,同时由于作用域的关系,变量又分了局部和全局两种,作为全局变量来讲,它是运行任何人在任何时候进行访问的,那么对于储存在全局变量当中的数据的安全性就会有一定影响,而局部变量作为声明在函数体内的变量,自身有一个特性,当函数执行完毕之后会立即释放掉,同时因为作用域的关系,局部变量是无法被外部直接访问的,那么,我们就认为局部变量相比全局变量对于数据的安全性会更好,但是又由于会在函数调用结束后立即被释放掉,所以导致局部变量无法长时间的储存数据
现在我们即需要局部变量的相对的安全性保障,同时又需要局部变量不能在函数执行完毕之后被垃圾回收机制回收掉,所以就出现了闭包函数的调用形式,这种形式下的函数执行完毕之后,其内部的局部变量不会被回收
使用闭包的注意事项
闭包虽然可以解决一些问题,但是也会带来一些问题,由于闭包会携带外部函数的局部变量,所以内层占用是比较大的,所以经量少用,慎用闭包
1、因为闭包的使用内存里面会一致维持一个变量存在,可以用做缓存,也可以用作提高数据安全性
2、函数内部的局部变量不会被垃圾回收机制销毁,增大内存消耗,导致内存泄漏,解决方案就是可以使用完之后手动给变量赋值null,或者通过delete关键字手动删除
闭包执行
function a(){var userName = "zhangsan";console.log(userName);}a();
上面的代码,a代表函数名,我们在函数名后面加上小括号就可以执行了,所以把上面的代码稍微改下
(function a(){var userName = "zhangsan";console.log(userName);})()
这里其实我们可以认为就是一个立即执行函数,只不过没有采用!或+ 的方式来进行而已
回调函数
回调函数就是把一个函数作为另一个函数的实参传入,被当作实参传入的函数我们就叫回调函数
1、当某些功能需要分段执行的时候(流水线操作)
2、当一个函数的返回值无法返回的时候
function abc(x,y,z){var a = x + y + z;var b = x - y - z;return [a,b];//如果我们需要把上面的a和b同时返回出去,目前来看只能通过数组的方式}
我们通过回调完成
function abc(x,y,z,callBack){var a = x + y + z;var b = x - y - z;callBack(a,b);}function print(_a,_b){console.log("三个数的和" + _a);console.log("三个数的差" + _b);}abc(10,20,30,print);
数组
数组实际上就是一个数据集合
JavaScript当中的数组与其他强类型语言的数组再概念上是有区别的
1、强语言数组会规定当前数组的长度,并且数组中所储存的数据类型也是规定死的
2、在JS当中,因为是弱类型语言,根据弱类型的特征,数组的长度与数组所储存的数据类型都没有强制规定
数组的定义
数组分几种方式可以创建:
通过 new Array()创建
var arr = new Array(); //创建了一个空数组,这个数组里面什么都没有,长度为0
这里有一个new关键字,这个关键字在后面构造函数中来讲
new Array() 代表创建了一个Array实例(数组对象)
var arr = new Array(6); //创建了一个空数组,但是这个数组有一个默认的长度为6
var arr = new Array(1,2,3,4,5);//创建了一个带有1,2,3,4,5的数组
注意事项:
var a = new Array("a");//创建了一个数组,这个数组包含了字符串avar b = new Array("2");//创建一个数组,这个数组包含了字符串2var c = new Array(-1); //报错var d = new Array(3.5);//报错var e = new Array(-2,-1);//静态化创建
通过 [ ] 的方式创建数组
var arr = [];var arr1 = [1,2,3,4,5,6];
数组的类型检测
我们检测数据类型的时候使用 typeof
var arr = [1,2,3];console.log(typeof arr);//object
这个时候检测出来的类型是object,检测结果不标准,我们只能知道一个大概,后期学习的很多东西通过typeof检测出来的都是object,所以我们认为typeof只适合检测原始数据类型
instanceof
instanceof与typeof的区别,typeof是直接告诉你检测的数据类型结果,而instanceof则是通过关键字的右边设置类型,左边设置要检测的数据看是否匹配,来给出一个true或者false的结果
var arr = [1,2,3,4];arr instanceof Array//true
通过Array.isArray() 来判断
这里是数组专门提供的一个方法,来检测当前变量里面是否包含了数组
var arr = [1,2,3,4];Array.isArray(arr);
数组的取值和赋值
数组的取值与赋值都是通过下标(索引)来完成,索引从0开始一次对应每一个数组中的元素 + 1,通过
数组名[索引值]
来完成取值与赋值,数组里面的值也叫数组元素
arr[0] //1arr[3] //543arr[5] //undefinedarr[2] = "hello"arr[2] //'hello'arr[6] = "haha"//[1, 'a', 'hello', 543, true, undefined, 'haha']
数组的遍历
遍历简单来讲就是把数组中的每一个元素都调用一遍
var arr = [1,"a","23",543,true,undefined];for(var i = 0; i < arr.length; i++){if(typeof arr[i] == "string"){console.log(arr[i]);}}
代码分析:
我们通过循环的方式将数组中的每一个元素挨个调用,然后参与循环体中的判断,来决定到底最终在控制台打印哪些数组元素
数组的属性与方法
属性:用于描述事物的特征
方法:这个对象的概念,就是我们之前学过的函数
学习数组其实就是学习数组的属性和方法让我们可以方便的操作数组
1、length属性,用与获取数组的长度
var arr = [1,2,3,4,5,6];var a = arr.length;
2、push() 向当前数组中的最后面添加一个数组元素,并返回当前数组的长度
var arr = [1,"a","23",543,true,undefined];var x = arr.push("abc");
3、pop() 移除当前数组的最后一个元素,并返回这个被移除的元素
var arr = [1,"a","23",543,true,undefined];var y = arr.pop();
4、unshift() 向当前数组的前面添加一个元素,并返回这个数组的长度
var arr = [1,"a","23",543,true,undefined];var z = arr.unshift("123");
5、shift() 移除当前数组中最前面一个元素,并返回这个元素
6、concat() 将多个数组进行合组成一个新数组,把需要组合的数组作为实参传入,并返回新数组,原数组不变
7、slice() 截取数组当中的元素,返回一个新数组,原数组不变
var newArr1 = arr.slice(1,3);//从索引1的位置开始,至索引3结束,不包含结束位var newArr2 = arr.slice(1);//从索引1开始截取到最后var newArr3 = arr.slice();//相当于把数组复制了一份var newArr4 = arr.slice(1,-1);//负数代表倒数,-1表示最后一个,整数理解索引,从索引1截取到倒数第2个,不包含结束位var newArr5 = arr.slice(-2,-1);//相当于从倒数第二个截取到倒数第一个,不包含结束位
8、reverse() 将数组元素内部的数组元素反转,返回一个新数组,原数组改变
9、toString() 将数组转换成字符串返回
10、join() 将数组中的元素用指定的符号隔开,然后再转成字符串
12、indexOf() 查找某一个元素再数组中最先出现的位置,返回这个元素的索引值,如果没有找到返回 -1
13、lastIndexOf() 查找某一个元素在数组中最后出现的位置,返回这个元素的索引值,如果没有找打返回-1
14、splice() 在指定的位置,删除指定个数的元素,放入指定个数的元素,然后删除的元素组成一个新数组返回
var a = arr.splice(1,2);//索引值1开始向后数2个删除,并返回删除元素var b = arr.splice(1,0,"haha")//索引1开始向后删除0个,并在索引1的位置添加"haha",并返回删除元素var c = arr.splice(1,1,"张三") //索引1开始向后删除1个,并在索引1的位置添加"张三",并返回删除元素
特殊情况
arr.splice(1); //索引1开始删除到最后面arr.splice(-2);//倒数第2个开始删除到最后arr.splice(1,-1);//数组保持不变arr.splice(1,"a")//
数组的高级方法与二维数组
数组的迭代方法:
数组的迭代方法也叫数组的遍历方法,它有5个遍历方法
所有的遍历方法都有以下特点
1、不能使用break,不能使用continue跳过
forEach方法
var arr = [1,2,3,4,5,6];arr.forEach(function(item,index,_arr){console.log(item * 2);})
上面的代码使用forEach遍历的方法,它使用回调函数完成后续操作,回调函数接收三个参数
- item 代表当前遍历的这一项数组元素
- index 代表当前遍历的这一项数组元素的索引
- _arr 代表当前正在遍历的数组
map方法
也是一个遍历数组的方法,与forEach类似,唯一不同的是可以把每次迭代的回调函数的返回值接收下来,然后组成一个新的数组
var newArr = arr.map(function(item,index,_arr){var x = item * 2;return x;})
filter方法
该方法与map类似,它会在原数组中返回符合指定条件的数组元素,然后讲这些数组元素组成一个新的数组返回到外面
var filterArr = arr.filter(function(item,index,_arr){return item % 2 == 0;})
every方法
该方法会对数组里面的每一个元素执行遍历,回调函数返回一个条件,这个条件会生成一个布尔值,每一次遍历都会把当前遍历的数组元素参与到这个回调函数中的返回条件中执行,并将执行结果转换成布尔,最后把每个元素执行的布尔结果再执行一个逻辑与运算
var result = arr.every(function(item,index,_arr){return item > 4;})
some方法
该方法完全参照every方法
reduce方法
语法如下
var result = arr.reduce(function(prev,current,index,_arr){})
prev 代表前一次回调函数的返回值【这一次回调函数的返回值会作为下次回调函数的prev实参使用】
current 代表当前遍历的值【reduce方法从数组的第二个元素开始】
index 代表当前遍历值的索引
_arr 代表当前正在操作的数组
场景一:数组求和
var result = arr.reduce(function(prev,current,index,_arr){var num = prev + current;return num;})
reduceRight方法
与reduce方法一样,只是它是从右向左遍历
面向对象编程
目前来讲,我们有两种比较流行的软件开发思路,面向过程和面向对象
对象的定义
对象的创建我们可以理解成对象的封装
通过键值对创建对象
var 对象名 = {属性名1:属性值1,属性名2:属性值2,属性名3:function(){}}
演示:
var stu_xm = {name:"小明",age:20,sex:"男",hobby:"打游戏",sayHello:function(){console.log("hello");}}var stu_xf = {name:"小芳",age:18,sex:"女",hobby:"吃饭"}
代码分析:
以上我们创建了两个对象,里面都包含了name\age\sex\hobby属性,记录了当前对象的一些特征,同时stu_xm对象中还包含了一个sayHello的方法,表示该对象还具备有相应的能力
注意:
通过以上方式创建的对象,我们叫做字面量对象
如果想在对象内部的方法中调用自己对象内部的属性,我们使用this变量,这个变量,现在先简单理解成,写在哪个对象里面,这个this变量就指向哪个对象
var stu_xm = { name:"小明", age:20, sex:"男", hobby:"打游戏", sayHello:function(){ console.log(this.name + "hello"); }}
通过Object方法创建
var obj = new Object();//该情况下创建的对象为空对象,我们需要自己手动去给它添加
添加或删除对象内的属性
添加:如果你从某一个对象中调用了一个这个对象不存在的属性,对象会给你自动添加上去
var stu_xf = {name:"小芳",age:18,sex:"女",hobby:"吃饭"}stu_xf.age = 20; //修改age属性的值stu_xf.height = 180; //对象内没有height属性,所以添加height属性并赋值180
删除对象的属性使用 delete 关键字
delete stu_xf.sex; //我们把对象中sex属性进行了删除
使用构造函数创建对象(重点)
所有的构造函数在调用的时候都是用new关键字来调用,并且构造函数的函数名首字母大写
回忆以下创建数组的时候
var arr = new Array();//这个过程中,Array()就是系统预设好的一个构造函数,通过new调用创建一个数组对象,赋值给arr,所以arr就是一个数组对象
现在我们来写一个构造函数
function Student(_name,_sex,_age){this.name = _name;this.age = _age;this.sex = _sex;this.sayHello = function(){console.log("大家好,我叫" + this.name);}}var stu1 = new Student("张三","男",20);var stu2 = new Student("李四","女",18);
工厂模式创建对象
function createStudent(_name,_sex,_age){var obj = {name:_name,sex:_sex,age:_age,sayHello:function(){console.log(this.name);}}return obj;}var stu1 = createStudent("张三","男",20);
代码分析:
通过上面的方式,也可以快速的创建对象,这个方式就相当于模拟了构造函数的new的过程
构造函数与普通函数的区别
构造函数和普通函数在书写上没有区别,关键在于调用上
1、构造函数使用new调用,普通函数直接函数名() 调用
2、构造函数的返回值一定是一个对象,而普通函数的返回值是根据return来决定
3、普通函数的this它指向的是window对象,而构造函数的this指向的是创建的对象
4、如果通过new的形式调用构造函数,在执行的时候如果没有实参需要传可以不写调用时的小括号
5、构造函数的函数名首字母大写,普通函数我们使用小驼峰方式命名
特殊需求属性
现在我们创建的对象,我们都叫基础对象,现在我们对对象有一些高级的需求,比如有一些特殊属性年龄,应该时随着时间的增长而增长的,类似这样的属性我们怎么定义?
Object.defineProperty() 定义对象属性
var stu1 = {};Object.defineProperty(stu1,"name",{//关于特殊属性的定义})
现在我们在对象stu1当中定义了个name属性,现在它还是一个普通属性,现在我们来看下,我们可以设置属性的哪些行为描述
数据属性:
数据属性就是包含一个数据的位置,我们可以在这个位置写入或者读取值,有4个描述具体行为的特征
Configurable:表示是否可以同delete关键字删除这个属性,从而新定义这个属性,或者是否能把这个属性修改成访问器属性,它的默认值是true
EnumeErable: 表示能否被for…in循环遍历返回属性名,这个默认值是true
**Writable:**表示是否允许修改属性的值,默认值是true
value: 包含了这个属性的数据值,读取属性的时候,从这个位置读/写属性值的时候,把新信保持在这个位置上,默认值是undefined
var stu = {aaa:"hello"}//我们开始添加一个具备特殊行为描述的数据属性Object.defineProperty(stu,"name",{configurable:false,//false代表不能被delete删除enumerable:false,//表示不能被for...in循环遍历到writable:false,//不能修改这个值value:"小芳"})for(var i in stu){console.log(i);}
代码分析:
现在我们给stu对象添加了一个name属性,对这个属性的各种行为都设置为false,表示这个属性即不能被删除,也不能被修改,还不能被遍历到(像一些关键参数,我们可以就需要做这样的行为描述,避免被误会修改)
访问器属性:
访问器属性是不能直接被定义的,必须使用 Object.defineProperty() 来定义
Configurable:表示是否可以同delete关键字删除这个属性,从而新定义这个属性,或者是否能把这个属性修改成访问器属性,它的默认值是true
EnumeErable: 表示能否被for…in循环遍历返回属性名,这个默认值是true
**Get:**在读取属性的时候调用的函数,默认值是undefined
Set: 在写入属性的时候调用的函数,默认值是undefined
var stu = {birthday:"1997-7-5"}//现在有一个学生对象,我们在这个对象里面添加了一个数据属性birthday生日//现在我们希望再添加一个age属性,但是这个age属性的值要根据生日来计算Object.defineProperty(stu,"age",{configurable:false,enumerable:true,get:function(){return "你正在读取age属性"//访问器属性所调用出来的值由get的return决定},set:function(v){//v表示你赋的值console.log("你正在对这个age属性赋值,你要赋的值是" + v)}})
上面的get和set定的age属性就是一个访问器属性,它在取值和赋值的时候会分别除法get和set方法
接着我们看访问器属性的具体作用:
var stu = {birthday:"1997-7-5"}//现在有一个学生对象,我们在这个对象里面添加了一个数据属性birthday生日//现在我们希望再添加一个age属性,但是这个age属性的值要根据生日来计算Object.defineProperty(stu,"age",{configurable:false,enumerable:true,get:function(){var currentDate = new Date();//获取当前时间点的日期对象var birthdayDate = new Date(this.birthday);//获取出生日期的日期对象var totalTime = currentDate.getTime() - birthdayDate.getTime();var yearCount = parseInt(totalTime / 1000 / 60 / 60 / 24 / 365);return yearCount;}// set:function(v){// //v表示你赋的值// console.log("你正在对这个age属性赋值,你要赋的值是" + v)// }})
代码分析:
我们给stu对象添加了一个访问器属性age,并对age调用时的行为进行了描述,对age属性的调用执行了一个get方法
也就是说,针对访问器属性的调用和赋值操作
调用:执行访问器属性中行为描述的get方法
赋值:执行访问器属性中行为描述的set方法
扩展:
这里我们使用到了日期对象,JS为我们提供了一个Date构造函数,通过new调用可以直接获取当前日期的日期对象
同时,这里我们在new日期对象的时候,我们还使用了传参,当new日期对象的时候传入的参数必须是固定的格式 YYYY-MM-DD的字符串
通过上面的访问器属性,我们设置了age,现在我们还想设置一个sex性别,根据身份证号来获取的,现在我们需要在stu对象上面添加两个带有特殊行为描述的属性,但是 Object.defineProperty 方法一次只能添加一个
Object.defineProperties() 定义对象属性
这个方法其实就是defineProperty的复数形式,可以一次定义多个带特殊行为描述的属性
var stu = {birthday:"1997-7-5",idCard:"420502199707050116"}Object.defineProperties(stu,{name:{value:"张三",configurable:false,writable:false},age:{get:function(){var currentDate = new Date();//获取当前时间点的日期对象var birthdayDate = new Date(this.birthday);//获取出生日期的日期对象var totalTime = currentDate.getTime() - birthdayDate.getTime();var yearCount = parseInt(totalTime / 1000 / 60 / 60 / 24 / 365);return yearCount;}},sex:{get:function(){// if(this.idCard[16] % 2 == 0){// return "女"// }else{// return "男"// }return this.idCard[16] % 2 == 0 " />"女" : "男"}}})
获取对象属性的描述信息
var nameDesc = Object.getOwnPropertyDescriptor(stu,"name");//获取name属性的行为描述信息
构造函数与特殊属性结合
构造函数与特殊属性结合,主要指的就是构造函数与 Object.defineProperties 的结合,这样我们就可以在构造函数的体内直接定义特殊属性,通过这个构造函数new出来的对象(对象的实例化)的属性就一直是一个经过特殊定义的属性
案例:
现在我们定义一个Person构造函数,这个构造函数实例化的对象有5个属性,分别是name,birthday,sex,age,IDCard,这5个属性不能被delete,同时都是只读,age属性根据生日计算,sex属性根据身份证号决定
function Person(_name,_birthday,_IDCard){Object.defineProperties(this,{name:{value:_name,configurable:false,writable:false},birthday:{value:_birthday,configurable:false,writable:false},IDCard:{value:_IDCard,configurable:false,writable:false},age:{configurable:false,get:function(){var currentDate = new Date();var birthdayDate = new Date(this.birthday);var totalTime = currentDate.getTime() - birthdayDate.getTime();var yearCount = parseInt(totalTime / 1000 / 60 / 60 / 24 / 365);return yearCount;}},sex:{configurable:false,get:function(){return this.IDCard[16] % 2 == 0 ? "女" : "男"}}})}var p1 = new Person("张三","2000-1-1","420502200001010222")
对象的继承
通过原型来实现继承
现在我们通过在控制台打印一个new出的来对象,在对象中找到一个属性 __proto__
这个是对象的特殊属性,它指向的是对象的原型,我们可以对对象的原型理解成对象的父级,那么我们就通过这点来实现继承
有一点先说明:
在JavaScript的面向对象当中,有这么一句话,一个对象的__proto__
应该等于这个对象的构造函数的prototype
p1.__proto__ === Person.prototype //true
__proto__
可以设置对象的父级,那么 prototype
应该也可以设置对象的父级,所以根据这个特点我们额来尝试去完成对象的继承
function Person(_height){this.height = _height;this.sayHello = function(){console.log("大家号,我叫haha");}}function Student(_userName,_sex,_age,_height){this.userName = _userName;this.age = _age;this.sex = _sex;this.__proto__ = new Person(_height) //s1.__proto__ = p1}var s1 = new Student("张三","男",18,190)
this关键字
this 是一个关键字,也是一个指针,具体指的什么就要看这个关键字谁在调用它
对象中的this
var name = "张三";var stu = {name:"李四";sayHello:function(){console.log(this.name);}}
上面的代码中,我们看到sayHello方法里面有个this,那么这个this指向谁?
我们之前说过,this指向当前调用它的对象
问题:这个当前对象到底是谁?
1、在全局环境下,this指向的当前对象是window
2、在自己定义的对象中,this指向当前调用这个方法的对象
根据上面的特点,全局环境的this指向window对象,那么,我们补充以下几点
之前我们在声明变量的时候通过var关键字声明的变量,其实还可以理解成是给window对象添加属性,通过function关键字声明的函数,其实就是给window对象添加方法,而直接添加在window对象下的属性和方法在调用的时候是可以不写window对象自己的
var name = "张三";
上面的代码定义了个全局变量,其实全局变量就是window对象的属性
window.name = "张三"
因为this在全局环境下指向的是window对象
var name = "张三";var stu = {name:"李四",sayHello:function(){console.log(this.name);}}stu.sayHello();var s = stu.sayHello;s();
代码分析:
var s = stu.sayHello 相当于 window.s = stu.sayHello 所以接下来调用 s() 其实就相当于window.s()
根据我们之前讲过的,对象方法里面的this指向调用这个方法的对象,现在这个方法被window调用的,所以里面的this指向window
构造函数里面的this
函数就是方法,在面向对象的思维里面,函数就是方法
我们在使用构造函数的时候也使用了this,它指向谁?
var name = "haha";function Person(){console.log(this.name);}
构造函数与普通函数在本质上是没有区别的,关键在于其调用方式,用new调用的函数我们就叫构造函数
Person()//window.Person(),所以这里的this指向windownew Person()//构造函数在调用的时候,体内的this指向的是当前实例化的对象
函数的不同调用形式决定了this指向
先简单回顾下函数的调用形式:
1、方法名()
2、var 方法名 = function(){ }()
3、!function(){ }()
4、(function(){ })()
5、new function(){}
除了上面三种之外,我们还有另外的三种调用形式,这三种方式也可以改变this指向
通过call方法调用
var name = "张三";function sayHello(str){console.log(str);console.log(this.name)}var stu1 = {name:"李四"}var stu2 = {name:"王五"}sayHello("普通调用");sayHello.call(window,"我是通过call来调用的");sayHello.call(stu1,"我是通过call来调用的");sayHello.call(stu2,"我是通过call来调用的");
通过apply来调用
这个调用方式跟call调用一模一样,唯一不同的就是在原来方法的参数赋值上面
var name = "张三";function sayHello(str,x,y,z){console.log(str);console.log(x)console.log(this.name)}var stu1 = {name:"李四"}var stu2 = {name:"王五"}sayHello.apply(stu1,["我是通过apply调用的",1,2,3])
通过bind来调用
bind方法在调用方法的时候,不会立即执行原来的方法,而是返回一个新的方法,通过新的方法调用原方法
var name = "张三";function abc(){console.log(this.name);}var stu = {name:"李四"}var x = abc.bind(stu);x();
如果带有参数
var name = "张三";function abc(x,y){console.log(arguments);console.log(this.name,x,y);}var stu = {name:"李四"}var a = abc.bind(stu,99,100);a();var b = abc.bind(stu,200);b()var c = abc.bind(stu);c(500,501)var d = abc.bind(stu,600,601);d(602,603)
★★★DOM
document object model 文档对象模型,主要目的就是把网页里面的元素(标签+内容)当成一个JS对象来操作
DOM技术的本质就是操作我们二点html元素进行各种各样的操作
DOM技术可以理解成两个部分,操作HTML,操作CSS
在学习之前我们先简单说明一下:
1、document是文档的意思,这个文档指向的就是当前网页
2、页面上所有的元素,都被js转换到JS里面的对象中去
3、如果是非IE浏览器,我们一般会根据一个id来生成一个对象,方便我们直接获取
通过JS获取页面元素
1、通过id来获取元素
var user = document.getElementById("user");
通过这种方式获取元素,永远只能获取1个或者0个(null)
2、通过class获取元素
document.getElementsByClassName("类名")
这个方法会返回一个叫做HTMLCollection的集合(类数组),这个集合里面存放的是DOM对象,如果没有获取到返回一个空数组
3、通过标签名获取元素
document.getElementsByTagName("标签名");
这个方法会返回一个叫做HTMLCollection的集合(类数组),这个集合里面存放的是DOM对象,如果没有获取到返回一个空数组
4、通过name属性来获取
document.getElementsByName("name属性值");
这个方法返回的是一个叫做NodeList的集合,它也是个类数组
以上4种方式是我们最基础的4种获取元素的方式,这些方法兼容性好,在绝大部份浏览器都是可以使用的
<div id="div1"><div class="div2">我爱北京天安门</div><div class="div2">天安门上太阳升</div><div class="div3">啊哈哈哈</div></div><div class="div2">我是外面的第二个盒子</div><div class="div2">我是外面的第二个盒子的副本</div><p>我是一个段落</p><script>//我要获取id为div1里面的两个类名为div2的元素var divList = document.getElementById("div1").getElementsByClassName("div2");console.log(divList);</script>
但是这是很早的解决方案,现在我们已经可以不是使用,转而另寻新欢(强烈推荐)
新的方法主要是结合HTML5和CSS3的东西,我们直接把CSS种的选择器与JS做了结合
1、通过 querySelector("CSS选择器")
来获取元素
var div1 = document.querySelector("#div1");var div2 = document.querySelector(".div2");
这种方法通过CSS选择器来获取元素,如果选中了就返回一个元素,找不到返回null,如果找到多个值返回第一个
2、通过 querySelectorAll("CSS选择器")
来获取元素
var div2List = document.querySelectorAll("#div1>.div2")
通过这种方式返回的是一个NodeList集合,是一个类数组
总结:
get系列的方法,相当于是把不同的选择器分成了对应的方法来执行
querySelector 把所有的CSS选择器综合进行使用
DOM结构
DOM本身也是一个对象,而我们以前讲过对象都有自己的属性和方法,这些属性和方法基本上都是从原型继承来的,所以我们看结果可以理解成看对象的原型
分析DOM对象
标签名 | 第一级 | 第二级 | 第三级 | 第四级 | 第五级 |
---|---|---|---|---|---|
div | HTMLDivElement | HTMLElement | Element | Node | EventTarget |
p | HTMLParagraphElement | HTMLElement | Element | Node | EventTarget |
通过上面两个标签对象的对比,我们可以看到所有的元素都继承自Node对象(EventTarget暂时不管),我可以把Node当成所有页面元素对象的父级,这个时候我们弄清楚Node对象也就弄清除了DOM对象
Element与Node区别
<div id="div1">我是前面插入的一段文字<div class="div2">我爱北京天安门</div><div class="div2">天安门上太阳升</div><div class="div3">啊哈哈哈</div></div>
1、网页当中的所有标签都可以是一个元素(Element),同时也可以是一个节点Node
2、但是并不是所有的节点Node都是一个元素Element(节点是可以包含元素、文字、注释、回车…)
3、上面网页结构中,元素就4个,但是节点就很多了
Element常用属性与方法
1、children属性,获取当前元素下所有的子元素,返回一个HTMLCollection的集合
2、parentElement 属性 获取当前元素的父元素
3、nextElementSibling 当前元素的下一个兄弟
4、previousElementSibling 当前元素的上一个兄弟
以上4个属性以当前元素为中心获取父级、子集、兄弟
5、className属性 ,获取或者设置当前元素的class属性(类名)
<div id="div1"><div class="div2">2222</div><div class="div2">1111</div><div class="div3 size">啊哈哈哈</div></div><div class="div2">我是外面的第二个盒子</div><div class="div2">我是外面的第二个盒子的副本</div><p>我是一个段落</p><button onclick="changeColor()">按钮</button><script>var div3 = document.querySelector(".div3");function changeColor(){div3.className = "color"}</script>
className有个情况要注意下:
虽然上面我们可以通过className的方式操作元素样式了,但是实际工作中,我们实际不会这么干,因为有问题,className的赋值是将整个class属性中的值全部替换掉,如果出现了一个标签多个类名的情况,它会把所有的类名全部替换掉
6、classList属性,返回当前元素的class类名的集合,它会返回一个叫做DOMTokenList对象(类数组),在这个集合里面包含了当前元素的所有类名
- add() 方法,在当前元素的classList里面添加一个class
- remove() 在当前元素的classList中删除一个类名
- toggle() 如果classList当中有这个类名就删除,没有就添加
- contains() 判断当前元素中是否有这个类名,返回布尔
7、firstElementChild 属性获取当前元素下的第一个子元素,相当于children[0]
8、lastElementChild 属性 获取当前元素下的最后一个子元素
9、innerText属性 ,获取或设置当前元素的文本内容 ,如果赋值的时候是html标签,那么依然会按照文本的形式输出,不会转义成html标签
10、innerHTML属性,获取或设置当前元素的HTML内容,如果赋值的时候是html标签,会对html标签转义
11、tagName属性,获取当前元素的标签名
12、value属性,它是表单的专属属性,可以调用和赋值
13、childElementCount 属性 ,获取当前元素的子元素的个数
Element常用方法
1、createElement() 根据标签名创建一个元素
var div1 = document.createElement("div");
2、appendChild() 向当前元素里面的最后去追加一个新的元素
var div1 = document.createElement("div");div1.innerText = "今天天气不错"div1.classList.add("color");var box = document.querySelector(".box");box.appendChild(div1);
3、remove() 删除当前元素,并且子元素会一并删除
var p1 = document.querySelector("p");p1.remove();
4、removeChild() 删除指定的子元素,返回删除掉的子元素
var IDdiv = document.querySelector("#div1");IDdiv.removeChild(IDdiv.children[1]);ODdiv.children[1].remove();
5、cloneNode() 克隆一个相同的元素出来,这个方法可以接收一个参数,默认是false,传入一个true表示要连同子元素一起复制
var cloneDiv = IDdiv.cloneNode(true);var cloneDiv = IDdiv.cloneNode();
6、insertAdjacentElement() 在指定的位置插入元素,指定的位置由传入的参数决定
参数值 | 解释 |
---|---|
beforeBegin | 开始标签之前 |
afterBegin | 开始标签之后 |
beforeEnd | 结束标签之前 |
afterEnd | 结束标签之后 |
IDdiv.insertAdjacentElement("beforeBegin",cloneDiv);
出来以上这个方法可以插入元素,还有其他类似的方法
insertAdjacentHTML() 插入网页标签
insertAdjacentText()插入文本
var text = "我是h2标题
"IDdiv.insertAdjacentHTML("beforeBegin",text) //innerHTML类似IDdiv.insertAdjacentText("beforeBegin",text) //innerText类似
Event事件:
事件是DOM对象里面一个特殊属性,它需要经过一个条件触发,与普通属性相比最大的区别就是属性值,事件作为一个特殊属性它接收的值是一个function 或者直接写一个执行语句也行
<button type="button" id="btn" onclick="sayHello()">按钮</button><button type="button" onclick="console.log('hello')">再来点我</button><script type="text/javascript">function sayHello(){console.log("hello");}</script>
用户触发了事件,事件调用了方法
按照W3C的标准,DOM事件被分为了两个级别
0级DOM事件
这是一个基本的DOM事件,0级事件本质上就是一个DOM对象的属性,只是这个属性比较特殊
1、属性值都是一个function
2、这些属性都是以on开头
在0级书简中,我们的事件又可以分为以下几个类
1、鼠标事件
2、键盘事件
3、文档事件
4、其他事件
事件绑定
第一种绑定方式
<button type="button" onclick="console.log('hello')">再来点我</button>
第二种绑定方式
<button type="button" id="btn" onclick="sayHello()">按钮</button><script type="text/javascript">function sayHello(){console.log("hello");}</script>
第三种绑定方式(具名函数)
var btn = document.querySelector("#btn1");function sayHello(){console.log("hello");}btn.onclick = sayHello;
第三种绑定方式(匿名函数)
var btn = document.querySelector("#btn1");btn.onclick = function(){console.log("hello");};
现在我们有个问题?我们现在有个无序列表,我现在想给每一个列表项绑定一个点击事件,当点击当前li的时候改变当前文字颜色,怎么实现?
<ul><li>1</li> <li>2</li><li>3</li><li>4</li><li>5</li></ul><script>var liList = document.querySelectorAll("li");for(var i = 0;i < liList.length;i++){liList[i].onclick = function(){this.classList.toggle("active");}}</script>
0级事件的批量绑定的注意事项
场景:现在页面上有10个按钮,我们希望对这10个按钮绑定一个onclick事件,每个按钮点击的时候,在控制台打印当前按钮的序号,怎么实现?
<button type="button">0</button><button type="button">1</button><button type="button">2</button><button type="button">3</button><button type="button">4</button><button type="button">5</button><button type="button">6</button><button type="button">7</button><button type="button">8</button><button type="button">9</button><script>var btnList = document.querySelectorAll("button");for(var i = 0;i < btnList.length;i++){btnList[i].onclick = function(){console.log(btnList[i].innerText);}}</script>
代码分析:
以上的写法是错误的,因为for循环已经执行完毕,i的值已经固定成了10,这个时候当我们去触发事件,事件再调用方法的时候,方法里面的 i 调用的值就是10了,所以实际当你触发执行方法的时候,调用的是 btnList[10] ,但是集合里面没有索引为10的这个元素,所以报错
解决方案一:通过this
var btnList = document.querySelectorAll("button");for(var i = 0;i < btnList.length;i++){btnList[i].onclick = function(){console.log(this.innerText);}}
这种方式,用户触发了事件,事件再调用方法,所以事件方法里面的this指向当前DOM对象
解决方案二:通过闭包立即执行
var btnList = document.querySelectorAll("button");for(var i = 0;i < btnList.length;i++){btnList[i].onclick = (function(j){return function(){console.log(btnList[j].innerText);}})(i)}
事件对象event(重点)
事件对象是所有事件都具备的,当用户触发事件的时候,事件会调用方法,事件再调用方法的过程种会向当前方法注入一个参数,这个参数就是一个事件对象event
获取事件对象
<div class="box">事件对象</div><script>var box = document.querySelector(".box");box.onclick = function(e){e = event || window.event;//兼容写法,保障我们在不同的浏览器种都可以正确调用到事件对象console.log(e);}</script>
事件传播
简单解释,一个DOM对象上面的事件会传播给另外一个DOM对象
<div class="box"><button id="btn">按钮</button></div><script>var box = document.querySelector(".box");var btn = document.querySelector("#btn");box.onclick = function(){console.log("我是box")}btn.onclick = function(){console.log("我是btn")}</script>
代码分析:
上面的代码种,我们点击box中的btn按钮的时候,我们发现两个事件都被触发了,这就说明一点,内部按钮的onclick事件的行为传递给了外面元素box的onclick上面,这种现象我们叫做事件冒泡
事件冒泡
事件冒泡就指事件由内向外传播
取消事件冒泡
e.cancelBubble = true; //取消事件冒泡e.stopPropagation(); //取消事件传播
事件的触发者与绑定者
<div class="box"><button id="btn">按钮</button></div><script>var box = document.querySelector(".box");var btn = document.querySelector("#btn");box.onclick = function(e){e = event || window.event;console.log("我是box");console.log("target",e.target);console.log("currentTarget",e.currentTarget);}</script>
代码分析:
上面的按钮点击了btn,但是这个按钮本身是没有绑定事件,所以就算点击了这个按钮也不会有任何的触发,但是它会冒泡到外面的box上面,外面box是绑定的有事件的,导致btn的点击行为传播到了外面的box上面,因此触发了box身上的事件,那么box的的事件实际上是由btn触发的
这里我们理解,btn是触发,box是绑定
target是事件的触发者
currentTarget是事件的绑定者
事件冒泡实现事件委托
<button onclick="addNewLi()">添加列表项</button><ul class="ul1"><li>1</li><li>2</li><li>3</li><li>4</li></ul><script>function bindEvent(){var liList = document.querySelectorAll(".ul1>li");for(var i = 0; i < liList.length;i++){liList[i].onclick = function(){console.log(this.innerText)}}}bindEvent();//当点击一个按钮的时候,向列表中新增列表项function addNewLi(){var newLi = document.createElement("li"); //创建一个li元素newLi.innerText = "新增的项"; //给创建好的li设置一个内容document.querySelector(".ul1").appendChild(newLi); //获取ul列表,向这个列表里面的最后面添加libindEvent();}</script>
代码分析:
上面的代码中,我们每添加一个新的li,都需要重新执行一遍事件绑定,这样非常消耗性能,我们转换下思考方式,能不能把这个事件绑定到li的父级ul上,然后让里面的元素通过事件冒泡来执行绑定在父元素上面的事件方法
var ul1 = document.querySelector(".ul1");ul1.onclick = function(e){e = event || window.event;console.log(e.target.innerText);}function addNewLi(){var newLi = document.createElement("li"); //创建一个li元素newLi.innerText = "新增的项"; //给创建好的li设置一个内容document.querySelector(".ul1").appendChild(newLi); //获取ul列表,向这个列表里面的最后面添加li}
代码分析:
上面的代码就是基本的事件委托代码,这个过程中onclick事件本来应该绑定在li身上,但是现在委托给了ul进行绑定,然后利用事件冒泡的原理进行事件的传递(传播),传递给了外面的ul,这种现象就是事件委托
现在我们想让指定的元素成为触发者,只有点击指定的元素才会触发事件完成委托,那么现在需要判断触发者为指定元素
在DOM对象里面由一个API方法,matches() 来完成判断
var ul1 = document.querySelector(".ul1");ul1.onclick = function(e){e = event || window.event;if(e.target.matches("li.active")){console.log(e.target.innerText);}}function addNewLi(){var newLi = document.createElement("li"); //创建一个li元素newLi.innerText = "新增的项"; //给创建好的li设置一个内容document.querySelector(".ul1").appendChild(newLi); //获取ul列表,向这个列表里面的最后面添加li}
代码分析:
这里使用的matches方法通过css选择器来指定元素,将指定好的元素的css选择器作为参数传入来指定触发者
元素的默认行为
有些标签在经过JS的处理之后还由有一些响应,比如a标签
<a href="https://www.baidu.com">百度一下</a><script>var a1 = document.querySelector("a");a1.onclick = function(){alert("hello world")}</script>
在上面的代码中,当我们点击a标签的时候,它会执行onclick事件,然后在去执行a标签的跳转
我们可以在事件当中取消这个某些元素的默认行为,只需要在事件方法的后面返回一个false即可
a1.onclick = function(){alert("hello world");return false;}
同样的其他自带默认行为的html标签页是同理,比如重置按钮之类的
2级事件
在2级事件中,所有的事件都通过一个监听的方法来绑定
添加监听
DOM对象.addEventListener("事件名",function(){},传播方向)
举例:
var btn = document.querySelector("button");function sayHello(){console.log("hello");}btn.addEventListener("click",sayHello)
也可以直接传入要给匿名回调函数
var btn = document.querySelector("button");btn.addEventListener("click",function(){console.log("ahhahaha")})
2级事件与0级事件的区别点
1、0级事件都是以属性存在的,2级事件都是以事件监听的形式存在的
因为0级事件是以属性存在的,所以每次赋值新的方法会把老方法覆盖掉,而2级事件可以实现多次监听
var btn = document.querySelector("button");btn.addEventListener("click",function(){console.log("ahhahaha")})btn.addEventListener("click",function(){console.log("呵呵呵呵")})
2、0级事件都是以on开头,2级事件在监听的时候都是去掉了on
3、0级事件不可以改变事件传播的方法,2级事件可以
移除2级事件监听
移除在2级事件当中专门提供了一个方法 removeEventListener 来完成
var box = document.querySelector(".box");var btn = document.querySelector("button");function a(){console.log("我是box")}box.addEventListener("click",a);btn.addEventListener("click",function(){box.removeEventListener("click",a);})
如果我们希望某一个事件的方法只被执行一次,怎么办?
var btn = document.querySelector("#btn");btn.addEventListener("click",function(e){console.log("我是一次性按钮");console.log(arguments);e = event || window.event;this.removeEventListener(e.type,arguments.callee);})
代码分析:
e.type 指当前事件的类型名称click
arguments.callee 指向当前函数的引用,也就是函数本身
2级事件取消默认行为
百度一下var a1 = document.querySelector("a");a1.addEventListener("click",function(e){e = event || window.event;alert("我是2级事件");e.preventDefault();//阻止默认行为,return false在2级事件中没用})
我们打印出事件对象
正则表达式
正则表达式并不是JS独有的技术,基本上所有语言都支持这个技术
正则表达式的目的也很单纯,核心作用就是验证数据的合法性
正则表达式的特点:
1、正则表达式在JS中只针对字符串起作用
2、正则表达式会根据你设置的规则,对字符串进行 提取,搜索、替换 等操作
3、JavaScript中正则表达式是一个内置对象,这个对象通过RegExp() 创建,也可以直接通过赋值一个正则表达式来创建
第一种:
var reg = new RegExp(正则表达式)
第二种:
var reg = /张/
方法:
1、test() 用于验证某一个字符串是否符合正则的规则
var str ="abc";var reg = /a/;reg.test(str);
2、exec()根据正则表达式提取字符串中符合要求的字符,用法与上面类似,注意:正则表达式是可以添加修饰符的,这个修饰符会影响提取字符串的结果
修饰符
1、g代表global全局验证
2、i 代表忽略大小写
3、m 代表多行的意思,也就是可以换行匹配
正则表达式的规则
一元符号
.
匹配除换行符以外的任意符号
\w
匹配字符数字和下划线
\S
匹配任何空白符
\d
匹配所有数字 ,与 [0-9]
\b
匹配单词边界
|
或匹配
^
匹配字符串的开始
$
匹配字符串的结束
原子组和原子表
原子表
var reg = /^[张王赵]三$/console.log(reg.test("张三"));console.log(reg.test("王三"));console.log(reg.test("赵三"));console.log(reg.test("李三"));console.log(reg.test("张四"));console.log(reg.test("张三ssss"));console.log(reg.test("张ssss三"));
原子表是用中括号的形式存在,它会从这个表中拿出一个进行条件匹配
在原子表中,还可以写范围
var reg1 = /[0-9]/var reg2 = /[a-z]/var reg3 = /[A-Z]/
注意:原子表中的范围不能倒写,会报错
原子组
var reg = /张三|王三|赵三/;var reg2 = /(张|王|赵)三///原子组用小括号表示
反义字符
[^x] 匹配处理x以外的所有字符,这里的x可以是任意字符
[^xyz] 同上,匹配除了xyz以外的字符
\W 匹配除字母、数字、下划线以外的所有字符,等同 [^\w]
\B 匹配不是单词边界的字符
转义字符
\xnn 匹配十六进制数
\f 匹配换页符
\n 匹配换行符
\r 匹配回车符
\unnnn 匹配unicode编码
重复匹配
*
重复匹配零次或多次
+
重复匹配1次或多次
" />//我希望验证一个字符串,它是a开始,c结束,中间有4个英文字母。怎么写?var reg = /^a[a-zA-Z]{4}c$///我希望验证一个字符串,它是a开始,c结束,中间有4-6个数字。怎么写?var reg1 = /^a\d{4,6}c$///我希望验证一个字符串,它是a开始,c结束,中间至少有4个数字。怎么写?var reg2 = /^a\d{4,}c$///我希望验证一个字符串,它是a开始,c结束,中间至少有1个或多个数字。怎么写?var reg3 = /^a\d+c$///我希望验证一个字符串,它是a开始,c结束,中间至少有0个或多个数字。怎么写?var reg4 = /^a\d*c$///我希望验证一个字符串,它以三结束,以张开头,中间有0或1个字符var reg5 = /^张.?三$/
贪婪与惰性
重复匹配默认都是按照贪婪的特性去匹配较多的次数,如果我们需要按照惰性特征来匹配
我们可以把上面的多有的重复匹配符号后面加上一个 ?表示惰性匹配
var str = "cawqdewfeffwerderwe";//我要提取c开始到d结束中间任意长度的字符串var reg = /c[a-z]*d/;var newStr = reg.exec(str);//这个时候我们的正则表达式处在贪婪模式下,它会执行贪婪(0到贪婪多次)var reg = /c[a-z]*?d/;//执行惰性重复匹配
原子组编号
原子组通过()形成一个分组,这个分组会给一个分组编号
var str = "";//现在希望通过正则验证这个标签var reg = //;//上面的问题在于,我们希望开始标签和结束标签里面匹配到的是相同的字符var flag = reg.test(str);
在上面的表达式中,我们表面上看起来是完成了功能,但是又有隐患,如果我们要匹配的字符串是 这样也会验证成功,但是不符合要求
我们的主要需求是开始标签和结束标签要一致,我们开始匹配的内容 要与后面匹配的
保持一致,这个时候我们就要用到原子组
var str = "";//现在希望通过正则验证这个标签var reg1 = //;//上面的问题在于,我们希望开始标签和结束标签里面匹配到的是相同的字符var flag1 = reg1.test(str);var reg2 = //;var flag2 = reg2.test(str);
前瞻后顾
首先我们要弄清除两个条件
1、你的匹配条件是什么
2、你的限制条件是什么
前瞻
匹配条件是A,限制条件是B,B要出现在A的后面
A(?=B)
如果要匹配abc并且abc要在123的前面
/abc(?=123)/g//abc123true//abc456false//123abcfalse
负前瞻
匹配条件是A,限制条件是B,B不能出现在A的后面
/abc(?!123)/g
后顾
匹配条件是A,限制条件是B,B要出现在A的前面
(?<=B)A
要匹配abc并且abc要在123的后面
/(?<=123)abc/g//abc123false//abc456false//123abctrue
负后顾
匹配条件是A,限制条件是B,B要不能出现在A的前面
(?<!B)A
特殊情况
如何匹配中文
var reg = /^[\u4E00-\u9FA5]{2,4}$/
BOM
BOM全称broswer object model 浏览器对象,它主要是为我们提供了浏览器向外暴露的API,我们可以通过这些浏览器向外暴露的API来操作浏览器
location
location的值就是浏览器的地址栏
1、hostname 代表网页的主机名
2、port 代表当前网页的端口
3、host 代表当前网页的主机地址,包含了hostname和port
4、protocol 网页传输协议 比如http/ https
5、pathname 代表当前网页域名后面的 / 以后的部分
6、origin 代表当前网页的域信息,它由协议名、主机名、端口号共同组成
7、hash 代表当前网页的地址中 # 后面的部分
重点:这是一个非常重要的东西,后面的SPA单页面开发必不可少
8、search 当前地址栏中 ?后面的所有东西
超级重点:超级重要的东西,后面不管是MVC还是MVVM开发模式都需要使用它,它主要实现的是页面间传值,客户端与服务端传值
9、href 当前浏览器地址栏中的地址,它是可以取值也可以赋值的,如果赋值页面跳转
10、assign() 载入一个新地址,用法和href,把地址作为参数传入
11、replace() 替换地址栏的地址,网页会实现跳转
12、reload() 代表刷新
history
这个对象用于访问当前浏览器的历史记录
1、length 当前历史记录有几条
2、back() 在历史记录中后退一步
3、forward() 在历史记录中向前一步
4、go(step) 如果step是正值向前操作,负值向后操作
window
window是我们的浏览器也就是全局对象
1、alert()
2、confirm() 弹出一个询问框,点击确定按钮返回true,点击取消按钮返回false
3、prompt() 弹出一个内容输入框,点击确定返回输入的内容,点击取消返回null
4、open() 打开一个新网页,新的网页称为子页面,可以传入三个参数
参数1:要打开的网址
参数2:用什么方式打开 ,相当于a标签的target属性,写的实参也是target的值
参数3:打开新窗口的行为样式
父子页面
open方法在使用的十四号会打开一个新页面,比如a.html通过open打开b.html, 那么b现在就是a的子页面
<h2>我是a页面</h2><button onclick="openNewPage()">我是父页面按钮</button><button onclick="changeChild()">修改子页面</button><script>var childWindow;function openNewPage(){childWindow = open("b.html","_blank");}function changeChild(){childWindow.document.querySelector("h2").innerText = "我是你爸爸派来的"}</script>
通过open方法打开的页面中,我们可以在子页面的window对象中调出一个opener属性,这个属性里面装的是父页面的window对象
<h2>我是b页面</h2><button onclick="changeFather()">修改父页面</button><script>function changeFather(){var parentWindow = window.opener;parentWindow.document.querySelector("h2").innerText = "我是你儿子"}</script>
localStorage本地缓存(重点)
localStorage,它可以将字符串数据储存在本地浏览器中,它是window对象下的一个属性对象
1、setItem(key,value) 设置一个缓存
2、getItem(key) 获取一个缓存
3、removeItem(key) 删除一个缓存
4、clear() 清除所有缓存
localStorage.setItem("userName","张三");var str = localStorage.getItem("userName");console.log(str);localStorage.setItem("age",18);var age = localStorage.getItem("age");console.log(age);localStorage.removeItem("age");localStorage.clear();
缓存是以一个对象的形式存在的,所以我们可以像操作字面量对象一样设置缓存
localStorage.userName = "张三";localStorage.age = 18;console.log(localStorage.age);delete localStorage.userName;
关于本地缓存的特点
1、你不主动删除,它会一直在
2、可以跨页面访问,后面我们可以通过这种方式实现跨页面传值
3、不可以跨域访问
4、不可以跨浏览器访问
session会话缓存
先理解什么是会话?浏览器域服务器之间的链接
所谓的会话就是指浏览器到服务器之间的一个来回过程,但是总会有聊崩的时候,也就是浏览器与服务器之间断开链接的时候,而一旦断开,储存在session中的缓存数据就会销毁
它与localStorage的操作方法是一样的
1、setItem(key,value) 设置一个缓存
2、getItem(key) 获取一个缓存
3、removeItem(key) 删除一个缓存
4、clear() 清除所有缓存
session也跟本地缓存一样,可以作为一个对象 sessionStorage使用
sessionStorage.age = 18;delete sessionStorage.age
特点:
1、在浏览器关闭的时候会话会自动销毁
2、会话默认是不允许跨页面调用的,但是可以通过父子页面
- 通过window.open() 开打的页面是可以互动的
- 通过a标签打开的,但是target=“_self” 打开的也可以进行互动
3、不能跨域
4、不能跨浏览器
JSON对象(重点)
先看一个情况
var stu = {stuName:"张三",age:18,sex:"男"}localStorage.setItem("stu",stu);var a = localStorage.getItem("stu");console.log(a);//这个时候我们会发现缓存不了对象
代码分析:
在上面的代码中,我们看到,我们需要给当前缓存中缓存的数据是一个对象,但是缓存的结果却是[object Object] ,这样的结果意味着对象不能直接缓存,系统会自动把对象转成字符串缓存,但是转成了数据类型来进行缓存
这个时候,JSON(JavaScript Object Notation) JS对象简谱,这是一个轻量级的数据转换格式
通俗一点讲,JSON就是以字符串的形式来表示对象
var obj = {userName:"haha",age:18}//"{'userName':'haha','age':'18'}"
JSON的序列表
var stu = {stuName:"张三",age:18,sex:"男"}var str = JSON.stringify(stu);localStorage.setItem("stu",str);var a = localStorage.getItem("stu");console.log(a);
这个时候,我们将对象转换成JSON字符串,这个过程我们就交JSON的序列化,通过JSON.stringify() 方法实现
JSON的反序列化
var stu = {stuName:"张三",age:18,sex:"男"}var str = JSON.stringify(stu);localStorage.setItem("stu",str);var a = JSON.parse(localStorage.getItem("stu"));console.log(a);
URL对象
URL对象和location比较相似,但是比location更高级
var str = "http://www.xxxxx.com:8080/abc/1.html" />;var u = new URL(str);
URLSearchParams对象
在创建号的URL对象中,有一个属性叫做searchParams,这个属性里面装的是一个叫做URLSearchParams的对象
这个对象可以实现通过地址栏传值
a.html
<input type="text" id="userName"><input type="password" id="pwd"><button type="button" onclick="checkLogin()">登录</button><script>function checkLogin(){var userName = document.querySelector("#userName").value;var pwd = document.querySelector("#pwd").value;window.open("admin.html?userName=" + userName + "&pwd=" + pwd);//admin.html?userName=xxxx&pwd=xxxx}</script>
admin.html
<h2></h2><script>var u = new URL(location.href);var a = u.searchParams.get("userName");var b = u.searchParams.get("pwd");document.querySelector("h2").innerText = "你之前的登录账号" + a + ",你的登录密码是" + b;</script>
代码分析:
在a.html中,我们在跳转页面的时候,给地址的后面追加了一个search部分,这个search不会影响跳转的,我们真正实现条件的是地址栏中pathname的部分,后面的hash和search部分对跳转没有影响
2、admin.html中,我们将location.href的地址转换成了url对象,然后通过url对象中的searchParams来操作search后面传递过来的参数
URL编码与转码
当网页地址出现中文的,浏览器在操作的时候会自动编码
这个时候我们需要方法将其转回来
var str = encodeURIComponent(location.href + "?abc=急啊急啊");//把中文转换var url = decodeURIComponent(str); //把转码之后结果解码
总结:
以上便是JavaScript,HTML,CSS的知识总结,接着会持续更新Ajax,jQuery,vue,React
“`
通过open方法打开的页面中,我们可以在子页面的window对象中调出一个opener属性,这个属性里面装的是父页面的window对象
<h2>我是b页面</h2><button onclick="changeFather()">修改父页面</button><script>function changeFather(){var parentWindow = window.opener;parentWindow.document.querySelector("h2").innerText = "我是你儿子"}</script>
localStorage本地缓存(重点)
[外链图片转存中…(img-BbR3QI52-1695886174185)]
localStorage,它可以将字符串数据储存在本地浏览器中,它是window对象下的一个属性对象
1、setItem(key,value) 设置一个缓存
2、getItem(key) 获取一个缓存
3、removeItem(key) 删除一个缓存
4、clear() 清除所有缓存
localStorage.setItem("userName","张三");var str = localStorage.getItem("userName");console.log(str);localStorage.setItem("age",18);var age = localStorage.getItem("age");console.log(age);localStorage.removeItem("age");localStorage.clear();
缓存是以一个对象的形式存在的,所以我们可以像操作字面量对象一样设置缓存
localStorage.userName = "张三";localStorage.age = 18;console.log(localStorage.age);delete localStorage.userName;
关于本地缓存的特点
1、你不主动删除,它会一直在
2、可以跨页面访问,后面我们可以通过这种方式实现跨页面传值
3、不可以跨域访问
4、不可以跨浏览器访问
session会话缓存
先理解什么是会话?浏览器域服务器之间的链接
所谓的会话就是指浏览器到服务器之间的一个来回过程,但是总会有聊崩的时候,也就是浏览器与服务器之间断开链接的时候,而一旦断开,储存在session中的缓存数据就会销毁
它与localStorage的操作方法是一样的
1、setItem(key,value) 设置一个缓存
2、getItem(key) 获取一个缓存
3、removeItem(key) 删除一个缓存
4、clear() 清除所有缓存
session也跟本地缓存一样,可以作为一个对象 sessionStorage使用
sessionStorage.age = 18;delete sessionStorage.age
特点:
1、在浏览器关闭的时候会话会自动销毁
2、会话默认是不允许跨页面调用的,但是可以通过父子页面
- 通过window.open() 开打的页面是可以互动的
- 通过a标签打开的,但是target=“_self” 打开的也可以进行互动
3、不能跨域
4、不能跨浏览器
JSON对象(重点)
先看一个情况
var stu = {stuName:"张三",age:18,sex:"男"}localStorage.setItem("stu",stu);var a = localStorage.getItem("stu");console.log(a);//这个时候我们会发现缓存不了对象
代码分析:
在上面的代码中,我们看到,我们需要给当前缓存中缓存的数据是一个对象,但是缓存的结果却是[object Object] ,这样的结果意味着对象不能直接缓存,系统会自动把对象转成字符串缓存,但是转成了数据类型来进行缓存
这个时候,JSON(JavaScript Object Notation) JS对象简谱,这是一个轻量级的数据转换格式
通俗一点讲,JSON就是以字符串的形式来表示对象
var obj = {userName:"haha",age:18}//"{'userName':'haha','age':'18'}"
JSON的序列表
var stu = {stuName:"张三",age:18,sex:"男"}var str = JSON.stringify(stu);localStorage.setItem("stu",str);var a = localStorage.getItem("stu");console.log(a);
这个时候,我们将对象转换成JSON字符串,这个过程我们就交JSON的序列化,通过JSON.stringify() 方法实现
JSON的反序列化
var stu = {stuName:"张三",age:18,sex:"男"}var str = JSON.stringify(stu);localStorage.setItem("stu",str);var a = JSON.parse(localStorage.getItem("stu"));console.log(a);
URL对象
URL对象和location比较相似,但是比location更高级
var str = "http://www.xxxxx.com:8080/abc/1.html?cation=getAllList&age=18#box";var u = new URL(str);
URLSearchParams对象
在创建号的URL对象中,有一个属性叫做searchParams,这个属性里面装的是一个叫做URLSearchParams的对象
这个对象可以实现通过地址栏传值
a.html
<input type="text" id="userName"><input type="password" id="pwd"><button type="button" onclick="checkLogin()">登录</button><script>function checkLogin(){var userName = document.querySelector("#userName").value;var pwd = document.querySelector("#pwd").value;window.open("admin.html?userName=" + userName + "&pwd=" + pwd);//admin.html?userName=xxxx&pwd=xxxx}</script>
admin.html
<h2></h2><script>var u = new URL(location.href);var a = u.searchParams.get("userName");var b = u.searchParams.get("pwd");document.querySelector("h2").innerText = "你之前的登录账号" + a + ",你的登录密码是" + b;</script>
代码分析:
在a.html中,我们在跳转页面的时候,给地址的后面追加了一个search部分,这个search不会影响跳转的,我们真正实现条件的是地址栏中pathname的部分,后面的hash和search部分对跳转没有影响
2、admin.html中,我们将location.href的地址转换成了url对象,然后通过url对象中的searchParams来操作search后面传递过来的参数
URL编码与转码
当网页地址出现中文的,浏览器在操作的时候会自动编码
这个时候我们需要方法将其转回来
var str = encodeURIComponent(location.href + "?abc=急啊急啊");//把中文转换var url = decodeURIComponent(str); //把转码之后结果解码
总结:
以上便是JavaScript,HTML,CSS的知识总结,接着会持续更新Ajax,jQuery,vue,React