第01节:JavaScript概述
一、JavaScript概述
1995年,JavaScript问世,主要目的是处理表单验证。起初命名为LiveScript,后来因为java语言盛行,更名为JavaScript,目的是希望借着Java的火爆流行起来(JavaScript的开发者一定想不到JavaScript在20多年后的今天会如此盛行)。
1997年,欧洲计算机制造商协会发布了ECMAScript,在接下来的几年里,web浏览器厂商就开始将ECMAScript作为JavaScript实现的标准。
2009年,Node.js问世,JavaScript这门语言逐步在后台占据一席之地,目前,前端开发的大量工具都基于node.js。
2015年,ECMAScript2015(ES6)正式发布,使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
后续版本统称ES2015+,ES2015是完全向下兼容的
二、JavaScript是做什么的
在前端,通过javascript可以实现更多的页面交互,与后台的数据交互,以及更为丰富的网页效果。
在后台,借助node的运行环境,使用javascript一门语言,即可完成服务器端开发,我们会在后续的章节中讲解如何使用JavaScript做服务端开发,本章主要内容仍然面向前端开发。。
三、hello world
JavaScript放在<script></script>
这个标签里
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script>
alert("hello world");
</script>
</body>
</html>
四、控制台
chrome浏览器中的console
选项,他是JavaScript的控制台工具,我们可以在其中输出JavaScript程序,也可以在其中看到程序运行结果。例如我们在网页中编写如下代码:
console.log('hello world');
可以看到控制台中会输出hello world
。
第02节:变量与数据类型
一、变量的基本概念
变量可以理解为是一个存储数据的容器
代码如下所示:
var n = 100;
var s = "hello world";
var s1="hello",s2="world";
通过var声明一个变量,var
后面的英文字母就是变量名,变量名是自定义的,在一定的规则下我们可以随意命名(下一部分我们来讲命名规则)。
每行结尾的分号并不是必须写的,但是为了代码更加规范,我们要求每行代码的结尾都要写分号,用以表示本行结束(注意必须是英文半角的分号)。
二、变量的命名规范
代码如下所示:
var age = 17;
var num1 = 198;
var num2 = 200;
var price = 25.6;
var _name = "小明";
var $fruit = "苹果";
var firstName = "Lily";
var message = "I love javascript";
- 变量名要见名知意
- 变量名可以是字母、下划线、$,还有数字;但是不能以数字开头
- 小写字母开头,多个单词,第二个单词首字母大写(驼峰命名)
- 不可以与关键字、保留字重复
三、数据类型
JavaScript有六种数据类型,如下表所示:
类型名称 | 值 | 说明 |
---|---|---|
数值 | 100;3.14 | 不管是整数还是小数,都是数值型。 |
字符串 | "hello";"100" | 双引号或单引号中的值是字符串。 |
布尔 | true;false | 布尔值只有两个值,代表真和假。 |
空 | null | 空值只有null,后续讲解。 |
未定义 | undefined | 未定义值只有undefined,后续讲解 |
对象 | {} | 未初始化时可以暂时赋值为null |
关于undefined
//尽量不要让变量值是undefined
var v;//undefined
var a = undefined;//不要直接给变量赋undefined,因为不赋值就是
//为变量初始化时尽量这样写
var str = "";//初始化字符串
var num = 0;
var obj = null;
第03节:表达式与运算符
一、表达式概述
字面量
赋值符号
=
右边某种数据类型的值,就是字面量例如字符串
"hello world"
或是数字100
都是字面量
表达式
通过运算符将变量、字面量组合起来,就是表达式。
每一个表达式都有一个固定返回值(表达式的结果)
"hello" + "world" //"helloworld"
100 + 200 //300
二、运算符
算数运算符
运算符 | 描述 | 示例 | 结果 |
---|---|---|---|
+ | 加法 | 20 + 10 | 7 |
- | 减法 | 20 - 10 | 10 |
* | 乘法 | 20 * 10 | 200 |
/ | 除法 | 20 / 10 | 2 |
% | 求于 | 11 % 2 | 1 |
++ | 自增1 | ++7 | 8 |
-- | 自减 | --7 | 6 |
var num = 1;
console.log(num++); //输出1
console.log(++num); //输出2
console.log(num++ + ++num);//输出4
自增和自减运算符如果写在变量后面,那么表达式的返回值是变量本身,然后变量自增或自减,运算符写在变量前面,那么变大时的返回值直接就是变量自增或自减后的结果。
比较运算符
比较运算符的返回值是布尔值
运算符 | 描述 | 示例 | 结果 |
---|---|---|---|
> | 大于 | 20 > 10 | true |
< | 小于 | 20 < 10 | false |
>= | 大于等于 | 20 >= 10 | true |
<= | 小于等于 | 20 <= 10 | false |
== | 等于(会进行类型转换,不比较数据类型,效率低) | 20 == "20" | true |
!= | 不等(会进行类型转换,不比较数据类型,效率低) | 20 != 10 | false |
=== | 恒等于(不进行类型转换,会比较类型,效率高) | 20 === "20" | false |
!== | 非恒等于(不进行类型转换,会比较类型,效率高) | 20 !== "20" | true |
逻辑运运算符
运算符 | 描述 | 示例 | 结果 |
---|---|---|---|
&& | 逻辑与 | true && false | false |
|| | 逻辑或 | true || false | true |
! | 逻辑非 | !true | false |
赋值运算符
运算符 | 描述 | 示例 | 等同于 |
---|---|---|---|
= | 赋值 | x = 10 | 无 |
+= | 加并赋值 | x += y | x = x + y |
-= | 减并赋值 | x -= y | x = x - y |
*= | 乘并赋值 | x *= y | x = x * y |
/= | 除并赋值 | x /= y | x = x / y |
第04节:条件语句
一、概述
语句执行流程有三种:顺序执行、条件执行、循环执行。
条件语句表示的就是按照条件判断执行哪些代码(或不执行哪些代码)。
二、if语句
if语句是最基本的条件控制语句,它让JavaScript程序可以选择执行顺序,我们可以通过一个布尔值来控制一行语句是否执行,if语句有多种形式,下面我们一一介绍: 示例代码如下:
if(true)
console.log("执行代码");
在上面的代码中,if括号内的值如果是true,则执行第二行代码,如果是false,则不执行第二行代码。
if后面的括号内一般不会直接写一个布尔值,而是写一个表达式, 示例代码如下:
var num1 = 10;
var num2 = 20;
if(num1<num2) //如果改成num1>num2,则不会输出下面的文字
console.log("num1小于num2")
上面我们将的两个例子都是通过判断条件来执行一行代码,但是大多数情况,我们需要执行多行代码,那么我们需要在if后面加上一对花括号,并且,为了让代码块更直观,我们在以后的代码中,都会写if后面的花括号。 示例代码如下:
var num1 = 10;
var num2 = 20;
if(num1<num2){
console.log("判断num1是否小于num2");
console.log("num1小于num2");
}
通过上面的例子,我们通过判断条件确定是否执行某一个代码块,下面我们通过if...else...语句实现:在两个代码块中,选择一个来执行 示例代码如下:
var num1 = 10;
var num2 = 20;
if(num1<num2){
//num1如果小于num2,表达式为true,输出if语句后的代码;num1如果大于num2,表达式为false,输出else语句后的内容
console.log("num1小于num2");
}else{
console.log("num1大于num2");
}
if...else语句可以判断两种情况下,需要执行哪些代码,如果需要判断的条件是三种情况,我们可以使用if...else if...语句 示例代码如下:
var num1 = 10;
var num2 = 20;
if(num1<num2){
console.log("num1小于num2");
}else if(num1>num2){
console.log("num1大于num2");
}else if(num1===num2){
console.log("num1等于num2");
}
我们可以通过修改num1和num2的值来判断输出哪一行语句。
通过控制运算符来实现数学运算
var num1 = 10;
var num2 = 20;
var sign = "+"; //通过修改操作符,输出不同的结果
var result = 0; //result用来存储计算的结果,现在设置一个初始值0
if(sign === "+"){
result = num1 + num2;
console.log(result)
}else if(sign === "-"){
result = num1 - num2;
console.log(result)
}else if(sign === "*"){
result = num1 * num2;
console.log(result)
}else if(sign === "/"){
result = num1 / num2;
console.log(result)
}else{
console.log("请输入正确的运算符")
}
三、switch语句
if语句在程序执行的过程中创建一条分支,并且可以使用if...else if...语句来处理多条分支,然而当所有的分支都依赖于同一个表达式的值时,重复计算多条if语句中的条件是非常浪费时间的做法,switch语句正合适处理这种情况
var num = 0; //通过修改num的值控制执行哪行语句
switch(num){
case 0:
console.log("num的值是零"); //当n===0,执行
break;
case 1:
console.log("num的值是一"); //当n===1,执行
break;
case 2:
console.log("num的值是二"); //当n===2,执行
break;
case 3:
console.log("num的值是三"); //当n===3,执行
break;
default:
console.log("其他"); //当n的值不是0,1,2,3,执行
break;
}
我们了解switch语句的语法,下面我们使用switch语句改写demo03,实现通过控制运算符来实现数学运算
var num1 = 10
var num2 = 20
var operator = " + "
switch(operator){
case " + ":
console.log("num1+num2=" + (num1 + num2) )
break;
case " - ":
console.log("num1-num2=" + (num1 - num2) )
break;
case " * ":
console.log("num1*num2=" + (num1 * num2) )
break;
case " / ":
console.log("num1/num2=" + (num1 / num2) )
break;
default:
console.log("其他")
break;
四、条件运算符
如果是简单的判断,我们可以使用条件运算符
表达式?第一个值:第二个值
如果表达式为true,表达式的返回值是第一个值,如果表达式为false,那么表达式的返回值是第二个值,示例代码如下所示示例示例代码如下:
var num1 = 10;
var num2 = 20;
var result = num1 > num2 ? 100 : 200;
//如果num1大于num2,条件表达式的值为100,若num1小于等于num2时,条件表达式的值为200;
console.log(result);
第05节:循环语句
条件语句的代码可以被想象成是一条条分支的路径,而循环语句的代码则是程序路径的一个回路,可以让一部分代码重复执行。JavaScript中的循环语句有for语句和while语句。
一、for语句
for语句的语法如下:
for(初始值;布尔值;计数器){
//语句块
}
在for语句中,如果布尔值是true,就会一直执行语句块中的内容,为了防止死循环,需要有一个计数器,当数值达到指定值,布尔值就会变成false,循环便停止了,下面的示例代码使用for循环输出0~9九个数字 示例代码如下:
for(var i = 0;i<10;i++){
// i的初始值是0
// 判断i是否小于10,如果小于10则执行花括号中的代码
// 每次执行完花括号中的代码后,i的值加1
console.log(i);
}
通过上面的例子我们进一步理解了for语句的用法,下面我们来做一个联系,利用for循环语句输出100以内所有正整数的加和 示例代码如下:
var sum = 0; //sum用来存储循环过程中正整数的加和
for(var i = 1;i<=100;i++){
sum += i;
}
console.log(sum); //这时候输出的就应该是5050
二、while语句
while语句语法如下所示:
while(bool){
//bool为true,循环执行
}
当bool为true的时候,花括号中的内容会循环执行。为了防止死循环,需要在循环的过程实现类似for计数器的功能,让循环在有限的次数内定制,下面我们使用while语句输出0~9是个数字 示例代码如下:
var n = 0;
while(n<10){
console.log(n);
n++;
}
在每次循环的过程中都会让n的值加1,这样当n的值等于10,循环便停止,下面我来使用while语句输出100以内所有正整数的加和 示例代码如下:
var n = 0;
var sum = 0;
while(n<=100){
sum += n;
n++;
}
console.log(sum);
三、continue
continue可以结束本次循环,直接进入到下一次循环,例如我们用for循环语句来实现输出0 ~ 5,7 ~ 9九个数字(跳过6) 示例代码如下:
for(var i = 0;i<10;i++){
if(i===6){
continue;
}
console.log(i);
}
上面的代码通过判断,实现当i的值为6的时候,跳过本次循环,直接接入下一次循环。下面我们使用continue来实现计算100以内所有不能被7整除的正整数加和 示例代码如下:
var sum = 0;
for(var i = 0;i<=100;i++){
if(i%7===0){
continue;
}
sum += i;
}
console.log(sum);
四、break
在学switch语句中,我们已经接触到了break,它可以让分支语句在结束一个case之后,跳出switch语句,break同样可以用在循环语句当中,当代码执行到break时,直接结束循环 示例代码如下:
for(var i = 0;i<10;i++){
if(i===6){
break;
}
console.log(i);
}
如上面的代码所示,当控制带输出5之后,循环结束。
第06节:函数基础
一、函数的基本概念
函数是一个可执行的语句块,定义的时候不执行,调用的时候执行,使用"函数名()"的形式可以调用函数
语法如下:
function fun(){ //定义函数,函数名为fun
//函数体
}
fun(); //调用函数
我们先来编写第一个最简单的函数,当这个函数执行的时候会在控制台输出"hello function" 示例代码如下:
function fun(){
console.log("hello function")
}
fun();
在上面的代码中我们定义了一个函数,并调用了一次,这样就会在控制台输出一次“hello function”。我们在编程的过程中,很多代码是需要多次使用的,我们可以把它们写在一个函数中,这样我们每次希望执行这些代码的时候,只需要调用这个函数,而不是复制-粘贴多次代码。
二、参数
下面编写一个函数sum,输出10和20两个数之和 示例代码如下:
function sum(){
var num1 = 10;
var num2 = 20;
var result = num1 + num2;
console.log(result);
}
sum();
在上面例子中我们调用sum函数,可以成功在控制台输出计算结果,但是函数内部的代码是固定的,虽然可以多次使用,但是每次使用输出的都是10和20的加和,为了让函数更加灵活,我们希望实现一个函数可以计算任意两个数的加和,那么我们就需要了解函数是如何传递参数的。
下面一个简单的例子演示函数如何传递参数 示例代码如下:
function fun(str){
console.log("hello" + str);
}
fun("world");
在定义函数的括号中,我们添加了一个参数str,这个参数叫做形参。它在函数内部像一个变量一样。但是在函数调用之前他是没有值的。当调用函数的时候,调用函数的括号中我们也添加了一个参数"world",这个参数叫做实参,他可以是任意数据类型的值。函数被调用后,形参str被赋予了实参"world"的值,然后执行console.log便会在控制台输出"helloworld"
上面的函数有一个形参和一个实参,函数可以传递多个参数,用逗号间隔 示例代码如下:
function sum(num1,num2){
console.log(num1 + num2);
}
sum(10,20);
当我们调用函数的时候,实参和形参是一一对应的,10对应的是num1,20对应的是num2,函数执行后会在控制台输出10和20的加和。
下面我们来编写一个函数,它有一个正整数参数n,当我们调用函数时,函数会输出包括n在内,1~n所有正整数的加和 示例代码如下:
function sum(n){
var sum = 0;
for(var i = 0;i<=n;i++){
sum += i;
}
console.log(sum);
}
sum(100);
sum(567);
我们通过一个函数规定了一种计算方式,我们使用函数的只要输入一个值,函数就会计算出一个准确的结果。
三、返回值
在上面的例子中,我们输入了一个参数,函数就可以在控制台输出我们希望得到的结果,但是在实际开发中,很多情况我们要的不是在控制台输出得到的结果,而单纯的只是为了获取这个值,那么我们就需要用到函数的返回值。
在函数中,我们可以通过return关键字指定一个返回值,函数有了return,当函数被调用的时候就可以把调用的结果赋值给另一个变量了 示例代码如下:
function fun1(){
}
function fun2(){
return "hello fun";
}
var str1 = fun1();
var str2 = fun2();
console.log(str1); //输出undefined
console.log(str2); //输出"hello fun"
在上面的例子中,函数fun1没有返回值,所有将fun1调用的结果赋值给str1,str1的值为undefined,函数fun2有返回值,返回值是"hello fun",所以当fun2被调用后,将函数运行的结果赋值给str2,str2的值就是"hello fun"
下面我们来编写一个函数,让函数来计算四则运算的结果
function count(num1,sign,num2){
var result = 0;
switch(sign){
case "+":result = num1 + num2;break;
case "-":result = num1 - num2;break;
case "*":result = num1 * num2;break;
case "/":result = num1 / num2;break;
default:console.log("请输入真确的操作符")
}
return result;
}
console.log(count(10,"*",20));
四、函数表达式
var functionName=function(arg){
//函数体
}
这种形式看起来好像是常规的变量赋值语句,即创建一个函数并将它赋值给变量functionName。这种情况下创建的函数叫做匿名函数。因为function关键字后面没有标识符。
函数表达式与其他表达式一样,在使用之前必须先赋值;如下面代码就会导致错误;
helloworld(); //错误,还未赋值,函数不存在
var helloworld=function(){
console.log("hello world");
}
有了函数表达式,我们就可以给函数表达式赋值了;如下面代码:
var helloworld; //声明
if(condition){ //条件
helloworld=function(){ //赋值
console.log("hello world");
}
}
else{
helloworld=function(){ //赋值
console.log("你好,世界");
}
}
五、函数声明提升
func()
function func () {
}
上例不会报错,正是因为 ‘函数声明提升’,即将函数声明提升(整体)到作用域顶部(注意是函数声明,不包括函数表达式),实际提升后结果同下:
// 函数声明提升
function func () {
}
func()
==函数表达式没有函数声明提升==
六、作用域
在 JavaScript 中, 对象和函数同样也是变量。 在 JavaScript 中, 作用域为可访问变量,对象,函数的集合。 JavaScript 函数作用域: 作用域在函数内修改。
局部作用域
变量在函数内声明,变量为局部作用域。 局部变量:只能在函数内部访问。
// 此处不能调用 carName 变量
function myFunction() {
var carName = "Volvo";
// 函数内可调用 carName 变量
}
因为局部变量只作用于函数内,所以不同的函数可以使用相同名称的变量。 局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁。
全局变量
变量在函数外定义,即为全局变量。 全局变量有 全局作用域: 网页中所有脚本和函数均可使用。
var carName = " Volvo";
// 此处可调用 carName 变量
function myFunction() {
// 函数内可调用 carName 变量
}
如果变量在函数内没有声明(没有使用 var 关键字),该变量为全局变量。 以下实例中 carName 在函数内,但是为全局变量。
// 此处可调用 carName 变量
function myFunction() {
carName = "Volvo";
// 此处可调用 carName 变量
}
第07节:对象
一、对象的基本概念
对象是属性的集合,是六种数据类型之一。
二、自定义对象
我们可以通过一对花括号来创建一个对象 如下所示:
var obj = {};
在花括号中,我们可以为对象定义属性,下面我们来写一个猫的对象 示例代码如下:
var cat = {
//定义一个对象cat,它有两个属性,name和age
name:"喵喵",
age:2
}
//有两种方法可以获取到对象的属性值:1、对象名.属性名;2、对象名["属性名"]
console.log(cat.name);
console.log(cat["name"]);
创建一个空的对象
var student = null;
三、方法
如果对象的属性值是函数,那么我们叫这个属性为这个对象的方法
var cat = {
name:"喵喵",
age:2,
sayName:function(){
console.log("我是喵喵");
}
}
cat.sayName();
上面的代码可以在控制台输出“我是喵喵”,其中sayName是cat的方法。
方法简写
var cat = {
name:"喵喵",
age:2,
sayName(){
console.log("我是喵喵");
}
}
cat.sayName();
四、this关键字
在上面的例子中,我们可以给cat的那么属性重新赋值,代码如下 示例代码如下:
var cat = {
name:"喵喵",
age:2,
sayName:function(){
console.log("我是喵喵")
}
}
cat.name = "小白";
console.log(cat.name); //输出"小白"
cat.sayName(); //输出"我是喵喵"
因为猫的名字已经改变,但是sayName方法里面的名字并没有一同变化,我们可以通过this关键字实现修改了名字,方法里面的名字也会改变。
在对象的方法中使用this,可以指向这个对象本身, 示例代码如下:
var cat = {
name:"喵喵",
age:2,
sayName:function(){
console.log("我是"+this.name)
}
}
cat.sayName(); //输出“我是喵喵”
cat.name = "小白";
console.log(cat.name); //输出"小白"
cat.sayName(); //输出“我是小白”
五、方法传参
给对象的方法传递参数和给函数传递参数是一样的,下面我们来定义一个会算数的猫 示例代码如下:
var cat = {
name:"喵喵",
age:2,
sayName:function(){
console.log("我是"+this.name)
},
count:function(num1,num2){
console.log(num1+num2);
}
}
cat.sayName();
cat.count(10,20);
我们在之前代码的基础上,有添加了一个count方法可以让猫可以计算两个数的加和,我们只要传入实参,猫就能计算结果。
六、对象展开运算符
let xiaoMing = {name:"小明",age:2}
let superXiaoMing = {weight:"1000kg",fly:true}
let superHero = {
...xiaoming,
...sukperXiaoming
}
console.log(superHero)
七、对象的分类
- 自定义对象:封装
- 内置对象:(例如Date,获取当前时间)
- 宿主对象(document)
- 第三方库对象(jQuery,Vue)
第08节:数组
一、数组的基本概念
数组是一个特殊的对象,对象的概念是属性的集合,而数组是元素的有序集合。我们可以通过一个中括号来定义一个数组
var numlist = [1,3,5,7,9];
//new + 构造函数
var list = new Array(1,3,5,"7","9");
在上面的代码中我们定义了一个数组,数组有5个元素,五个元素都是数值型。我们可以通过数组的变量名配合中括号来获取数组的元素
var numlist = [1,3,5,7,9];
console.log(numlist[0]); //输出数组的第一个元素
console.log(numlist[4]); //输出数组的第五个元素
中括号中的数字叫做数组的下标,我们可以通过下标获取数组的元素,要注意的是下标是从零开始的。
数组是特殊的对象,它有自己的属性和方法,其中最常用的属性就是length,它可以获取数组元素的个数。
var numlist = [1,3,5,7,9];
console.log(numlist.length) // 输出5
数组中的元素可以是任意类型的,但是我们一般将数组中的元素设置成相同数据类型,下面是一个字符串类型的数组,我们通过下标分别输出数组的所有元素。 示例代码如下:
var friends = ["小明","小亮","小红"];
console.log(friends[0]); //小明
console.log(friends[1]); //小亮
console.log(friends[2]); //小红
console.log(friends.length); //长度为3
二、遍历数组
- while
var friends = ["小明","小亮","小红","张三","李四","王五"];
var i=0;
while(i<friends.length){
console.log(friends[i++]);
}
- for
var friends = ["小明","小亮","小红","张三","李四","王五"];
for(var i = 0;i<friends.length;i++){
console.log(friends[i]); //输出数组中的所有元素
}
- for in(i是下标)
var friends = ["小明","小亮","小红","张三","李四","王五"];
for(var i in friends){
console.log(friends[i]);
}
- for of(i是元素)
var friends = ["小明","小亮","小红","张三","李四","王五"];
for(var i of friends){
console.log(i);
}
- map方法
var friends = ["小明","小亮","小红","张三","李四","王五"];
friends.map(function(value,index){
console.log("下标为"+index+"的元素是"+value);
})
三、数组的常用方法
map方法
map()方法创建一个新的数组,元素为使用此数组中的每个元素调用参数所提供的函数的结果
var list = ["a","b","c","d","e"]
list.map(function(value,index){
console.log(value);
})
push方法
push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。示例代码如下:
var list = ["a","b","c","d","e"]
list.push("f")
console.log(list)
sort方法(排序)
var list = [5,3,6,7,2]
list.sort()
console.log(list)//2,3,5,6,7
join方法
用于把数组中的所有元素放入一个字符串,参数为间隔符,默认为","
var list = ["a","b","c"]
var str = list.join();
console.log(str);//输出a,b,c
str = list.join("");
console.log(str);//输出abc
str = list.join("+");
console.log(str);//输出a+b+c
split方法
是string的方法,可以把一个字符串拆分成数组,参数是间隔符,参数为空时会把字符串转换为只有一个元素的数组,数组元素是一个字符串
var str = "a b";
var list=str.split("");
console.log(list);//["a"," ","b"]
filter方法(过滤器)
创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
const list = [1,2,3].filter((v)= > {
console.log(v); //输出为1,2,3
return v>1; //返回到新数组中
})
console.log(list); //输出为[2,3];
find方法
find()方法返回数组中第一个满足回调函数测试的第一个元素的值。否则返回undefined;示例代码如下:
const arr1 = [1, 2, 3, 4, 6, 9];
let found = arr1.find(e => e > 5); // 6
第9节:内置对象
一、内置对象概述
javascript为我们提供了很多内置对象,这些内置对象为我们提供了很多语言的基本功能。我们之前学过的数组就是JavaScript的内置对象,除了数组我们还应该了解的内置对象有:Math、Date、RegExp。
需要说明的是document对象是DOM提供的对象,不属于JavaScript内置对象,window对象是BOM中的对象,同样不属于JavaScript内置对象。
二、Math
Math对象不像数组那样需要我们手动去创建,我们在JavaScript程序中直接写Math代表的就是Math对象。我们可以通过Math对象直接获取圆周率
var pi = Math.PI;
console.log(pi);
Math对象提供了很多方法来简化我们的数学运算,下面简单列举几个方法
var pi = Math.PI;
var num1 = Math.floor(pi); //向下取整
var num2 = Math.ceil(pi); //向上取整
var num3 = Math.round(pi); //四舍五入
var num4 = Math.abs(-pi); //获取绝对值
var num5 = Math.random(); //获取[0,1)随机数
console.log(num1);
console.log(num2);
console.log(num3);
console.log(num4);
console.log(num5);
在Math对象的方法中,floor和random两个方法比较常用,我们可以通过这两个方法获取我们想要的随机数范围。例如我们想要1~10的随机数,代码如下
var number = Math.floor(Math.random()*10 + 1);
console.log(number);
下面我们来实现一个猜数字的游戏,JavaScript随机生成一个1~100之间的数字,我们通过文本输入框输入我们所猜的数字,猜的数字不管是大于结果,还是小于结果,还是等于结果,都会有相应的提示 代码如下
<input type="text" id="number">
<button id="guess">猜数字</button>
<script>
var target = Math.floor(Math.random()*100+1); //生成1~100的随机数。
var btn = document.querySelector("#guess");
var number = document.querySelector("#number");
btn.onclick = function(){
var value = Number(number.value);
if(value > target){
alert("大于结果");
}else if(value < target){
alert("小于结果");
}else if(value === target){
alert("回答正确");
}
}
</script>
三、Date
Date对象是JavaScript用于处理日期和时间的对象,我们可以通过Date对象获取当前的时间,需要说明的是Date对象获取的时间是本机的时间
var dateNow = new Date();
var year = dateNow.getFullYear(); //获取年,不能用getYear()方法,此方法已经被废弃
var month = dateNow.getMonth(); //获取月份 从0开始,一月份返回的值是0
var date = dateNow.getDate(); //获取日期
var hours = dateNow.getHours(); //获取小时
var minutes = dateNow.getMinutes(); //获取分钟
var seconds = dateNow.getSeconds(); //获取秒
var day = dateNow.getDay(); //获取星期
console.log(year);
console.log(month);
console.log(date);
console.log(hours);
console.log(minutes);
console.log(seconds);
console.log(day);
我们可以通过JavaScript将当前的时间显示在网页上
var h1 = document.querySelector("h1");
var dateNow = new Date();
var hours = dateNow.getHours();
var minutes = dateNow.getMinutes();
var seconds = dateNow.getSeconds();
var strTimeNow = hours + ":" + minutes + ":" + seconds;
h1.innerHTML = strTimeNow;
上面的例子我们成功将当前的日期显示在h1标签中,但是我们显示的时间是获取的那个时间点,显示的时间是静止不动的,我们可以通过计时器方法让我们显示的时间与实际时间同步
var h1 = document.querySelector("h1");
function getTime(){
var dateNow = new Date();
var hours = dateNow.getHours();
var minutes = dateNow.getMinutes();
var seconds = dateNow.getSeconds();
var strTimeNow = hours + ":" + minutes + ":" + seconds;
h1.innerHTML = strTimeNow;
}
getTime();
setInterval(getTime,1000);
我们将过去时间的代码放到了一个函数中,然后通过计时器方法每秒执行一次这个函数,这样我们显示出来的日期就想电子钟一样每秒与真实事件同步一次。
我们还可以通过参数创建一个指定时间的日期对象,我们修改一下
var dateNow = new Date("2017-5-1 17:30:20"); //创建指定日期和时间的对象
var year = dateNow.getFullYear();
var month = dateNow.getMonth();
var date = dateNow.getDate();
var hours = dateNow.getHours();
var minutes = dateNow.getMinutes();
var seconds = dateNow.getSeconds();
var day = dateNow.getDay();
我们在编写程序的时候,有的时候会希望获取一个唯一的时间点,我们可以使用getTime方法或得。 示例代码如下
var dateTarget = new Date("2017-5-1 17:30:20");
var dateNow = new Date();
var target = dateTarget.getTime();
var now = dateNow.getTime();
console.log(target);
console.log(now);
通过getTime方法可以获取时间戳,时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总毫秒数,我们可以用时间戳表示一个不会重复的时间点。
课后练习
制作一个倒计时的功能,要求如下:
- 计算距离指定日期还有多少天,多少小时,多少分钟,多少秒。
- 在控制台输出这个时间。
第10节:正则表达式
一、正则表达式概述
正则表达式用于匹配字符串,例如我们想验证某一个字符串是否为邮箱格式,可以使用正则表达式判断;我们希望特换一片文章中的所有英文字母,可以使用正则表达式;我们想截取一片文章中的某些内容,也可以使用正则表达式。
正则表达式对象RegExp是JavaScript中的内置对象,我们可以像创建数组一样创建它。
var arr = new Array(); //创建数组
var reg = new RegExp(); //创建正则表达式
在开发中,我们一般用简写的方法创建正则表达式,同样和数组比较,代码如下
var arr = [1,2,3]; //创建数组
var reg = /123/; //创建正则表达式
二、正则表达式语法
正则表达式可以用来匹配字符串,我们可以把正则表达式看做是一种规则,如果字符串中的内容符合这种规则,就会匹配,如果不符合这个规则,就不会匹配
var reg = /123/; //创建了一个正则表达式,这个正则表达式可以匹配字符串"123"
var str = "123"; //创建字符串"123"
console.log(reg.test(str)); //test方法可以测试字符串是否成功匹配,匹配返回true,不匹配返回false
我们可以看到程序在控制台输出了true,因为reg定义的时候就是为了匹配字符串123的,我们再来看下面的例子
var reg = /123/;
var str = "012345";
console.log(reg.test(str)); //仍然返回true
我们将字符串改成了"12345",返回结果仍然返回true,这是因为我们定义的正则表达式可以匹配任何包含"123"的字符串,为了证实这个说法,我们可以使用exec方法来输出匹配的内容
var reg = /123/;
var str = "012345";
console.log(reg.exec(str)); //exec方法返回一个数组,数组中包含匹配的内容,如果未匹配,返回null
这段代码在控制台输出了一个数组,数组的第一个元素是匹配的内容,大家可以看到匹配的内容是"123",数组还带有两个属性,index表示从字符串中第几个字母开始匹配,input表示被匹配的字符串的值。
有的时候,我们希望我们写的正则表达式只能匹配"123",如果是字符串包含"123"页不匹配,如果希望这样的话,我们需要改进我们的正则表达式
var reg = /^123$/; //^表示开头,$表示结尾,两个符号之间是匹配的内容
var str = "012345";
console.log(reg.exec(str));
这样就可以只能匹配字符串"123",但是如果正则表达式是直接把要匹配的内容写在正则表达式里,那意义也不是很大,接下来我们用正则表达式强大的语法来匹配各种字符串。
设定匹配范围
正则表达式可以通过[]设定匹配的范围,代码如下
var reg = /[123]/; //匹配123中的任意一个字符
var str = "02468";
console.log(reg.exec(str));
通过指定范围,正则表达式成功匹配了字符串中的数字2。
匹配数字
正则表达式可以在匹配范围中定义[0-9]来设定匹配数字
var reg = /[0-9]/; //匹配一位数字
var str = "02468";
console.log(reg.exec(str));
虽然字符串中都是数字,但是一个[]只能表示匹配一位数字,所以这里匹配的是0
匹配字母
正则表达式可以在匹配范围中定义[a-z]来设定匹配字母
var reg = /[a-z]/; //匹配一位字母
var str = "012345abcde";
console.log(reg.exec(str));
与匹配数字类似,上面的正则表达式可以匹配一位字母。
匹配多位
上面的匹配内容匹配的都是一位数字或字母,我们可以通过+来指定匹配多位
var reg = /[a-z]+/; //匹配多位字母
var str = "012345abcde";
console.log(reg.exec(str));
上面的代码表示匹配多位字母,所有abcde都成功的被匹配
匹配指定位数
有的时候我们需要匹配指定位数的字符,可以通过{}指定匹配的位数
var reg = /[a-z]{3}/; //匹配3位字母
var str = "012345abcde";
console.log(reg.exec(str));
下面我们来定义一个正则表达式来匹配一个邮箱格式的字符串,我们先来确定一下邮箱的格式:
- 5~12位的数字字母下划线开头
- 后面接@
- 后面接2~5位的数字和字母
- 后面接.
- 后面接com
var reg = /^[a-zA-Z0-9\_]{5,12}\@[a-zA-Z0-9]{2,5}\.com$/;
var str = "test123@qq.com"; //邮箱
console.log(reg.exec(str));
很多特殊符号在正则表达式都有特殊含义,为了取消它的特殊含义,我们需要在特殊符号之前加“\”将其转义。、
三、表单验证
通过上面的学习,我们已经对正则表达式有了初步的了解,下面我们来实现一个表单验证邮箱的功能,验证通过或者不通过,都要在文本框后面输出结果
<input type="text">
<span></span>
<script>
var input = document.querySelector("input");
var span = document.querySelector("span");
var reg = /^[a-zA-Z0-9\_]{5,12}\@[a-zA-Z0-9]{2,5}\.com$/;
input.onblur = function(){
if(reg.test(this.value)){
span.innerHTML = "验证通过";
}else{
span.innerHTML = "验证失败";
}
}
</script>
四、总结
在实际项目中,我们通常会用插件去实现字符串验证的功能。所以,大家了解了正则表达式基本语法之后,再去学习那些深度的东西
^:开头
$:结尾
[]:范围
{}:位数
():分组
+:同{1,}
?:同{0,1}
.:任意一位字符
\:转义
\d:同[0-9]
\w:数字、字母、下划线
\s:空格、换行
//在正则表达式结尾加g表示全局匹配
//如下会把所有小写字母去掉
var str="1234qweqwr"
var reg = /[a-z]/g
var result=str.replace(reg,"");
console.log(result);
课后练习
- 使用正则表达式,完成手机号验证;
- 使用正则表达式,完成邮箱验证;
第11节:常量变量与解构赋值
一、ECMAscript概述
ECMAscript简称ES,是JavaScript的标准,我们经常说的ES5,ES6等等,可以称作JavaScript的版本,我们在之前学过的所有JavaScript特性,都是基于ES5版本的,今天我们开始讲解的是ES6标准的特性。ES6已更名为ES2015,ES7等后续的版本,我们都可以统称为ES2015+
二、变量和常量
ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。我们之前一直使用var定义变量,在ES6版本中,我们可以使用let定义变量,下面我们来说说var与let的区别
块级作用域
ES5只有全局作用域和函数作用域,没有块级作用域的概念,这带来了很多不合理的场景。 实例代码如下:
for(var i = 0;i<10;i++){
console.log(i); // 0-9
}
console.log(i); // 10
因为没有块级作用域,所以我们在for语句的外面仍然能获取i的值,在实际开发中,这是一个不可理喻的场景,我们希望的是这个i只在for语句内有效,所以再ES6中添加了块级作用域的概念,我们可以用let声明变量,问题就解决了 实例代码如下:
for(let i = 0;i<10;i++){
console.log(i); // 0-9
}
console.log(i); // 报错 i is not defined
因为用let声明变量,变量只在块级作用域下有效,所以再for语句之外输出i会报错。另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
不存在变量提升 定义:函数声明和变量声明总是被JavaScript解释器隐式地提升(hoist)到包含他们的作用域的最顶端。 通过定义我们可以知道,只有变量的声明和函数的声明存在变量提升这一说,那么在ES2015+出现之前,JavaScript声明变量的方式是通过关键字var实现的,声明函数自然通过function啦,ES6中我们用let,const来声明变量和常量。 实例代码如下:
{
console.log(a) // 报错,a is not defined
let a=2;
}
如果let存在变量提升,那么上述代码就相当于下面这样
{
let a;
console.log(a) //undefined
a=2;
}
由此可见,let并不存在变量提升。
不允许重复声明
实例代码如下:
var a = 10;
var a = 20;
let b = 10;
let b = 20; //报错
多次声明是没有意义的,在ES6中,我们使用let声明变量限制了不能多次声明,如果多次声明同一个变量会报错。
常量
在ES6中,不仅有变量,还增加了常量的概念,我们用const声明常量,一旦声明,它的值就不能再改变 实例代码如下:
const PI = 3.1415926;
PI = 3 //报错
我们说常量不能再改变,说的是不能重新为这个常量赋值,但是如果常量存储的是一个对象,那我们是可以改变这个对象的属性的 实例代码如下:
const obj = {name:'小明'};
obj.name = '小红';
console.log(obj.name); //小红
三、模板字符串
之前我们也可以使用JavaScript输出模版字符串,通常是下面这样的:
<h1 id="result"></h1>
<script src="jquery.js"></script>
let person = {
name:"name",
age:"age"
}
$("#result").append(
"He is <b>"+person.name+"</b>"+"and we wish to know his"+person.age+".That is all"
);
但是我们可以看到:这样的传统做法需要使用大量的“”(双引号)和 + 来拼接才能得到我们需要的模版。但是这样是十分不方便的。
于是ES6中提供了模版字符串,用`(反引号)标识,用${}将变量括起来。上面的例子可以用模版字符串写成下面这样:
$("#result").append(
`He is <b>${person.name}</b>and we wish to know his${person.age}.that is all`
);
这样的做法就简洁了很多,我们不需要再使用大量的""和+来拼接字符串和变量。
上面代码中,模板字符串都是用反引号表示,如果在模板字符串中需要使用反引号,则需要在反引号前面用反斜杠转义。
var mes = `\`hello\` World!`; // `hello` World!
模板字符串是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
多行字符串
用单引号或是双引号表示的字符串在编辑的过程中只能在一行显示,若要在多行显示需要在每一行结尾添加一个斜杠,这样的编辑方式对开发者显然不够友好,我们可以使用模板字符串的功能换行编辑字符串 代码如下所示:
let str = `hello
world`;
console.log(str);
因为使用了模板字符串,所以hello world如上所示并没有报错,如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。
字符串中嵌入变量
我们在开发的过程中经常会遇到在字符串中嵌入变量的情况,以往我们都是使用字符串连接的方式。有了模板字符串,我们可以在字符串中添加变量或对象的属性,需要将变量名写在${}之中 代码如下所示:
const student = {
name:"小明",
age:2
}
console.log(`我是${student.name},我今年${student.age}岁了`);
这样的表达方式可以更友好地将代码呈现给开发者。
模板字符串调用函数
我们不仅可以将变量和对象的属性嵌入模板字符串,还可以将函数嵌入模板字符串,并显示出函数的返回值 代码如下所示:
let x = 'hello';
let y = 'world';
function fn(x,y){
return x + y;
}
console.log(`程序员最常用的一句话是${fn(x,y)}`);
如果模板字符串中嵌入的值是需要通过计算而得到的,我们就可以使用上面的方法来完成这个功能。
四、解构赋值
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值,解构赋值主要包括数组的解构赋值、对象的解构赋值、字符串的解构赋值、函数参数的解构赋值。
数组的结构赋值
实例代码如下:
//传统赋值
var num1 = 1;
var num2 = 2;
var num3 = 3;
//数组的解构赋值
let [str1,str2,str3] = ["hello","world","javascript"];
console.log(str1);
console.log(str2);
console.log(str3);
对象的解构赋值 解构不仅可以用于数组,还可以用于对象。 实例代码如下:
let {num1,num2} = {num1:100,num2:10};
console.log(num1);
console.log(num2);
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
字符串的结构赋值 字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象 示例代码如下:
let [a,b,c,d,e] = "hello";
console.log(a);
console.log(b);
console.log(c);
console.log(d);
console.log(e);
// 类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello';
//{length : len} length是长度的意思,len 是 let 声明的一个常量
console.log(len) //长度为数字的5
函数参数的结构赋值 函数的参数也可以使用解构赋值。 实例代码如下:
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
上面代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y。对于函数内部的代码来说,它们能感受到的参数就是x和y。
五、课后练习
- 熟练使用本节讲解的基础语法;
第12节:函数进阶
一、概述
在此前的内容中,我们已经学习了函数的基本用法,例如如何定义函数,如何调用函数,以及函数的形参、实参、返回值等内容。本节内容会继续讲解ES2015为函数赋予的新特性。
- 立即执行函数
- 闭包
- 函数默认值
- 箭头函数
- async函数
二、立即执行函数
立即执行函数顾名思义,就是声明之后立刻执行的函数,实例代码如下所示:
(function(){
console.log("hello world");
})()
函数体被一个小括号包裹起来,然后后面紧跟着一个小括号,这样当函数被声明之后,就立刻被调用。
立即执行函数通常用来封装代码,例如下面的例子:
(function(){
let a = 10;
let b = 20;
console.log(a + b)
})()
这段代码可以输出a + b
的结果,但是立即执行函数外部是不能访问a和b的,因此成功达到了封装的目的。
在比较古老的js代码中,大家都是这种方法来封装代码的,但是ES2015自带模块化语法,因此这种封装的写法就不太常见了,大家只做了解即可。
三、闭包
闭包是定义在函数内部的函数,我们用立即执行函数编写一个闭包的例子
(function(){
function add(a, b){
return a + b
}
let result = add(10, 20)
console.log(result)
})()
立即函数内部的add函数就是闭包,闭包的特点是具有全局性,闭包内部的this关键字会指向全局对象window。
闭包的特性:内部函数未执行完,外部函数即使执行完成,外部函数中的变量也不会被销毁。
四、函数默认值
在ES2015版本之前,我们可以通过下面的方法设置函数参数的默认值 代码如下所示:
function fun1(x,y){
x = x || 100;
y = y || 200;
return x + y;
}
上面的实例代码,利用逻辑或运算符的能力重新给参数赋值,也就是说如果有参数传入,则赋值为传入的参数,如果没有,则为或运算的第二个值。
这种设置默认值的方式看起来很不友好,ES2015中新增了更直观的设置函数默认值的方法,实例代码如下所示:
//es2015+设置默认值
function fun2(x=100,y=200){
return x + y;
}
五、箭头函数
在此前的课程中,我们学习了两种方法定义函数,
- 函数声明
- 函数表达式
在ES2015中增加了箭头函数的语法,可以使用=>
来定义函数,我们来对比一下三种写法,实例代码如下所示:
//函数声明
function add(a, b){
return a + b
}
//函数表达式
const add = function(a, b) {
return a + b
}
//箭头函数
const add = (a, b) => {
return a + b;
}
=>
左侧的括号存放参数=>
右侧的花括号存放函数体。
箭头函数简写
以上展示了一个完整的箭头函数的语法,这其实还不能体现箭头函数的优势。
箭头函数第一个优势是,简写的箭头函数可以让代码更简洁
- 如果函数只有一个形参,那么可以省略参数外面的括号。
- 如果函数体只有一个表达式作为返回值,可以省略花括号和
return
关键字
实例代码如下所示
const fun = x=>x*x;
let result = fun(6);
console.log(result) //36;
箭头函数中的this
箭头函数不仅简化了函数的写法,而且让函数中的this指针变得更人性化。
此前我们学习过的,函数中的this指向谁,取决于是哪个对象调用了这个函数。
例如下面中的示例代码
const person = {
sayMe: function () {
console.log(this)
},
sayMeDelay: function () {
(function(){
console.log(this);
})()
}
}
person.sayMe();
person.sayMeDelay();
在实例代码中
- sayMe方法输出的this是person这个对象,因为是person直接调用的sayMe。
- sayMeDelay方法因为加入了一个延迟函数,导致setTimeout中的函数由windows对象调用,因此this指向windows对象。
但是在开发的过程中,我们通常希望sayMedelay内部
const person = {
sayMe: function () {
console.log(this)
},
sayMeDelay: function () {
(() => {
console.log(this);
})()
}
}
person.sayMe();
person.sayMeDelay();
在上面的代码中,我们定义了一个变量self用来存储指向person的this,这样我们就可以正确地输出lee。但是这样的写法看起来有给我们增加了一些复杂度,我们可以用箭头函数来解决这个问题,
const person = {
firstName: "lee",
sing: function () {
console.log(this.firstName)
},
singDelay: function () {
setTimeout(() => {
console.log(this.firstName);
}, 2000)
}
}
person.sing();
person.singDelay();
在上面的代码中,成功地输出了两个lee,这是因为箭头函数中的this,不是调用函数时的this指向的对象,而是定义函数时this指向的对象,定义函数的时候,this指向的是person而不是window,所以这里可以正确地输出结果。
六、课后练习
- 说出箭头函数与普通函数的区别。
- 使用箭头函数延迟调用cat.sayName,输出cat对象的name属性。
第13节:面相对象
一、面相对象概述
首先面向对象是一种编程思想,是一种通过多个对象互相协作完成处理流程的编程思路【是对现实世界中一类事物的抽象,在编程中可以理解为是一种建立现实世界事物的模型】 推及到广义上,面向对象已经越了程序设计和软件开发,我认为面向对象又是一种思维方式,不局限于编程语言,甚至不局限编程本身,它把复杂的需求、业务逻辑抽丝剥茧、逐个分析。 主要分为:类的声明定义、对象的创建使用、面向对象拥有的特征【三大特征:封装、继承、多态】 封装:体现了对象对于敏感数据的保护特征 继承:体现了代码的复用和功能的扩展 多态:体现了不同操作环境中代码的多样性【程序的健壮性】 思路:大量的程序开发—软件的开发—解决问题—处理数据—CRUD【增删改查】
二、基于原型的面向对象
在ES2015版本之前,JavaScript是没有类的概念的,我们可以使用构造函数来模拟一个类,这在我们之前的课程中已经讲解过了,这里我们简单复习一下。
创建一个猫的构造函数
function Cat(name,age){
this.name = name;
this.age = age;
}
var cat = new Cat("miaomiao",2);
console.log(cat.name);
在上面的代码中,我们定义了一个构造函数Cat,通过var关键字创建了一个变量cat,cat有两个属性,name和age。我们可以直接输出cat.name;
我们可以通过原型属性为构造函数添加方法,例如我们给这个Cat添加一个shout方法,让这只猫可以叫 代码如下所示:
function Cat(name, age) {
this.name = name;
this.age = age;
}
Cat.prototype.shout = function(){
console.log("喵喵喵!")
}
var cat = new Cat("miaomiao", 2);
cat.shout();
这样猫就有了shout方法,可以调用shout方法让猫叫。
通过上面的代码,我们可以模拟一个类的概念,但是这样的写法与真正面相对象语言的写法相比,确实更难以理解。所以ES2015中增加了类的概念。
三、ES2015中的面向对象
基于原型的继承方式,虽然实现了代码复用,但是行文松散且不够流畅,可阅读性差,不利于实现扩展和对源代码进行有效的组织管理。不得不承认,基于类的继承方式在语言实现上更健壮,且在构建可服用代码和组织架构程序方面具有明显的优势。所以,ES2015+中提供了基于类class的语法。但class本质上是ES2015+提供的一颗语法糖,正如我们前面提到的,JavaScript是一门基于原型的面向对象语言。
我们可以用class来定义一个类,然后可以在这个类中定义构造函数,方法和属性。 代码如下所示:
class Cat{
constructor(name,age){
this.name = name;
this.age = age;
}
shout(){
console.log("喵喵喵");
}
}
let cat = new Cat("miaomiao",2);
cat.shout();
上面的这段代码与第二个案例的代码功能是完全一样的,但是通过class关键之定义一个猫的类,让这个类更像是一个整体,而非一个个零散的prototype组合起来的一个功能。
继承
- 使用prototype属性(ES5的方案,了解即可)
function Animal(name){
this.name = name;
}
Animal.prototype.sayName = function(){
console.log(this.name);
}
function Dog(name){
this.name = name;
}
Dog.prototype = new Animal();
let dog = new Dog("二哈");
dog.sayName();
- ES6以后使用extends关键字(掌握)
class Animal{
constructor(name) {
this.name = name;
}
sayName(){
console.log(this.name);
}
}
class Dog extends Animal{
constructor(name,age){
super(name);
this.age = age;
}
getAge(){
console.log(this.age);
}
}
let dog = new Dog("二哈",13);
dog.sayName();
dog.getAge();
课后练习
按要求实现功能:
- 拓展Date对象实现如下功能:实现dateFomate方法,返回值是 "xxxx年xx月xx日"。
Date.prototype.dateFormate = function (){
return `${this.getFullYear()}年${this.getMonth()+1}月${this.getDate()}日`}
let date = new Date();
console.log(date.dateFormate());
- 定义一个Person类,让Student类和Teacher类继承Person,为Person类添加getlnfotmation方法,是student类和Teacher类都可以通过此方法获取个人信息
class Person{
constructor(name,age) {
this.name = name;
this.age = age;
}
getInformation(){
return `我是${this.name},${this.age}岁`;
}
}
class Student extends Person{
constructor(name, age, id) {
super(name, age);
this.id = id;
}
getInformation() {
return super.getInformation()+`,学号是${this.id}`;
}
}
let s = new Student("小明",10,1332);
console.log(s.getInformation());
第14节:DOM(文档对象模型)
一、DOM的基本概念
本章节可谓是相当重要了,是让JavaScript灵动起来必不可少的一步
- 文档对象模型
- 定义了树状结构
- 定义了接口,可以用来操作树状结构
二、样式操作
docunment.getElementById()
:返回值是一个DOM节点docunment.getElementByClassName()
:返回值是一个DOM节点的集合- dom节点的属性innerHTML可以获取、设置元素内的所有内容
我们可以通过DOM提供的querySelector方法来获取元素,参数为CSS选择器,然后进一步操作它的样式 示例代码如下:
<h1>DOM样式测试</h1>
<script>
var h1 = document.querySelector("h1");
//querySelector的返回值是一个DOM对象,该方法可以通过选择器获取元素,若选择器找到多个元素,则返回第一个。
h1.style.color = "red";
//DOM对象的style属性可以设置元素内联样式。
</script>
若需要通过js设置多个元素的样式,可以使用querySelectorAll方法, 示例代码如下:
<ul>
<li>香蕉</li>
<li>苹果</li>
<li>鸭梨</li>
</ul>
<script>
var ali = document.querySelectorAll("li");
//querySelectorAll方法的返回值是一个类数组的集合,里面保存的是获取的所有元素,所以如果希望为每一个元素设置样式,需要遍历这个集合。
for(var i = 0;i<ali.length;i++){
ali[i].style.color = "red";
}
</script>
三、绑定事件
事件就是文档或者浏览器窗口发生的一些特定的交互瞬间,例如:用户点击网页会触发点击事件(onclick),用户在元素上移动会触发鼠标移动事件(onmouseover/onmouseenter),鼠标移出(onmouseout/onmouseleave)又恢复原本模样等。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box{
width:100px;
height:100px;
background-color: red;
}
</style>
</head>
<body>
<button>按钮</button>
<div class="box"></div>
<script>
let btn = document.querySelector("button");
let box = document.querySelector(".box");
box.onmouseenter = function(){
this.style.backgroundColor = "blue";
}
box.onmouseleave = function(){
this.style.backgroundColor = "red";
}
// 事件监听函数
btn.onclick = function(){
box.style.backgroundColor = "yellow"
}
</script>
</body>
</html>
我们将一个函数赋值给一个事件,当这个事件被触发的时候,这个函数就会被执行。
四、操作属性
我们可以通过JavaScript获取和设置元素属性,例如input的value属性值,或者img的src属性。
首先我们来实现一个效果,在文本框中输入字符串,然后点击按钮用在控制台输出我们输入的字符串 示例代码如下:
<input type="text">
<button>输出</button>
<script>
var input = document.querySelector("input");
var btn = document.querySelector("button");
btn.onclick = function(){
var text = input.value; //获取input的value属性
console.log(text);
}
</script>
我们还可以通过赋值的方式为一个元素设置属性,可以切换所示图片 示例代码如下:
<img src="images/0210_logo.jpg" alt="">
<button>切换图片</button>
<script>
var img = document.querySelector("img");
var btn = document.querySelector("button");
btn.onclick = function(){
img.src = "images/0210_img.jpg";
}
</script>
当点击按钮的时候,通过赋值的方式把另一张图片的地址赋值给img标签的src属性,就实现了图片切换的效果
五、数学计算案例
下面我们来实现一个能完成数学计算的程序,页面有四个文本框和一个按钮,我们在第一个文本框输入一个数字,在第二个文本框输入一个操作符,第三个文本框再输入一个数字,然后当我们点击计算按钮的时候,会在第四个文本框计算出结果,这个例子和我们学习switch语句的时候写的例子很像,但是那时候我们没有可操作的页面,现在我们把计算功能写在一个函数中。 示例代码如下:
<input type="text" id="num1">
<input type="text" id="sign">
<input type="text" id="num2">
<input type="text" id="result">
<button>计算</button>
<script>
var num1 = document.querySelector("#num1");
var sign = document.querySelector("#sign");
var num2 = document.querySelector("#num2");
var result = document.querySelector("#result");
var btn = document.querySelector("button");
btn.onclick = function(){
var n = Number(num1.value); //将字符串类型转换成数字类型
var m = Number(num2.value);
var r = count(n,sign.value,m);
result.value = r;
}
function count(n,s,m){ //n是第一个数字,s是操作符,m是第二个数
switch(s){
case "+":return n+m;break;
case "-":return n-m;break;
case "*":return n*m;break;
case "/":return n/m;break;
}
}
</script>
第15节:DOM操作
一、节点的分类
- 元素节点(querySelector;querySelectorAll)
- 属性节点(element.src;element.id)
- 文本节点(innerHTML) 上一节课我们讲解了获取元素节点,操作属性节点。本节课我们讲解添加和删除元素节点和编辑文本节点。
二、文本节点
在html中我们有一个h1标签和一个按钮,h1标签内已经有了一段文本。当我们点击按钮的时候,在h1标签中插入“hello world”
<h1>内容:</h1>
<button>添加文本节点</button>
<script>
var h1 = document.querySelector("h1");
var btn = document.querySelector("button");
btn.onclick = function(){
var textNode = document.createTextNode("hello world");
//createTextNode方法可以创建一个文本节点
h1.appendChild(textNode);
//appendChild方法可以将textNode节点添加到h1标签中。
}
</script>
在DOM中还有另一个属性可以更方便地获取和设置文本节点,这个属性是innerHTML,我们写一个简单的例子来测试innerHTML属性 示例代码如下:示例连接
<h1>内容:</h1>
<button>添加文本节点</button>
<script>
var h1 = document.querySelector("h1");
var btn = document.querySelector("button");
btn.onclick = function(){
h1.innerHTML = "hello world";
//设置h1的文本节点,innerHTML的内容会完全覆盖原节点的内容
}
</script>
对比着两种方法,第一种方法需要创建文本节点,然后通过appendChild方法将节点追加到之前文本内容的后面,第二种方法则是直接用innerHTML覆盖之前文本节点的内容。如果要实现demo01的功能,需要改写一下事件内的代码示例连接
var str = h1.innerHTML;//获取文本内容
h1.innerHTML =str + "hello world"; //原文本内容与新文本内容连接
三、创建和添加元素节点
上一节我们讲解了如何获取和设置文本节点,本节讲解如何创建和添加元素节点。创建原始节点可以使用createElement方法,添加元素节点仍然可以用appendChild方法。接下来我们来一步一步完成一个任务列表的功能,html代码如下所示:
<button>添加节点</button>
<ul>
<li>香蕉</li>
<li>苹果</li>
<li>鸭梨</li>
</ul>
我们要是先一个功能,当点击按钮的时候,在列表中添加一个li元素
var btn = document.querySelector("button");
var ul = document.querySelector("ul");
btn.onclick = function(){
var li = document.createElement("li"); //创建一个元素节点,li元素
ul.appendChild(li); //在ul元素中添加li元素
}
在这个案例中,我们已经成功地在ul标签中添加了li元素,但是li元素并没有文本节点,我们进一步改进点击事件中的内容
var li = document.createElement("li");
li.innerHTML = "鸭梨";
ul.appendChild(li);
通过上面的代码,我们已经可以在ul中添加带有文本节点的li元素了,但是文本节点是固定的“鸭梨”,我们还可以进一步通过一个文本框,让用户自己填写要插入的内容
<script>
var btn = document.querySelector("button");
var ul = document.querySelector("ul");
var text = document.querySelector("input");
btn.onclick = function(){
var li = document.createElement("li"); //创建一个元素节点,li元素
ul.appendChild(li); //在ul元素中添加li元素
li.innerHTML = text.value
}
</script>
四、删除元素节点
我们可以通过removeChild方法删除元素,下面的例子我们来实现点击按钮,删除h1标签的效果
<button>删除</button>
<div class="box">
<h1>待删除的内容</h1>
</div>
<script>
var btn = document.querySelector("button");
var box = document.querySelector(".box");
var h1 = document.querySelector("h1");
btn.onclick = function(){
box.removeChild(h1);
}
</script>
通过上面的代码可以知道,删除一个元素需要知道他的父级元素,然后通过父级元素的removeChild方法删除子级元素,那么如果不确定删除的元素的父级是哪个元素,我们该如何获取元素的父级元素呢,可以使用parentNode方法,我们直接来改写上面的代码
<button>删除</button>
<div>
<h1>待删除的内容</h1>
</div>
<script>
var btn = document.querySelector("button");
var h1 = document.querySelector("h1");
btn.onclick = function(){
var box = h1.parentNode;
box.removeChild(h1);
}
</script>
下面我们来实现一个删除水果列表中水果的功能
<ul>
<li><span>香蕉</span> <span class="del">删除</span></li>
<li><span>苹果</span> <span class="del">删除</span></li>
<li><span>鸭梨</span> <span class="del">删除</span></li>
<li><span>芒果</span> <span class="del">删除</span></li>
<li><span>草莓</span> <span class="del">删除</span></li>
</ul>
我们要实现点击删除按钮的时候,删除span父级的li元素
var aDel = document.querySelectorAll(".del");
for(var i = 0;i<aDel.length;i++){
aDel[i].onclick = function(){
var parent = this.parentNode
parent.parentNode.removeChild(parent)
}
}
第16节:事件流
一、绑定事件
想要给一个元素绑定事件,我们有两种方法:使用内联事件或事件监听器。在之前的课程中,我们一直使用的是内联事件来为元素绑定事件,例如一个按钮的点击事件,代码如下
btn.onclick = function(){} //绑定鼠标单击事件
我们还可以用使用事件监听器为元素绑定事件,代码如下
btn.addEventListener("click",function(){});
下面我们用两种方法为按钮绑定事件
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<script>
var btn1 = document.querySelector("#btn1");
var btn2 = document.querySelector("#btn2");
btn1.onclick = function(){
console.log("我是按钮1");
}
btn2.addEventListener("click",function(){
console.log("我是按钮2");
})
</script>
两种方法都能实现相同的效果,能成功的为按钮绑定了点击事件,但区别是使用addEventLitener可以无限制的为元素绑定事件,而内联事件后面的会覆盖前面的
var btn1 = document.querySelector("#btn1");
var btn2 = document.querySelector("#btn2");
btn1.onclick = function(){
console.log("我是按钮1");
}
btn1.onclick = function(){
console.log("我是按钮1,第二次绑定");
}
btn2.addEventListener("click",function(){
console.log("我是按钮2");
})
btn2.addEventListener("click",function(){
console.log("我是按钮2,第二次绑定");
})
第一个按钮第二次绑定的事件覆盖了第一次绑定的事件,第二个按钮两次绑定的事件都能被触发。
二、事件冒泡与事件捕获
接下来我们用事件监听器为三个div元素绑定点击事件,最外层的div宽高是300px,中间的div宽高都是200px,最内层的div宽高都是100px,那么思考一下,点击最内层的div,事件会如何触发,是只触发最内层的div,还是从内到外依次触发,还是从外到内依次触发
<div class="box1">
<div class="box2">
<div class="box3"></div>
</div>
</div>
var box1 = document.querySelector(".box1");
var box2 = document.querySelector(".box2");
var box3 = document.querySelector(".box3");
box1.addEventListener("click",function(){
console.log("我是box1")
})
box2.addEventListener("click",function(){
console.log("我是box2")
})
box3.addEventListener("click",function(){
console.log("我是box3")
})
通过上面的例子我们可以看到,事件是从最内层开始触发,然后依次向外,输出的顺序是box3-box2-box1。导致这种顺序的原因是因为:事件流有事件捕获阶段和事件冒泡阶段,事件捕获阶段是从最外层元素开始一层一层进入到事件目标(也就是我们点击的那个元素),到达事件目标后,进入事件冒泡阶段,事件从最内层流向最外层,事件默认情况下在冒泡阶段触发,所以我们看到的是先输出box3,最后输出box1。
我们也可以将事件设置为捕获阶段触发
box1.addEventListener("click",function(){
console.log("我是box1")
},true)
box2.addEventListener("click",function(){
console.log("我是box2")
},true)
box3.addEventListener("click",function(){
console.log("我是box3")
},true)
只要在添加事件方法中添加第三个参数为true,事件就会在捕获阶段被触发,这样输出的顺序就变成了box1-box2-box3。但是在日常开发中,我们几乎不用做此修改,让事件在冒泡阶段触发就可以了。
阻止事件冒泡
使用e.stopPropagation();可以阻止事件冒泡,即内层元素的事件触发不会导致外层元素的事件触发
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.big{
width:300px;
height:300px;
background-color: red;
}
.medium{
width:200px;
height:200px;
background-color: yellow;
}
.small{
width:100px;
height:100px;
background-color: blue;
}
</style>
</head>
<body>
<div class="big">
<div class="medium">
<div class="small"></div>
</div>
</div>
<script>
let big = document.querySelector(".big");
let medium = document.querySelector(".medium");
let small = document.querySelector(".small");
big.addEventListener("click",function(){
console.log("Hi,I'm big");
})
medium.addEventListener("click",function(){
console.log("Hi,I'm medium");
})
small.addEventListener("click",function(e){
console.log("Hi,I'm small");
e.stopPropagation();
})
</script>
</body>
</html>
阻止事件的默认行为
可以使用e.preventDefault()或者return fasle;
例如阻止a标签默认点击跳转到新页面
<!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>
<a href="http://baidu.com">baidu</a>
<script>
let a = document.querySelector("a");
a.onclick = function(e){
console.log('hello world')
// e.preventDefault();
return false;
}
</script>
</body>
</html>
三、事件委托
利用事件流的原理,通过e.target()我们可以实现事件委托,事件委托可以简单的理解为将子级的事件委托给父级来处理,我们先来看一个简单的例子
<div class="btnBox">
<button class="btn1">按钮1</button>
<button class="btn2">按钮2</button>
</div>
网页中有两个按钮,他们的父级是一个div标签,现在我们希望给这两个按钮绑定事件,当我们点击按钮的时候输出按钮的文本内容,按照我们之前学过的知识,可以有如下写法
//第一种写法
var btn1 = document.querySelector(".btn1");
var btn2 = document.querySelector(".btn2");
btn1.addEventListener("click",function(){
console.log(this.innerHTML)
})
btn2.addEventListener("click",function(){
console.log(this.innerHTML)
})
这种方法简单易懂,但是存在重复,两个按钮触发事件执行的代码完全一样,我们可以获取到所有按钮,再通过遍历绑定事件
//第二种写法
var btnArray = document.querySelectorAll("button");
for(var i = 0;i<btnArray.length;i++){
btnArray[i].addEventListener("click",function(){
console.log(this.innerHTML)
})
}
通过遍历我们优化了代码,但是仍然存在问题,首先,如果按钮的数量特别多,每一个按钮都绑定依次事件会非常影响程序的性能,其次,就算不考虑性能,通过这种方法绑定事件,如果使用js新增了一个按钮,这个按钮因为初始化的时候没有绑定事件,所以无法点击。为了解决上述问题,我们可以使用事件委托的方式来实现上面的功能
var btnBox = document.querySelector(".btnBox");
btnBox.addEventListener("click",function(event){
var target = event.target; //通过事件对象获取事件目标
console.log(target.innerHTML);
})
在事件监听函数中,我们可以在形参的位置获取到事件对象event,事件对象中包含了事件相关的信息,通过event.target可以获取到我们的事件目标,在这个例子中事件目标就是我们点击的按钮,而我们事件绑定的是按钮的容器,这样就可以将自己元素的事件委托给父级来处理。
四、事件类型
鼠标事件、键盘事件、触屏事件
键盘事件
document.onkeydown
和document.onkeyup
通过e.keyCode获取键盘按键
通过键盘上下左右实现方块的移动
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box{
width:100px;
height:100px;
background-color: red;
position: absolute;
top:100px;
left:100px;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
let box = document.querySelector(".box")
document.onkeydown = function(e){
let code = e.keyCode;
//element.offsetLeft获取元素相对于上级定位元素的左偏移量
//element.offsetTop获取元素相对于上级定位元素的上偏移量
switch(code){
case 37:box.style.left = box.offsetLeft - 5 + "px";break;
case 38:box.style.top = box.offsetTop - 5 + "px";break;
case 39:box.style.left = box.offsetLeft + 5 + "px";break;
case 40:box.style.top = box.offsetTop + 5 + "px";break;
}
}
</script>
</body>
</html>
触屏事件
element.ontouchstart
:在元素上按下element.ontouchend
:在元素上按下后放开element.ontouchmove
:在元素上滑动
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box{
width: 100px;
height: 100px;
background-color: red;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
let box = document.querySelector(".box");
box.ontouchstart = function(){
console.log("start")
}
box.ontouchend = function(){
console.log("end")
}
box.ontouchmove = function(){
console.log("hello world")
}
</script>
</body>
</html>
四、课后练习
一、实现如下功能(阻止事件冒泡)
- 点击一个按钮,显示一个容器的盒子;
- 点击容器,容器背景颜色改变;
- 点击容器按钮 容器关闭;
二、实现水果列表,让后添加的元素也可以删除(事件委托);
三、通过上下左右按键控制元素移动;
第17节:计时器方法
一、计时器方法概述
计时器方法可以实现在指定的时间过后,单次或重复调用函数的功能,setTimeout可以实现函数在指定毫秒数后单次执行,setInterval可以实现函数在指定毫秒数后重复执行,语法如下所示:
setTimeout(function(){
//一秒后执行
},1000);
setInterval(function(){
//一秒后执行,并且每隔一秒重复执行
},1000)
二、setTimeout(只执行一次)
下面我们来实现一个效果,页面加载3秒后在控制台输出hello world
setTimeout(function(){
console.log("hello world");
},3000)
当计时器开始计时后,我们可以使用clearTimeout方法让计时器停下来,下面我们来定义一个按钮,当页面加载后,如果我们在3秒钟之内点击按钮,计时器会停止,不会输出hello world,如果不点击按钮,3秒钟之后就会输出hello world
var btn = document.querySelector("button");
var t = setTimeout(function(){
console.log("hello world");
},3000)
btn.onclick = function(){
clearTimeout(t);
}
setTimeout方法会返回一个整数类型的值,通过这个值,我们可以停止计时器,我们将setTimeout方法的返回值赋值给一个变量,当点击按钮的时,使用clearTimeout()方法
,传入t,这样计时器就会停止,hello world就不会在控制台输出。
三、setInterval(不停执行)
setInterval的用法与setTimeout的用法非常类似,都是传入两个参数,第一个参数是计时器执行的函数,第二个参数是毫秒数。下面我们来实现一个效果,每3秒钟在控制台输出依次hello world
setInterval(function(){
console.log("hello world");
},3000)
从代码可以看出,setInterval与setTimeout完全相同,区别在于setInterval参数中的函数没个指定毫秒数后都会重复执行,当我们不希望计时器重复执行的时候,就可以使用clearInterval
方法来停止计时器
var btn = document.querySelector("button");
var t = setInterval(function(){
console.log("hello world");
},3000)
btn.onclick = function(){
clearInterval(t);
}
下面我们来实现一个效果,让控制台输出每隔1秒按顺序输出正整数,从数字1开始输出
var n = 1;
function showNumber(){
console.log(n);
n++;
}
setInterval(showNumber,1000);
showNumber(); //调用函数,可以在页面加载时直接输出1。
上面的代码与之前有一点区别,我们并没有直接给setInterval传递一个匿名函数,而是先定义了一个函数showNumber,然后将showNumber传递给setInterval,这两种写法效果是一样的,但是如果将匿名函数传入setInterval,这个函数将不能被调用。
在上面代码的结尾,我们在页面加载之后调用了一次showNumber,目的是为了让页面加载的时候就输出1,否则我们将要等待一秒之后才能看到控制台输出1。
这个案例会一直输出数字,下面我们来改进这个例子,当数字为10的时候就停止,效果看起来有些想之前讲过的for循环输出数字,但用计时器输出可以实现每个1秒输出一个数字,而不是连续的输出
var n = 1,t=0;
function showNumber(){
console.log(n);
if(n === 10){
clearInterval(t);
}
n++;
}
t=setInterval(showNumber,1000);
showNumber();
我们通过一个if语句判断n的值,当n到达10的时候,就停止计时器,这样计时器就不会再继续输出数字了。
我们还可以继续用按钮控制计时器,这次我们定义一个h1标签存放数字,再用两个按钮来实现“开始计数”和“停止计数”功能
<h1>0</h1>
<button id="start">开始计数</button>
<button id="stop">停止计数</button>
<script>
var btnStart = document.querySelector("#start");
var btnStop = document.querySelector("#stop");
var h1 = document.querySelector("h1");
var t;
btnStart.onclick = function(){
clearInterval(t);//防止连续点击开始计时,导致创建多个计时器
t = setInterval(function(){
var number = Number(h1.innerHTML); //将h1的文本节点转换成数字
number++;
h1.innerHTML = number;
},300);
}
btnStop.onclick = function(){
clearInterval(t);
}
</script>
在网页中我们经常会看到指定秒数跳转到其他网页,location.href
可以实现页面跳转,我们可以用计时器方法来实现这个功能
<p><span class="seconds">5</span>秒后跳转到百度</p>
<script>
var seconds = document.querySelector(".seconds");
setInterval(function(){
var s = Number(seconds.innerHTML);
s--;
seconds.innerHTML = s;
if(s === 0){
//location.href可以实现页面跳转
location.href = "http://baidu.com";
}
},1000)
</script>
四、防抖(debounce)与节流(throttle)
解决性能问题,短时间内多次触发事件会造成性能问题,尤其是事件内部有大量业务逻辑时。例如
widows.onscroll
窗口滚轮事件,一旦窗口滚动,就会在短时间内大量触发。还有input输入搜索内容,当停下输入时才执行搜索。
防抖
对于短时间内多次触发事件的情况,可以使用防抖停止事件持续触发
let timer = null;
window.onscroll = function(){
if(timer !== null){
clearTimeout(timer);
}
timer = setTimeout(() => {
console.log("hello world")
timer = null;
},2000)
}
节流
防止短时间内多次触发事件,但是在间隔一定时间后还是需要不断触发
//节流:每两秒钟执行一次
let mark = true;
window.onscroll = function(){
if(mark){
setTimeout(() => {
console.log("hello world");
mark = true;
},2000)
}
mark = false;
}
使用闭包封装防抖和节流
//使用闭包封装防抖函数
function debounce(fn,delay){
let timer = null;
function eventFun(){
if(timer !== null){
clearTimeout(timer);
}
timer = setTimeout(() => {
fn();
timer = null
},delay)
}
return eventFun;
}
//节流
function throttle(fn,delay){
let mark = true
//这种return更直观,推荐
return function(){
if(mark){
setTimeout(() => {
fn();
mark = true;
},delay)
}
mark = false;
}
}
window.onscroll = throttle(() => {
console.log("hello world");
},300);
第18节:BOM概述
一、概述
《javaScript高级程序设计》这本书讲过: javaScript = ECMAScript + DOM + BOM
- DOM: 文档对象模型 Document Object Model
- BOM:浏览器对象模型 Browser Object Model
二、Window对象(全局对象)
windows对象是全局对象,所有在浏览器可以直接使用的方法都是window对象的方法,其中document对象和alert()方法就是window的属性,使用时window.document和window.alert() 一般可省略window。
首先写一个打开vscode写一个案例计时器方法 示例代码如下:
window.setTimeout(()>={
console.log("hello word")
},1000)
如上代码时一个计时器方法使用setTimeout方法,一秒钟输出一次hello word;
计时器方法上节已经讲过了,本节主要讲的还是弹出框方法,弹出框的方法主要分为三种:
- alert()
alert("hello word") //直接弹出编写的参数
- prompt()
let str = prompt("请输入你的名字") //prompt方法中可以加入一个参数,但是通常我们只加入一个参数就可以了。
console.log(str) //点击确定控制台会输出弹出框的内容,点击取消会返回null
- confirm()
let result= confirm("确定是否删除此弹框") //confirm返回的是一个布尔值,也可以加入一个参数
console.log(result) //点击确定控制台会返回true 取消返回false
在开发应用中,一般不适用系统自带的弹出框,移动端可能会被屏蔽。
三、location对象
- location.href - 属性返回当前页面的URL - "https://www.baidu.com"
- location.hostname - 主机的域名 - "wwww.baidu.com"
- location.pathname - 当前页面的路径和文件名 "/s"
- location.port - 端口 - "8080"
- loaction.protocol - 协议 - "https:"
四、navigator对象
获取访问者浏览器的信息
navigator.userAgent 检查当前设备,并在控制台输出
判断访问者的浏览器类型
let equipment = checkagent(navigator.userAgent)
console.log(equipment)
function checkagent(ua) {
let isWindowsPhone = /(?:Windows Phone)/.test(ua),
isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,
isAndroid = /(?:Android)/.test(ua),
isFireFox = /(?:Firefox)/.test(ua),
isChrome = /(?:Chrome|CriOS)/.test(ua),
isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),
isPhone = /(?:iPhone)/.test(ua) && !isTablet,
isPc = !isPhone && !isAndroid && !isSymbian,
isWechat = /(?:MicroMessenger)/.test(ua);
let equipment = {
isTablet: isTablet,
isPhone: isPhone,
isAndroid: isAndroid,
isPc: isPc,
isWechat:isWechat
};
if (equipment.isAndroid) {
return "安卓手机";
} else if (equipment.isPhone) {
return "苹果手机";
} else if (equipment.isTablet) {
return "平板";
} else if (equipment.isPc) {
return "电脑";
}
}
第19节:原始类型与引用类型
一、原始类型与引用类型的基本概念
原始类型赋值给变量,遍历存储的是这个值本身,而你用类型赋值给变量,变量存储的是一个引用,这个引用会指向内存中的这个对象。
二、原始类型与引用类型的差异
接下来,我们在实际案例中展示一下原始类型与引用类型的区别:
原始类型与引用类型赋值的区别
实例代码(demo01.html)
var str1 = "hello world";
var str2 = str1;
str1 = "hello javascript";
console.log(str1); //hello javascript
console.log(str2); //hello world
字符串是原始类型,str1和str2两个变量存储的都是字符串类型的数据,所以对其中一个变量赋值不会影响第二个变量。
下面我们将程序中的字符串换成对象,再来看看这个例子(demo02.html)。
var obj1 = {name:'xiaoming'};
var obj2 = obj1;
obj1.name = 'xiaohong';
console.log(obj1.name); //xiaohong
console.log(obj2.name); //xiaohong
程序最后输出的两个值是相同的,但是我们只是将obj1的name属性赋值为xiaohong,并没有将obj2的name属性赋值为xiaohong,为什么他们都变成了同一个值?这就是原始类型与引用类型的区别:变量存储的原始类型,仅仅是存储它的值,所以我们将存储原始类型的变量重新赋值,不会影响其他变量,但是变量存储引用类型的时候,情况有所改变,变量并不是存储这个对象本身,而是存储这个对象的引用,而这个引用可以指向这个变量本身,所以当我们将obj1赋值给obj2的时候,其实是让这两个变量的引用同时指向一个对象,这样当我们改变这个对象的时候,两个变量都会有变化。
原始类型与引用类型比较的区别
接下来我们来看一下原始类型与引用类型比较的时候有什么来区别(demo03.html)。
var str1 = 'hello world';
var str2 = 'hello world';
console.log(str1 == str2); //true
var obj1 = {name:'xiaoming'};
var obj2 = {name:'xiaoming'};
console.log(obj1 == obj2); //false
在上面的代码中,两个字符串都是'hello world'他们比较后的返回值是true,但是两个对象name属性都是'xiaoming',比较后的返回值却是false,这也是原始类型与引用类型本质上的却别导致的,原始类型存的是值,比较的也是值,如果值相等,则返回true,如果值不等,则返回false,引用类型存的是应用,比较的也是引用,如果两个引用指向同一个对象,返回true,指向不同对象,则返回false,上面的例子中,两个对象虽然属性相同,但明显是不同的两个对象,他们就像两个重名的学生一样,即使有相同的名字,也不会是同一个人,所以返回值是false。
原始类型与引用类型传参的区别
下面我们分别将原始类型和引用类型当做参数传给一个函数,看看有什么样的区别(demo04.html)
var str = "hello world";
function fun(s){
s = "hello javascript";
}
fun(str);
console.log(str); //hello world
在上面的代码中我们将变量str传给函数fun,在fun内部将参数将另一个字符串赋值给参数,然后在函数外面输出str,发现str的值并没有变,然后我们再来看下面这个例子(demo05.html)
var obj = {name:'xiaoming'};
function fun(o){
o.name = 'xiaohong'
}
fun(obj);
console.log(obj.name); //xiaohong
将参数换成引用类型后,我们发现再次输出obj的时候,它的值已经变成了函数中赋的值,这是因为原始类型传参的时候,实参是形参的副本,改变形参的时候不会影响实参,而应用类型传参的时候,形参和实参的对象都指向一个引用,这样当我们修改形参的值的时候,其实是操作了内存中的对象,所以函数外部变量的值也就跟着变化了。
原始类型与引用类型的类型检测
- 原始类型:typeof(值)
- 引用类型:值 instanceof 类型
三、对象的浅度克隆
上面我们说了原始类型与引用类型的区别,在实际开发的过程中,有一种情况,我们需要得到一个对象的备份。例如我们有一个对象:
var student = {
name:'xiaoming',
age:2,
sex:'male'
}
现在希望或得一个student对象的克隆对象,这两个对象所有属性都相同,我们修改其中一个对象的时候不会影响另一个对象。我们来编写一个函数,参数是一个对象,返回值是这个对象的克隆对象。
我们首先需要了解一下for...in语句(demo06.html)
var student = {name:'xiaoming',age:2,sex:'male'};
for(var i in student){
console.log("属性"+i);
console.log("值"+student[i]);
}
我们通过for...in语句可以遍历对象中的属性和属性值,这样我们就可以很容易地实现对象克隆的功能(demo07.html)。
var student1 = {name:'xiaoming',age:2,sex:'male'};
function clone(obj){
var newobj = {}; //创建一个新对象
for(var i in obj){ //遍历传进来的参数对象
newobj[i] = obj[i]; //将传进来的参数的属性和属性值赋值给新对象的同名属性。
}
return newobj; //返回新创建的对象
}
var student2 = clone(student1);
student1.name = 'xiaohong';
console.log(student1.name);
console.log(student2.name);
在上面的代码中,我们成功复制了student1,当我们修改student1的时候,student2不会改变。但是在上面的例子中,对象的所有属性值都是原始类型,如果将对象换成下面的对象
var student = {
name:'xiaoming',
age:2,
sex:'male',
friends:["Lily","lucy"]
}
然后再用我们的clone方法克隆这个对象,就会发现,当我们改变friends属性的时候,两个对象都会改变。所以这样的clone方法只能叫“浅度克隆”,如果希望对象中所有属性都能被克隆,那么需要“深度克隆”,“深度克隆”我们会在下一章讲解。
第20节:异步编程
一、同步与异步
异步: 可以多条任务线去执行程序,一条任务卡顿不影响其他任务。
二、异步数据场景
function getData(){
setTimeout(() => {
let data = "hello world"; //模拟一个异步数据
return data; //无法通过返回值输出数据。return只能返回同步的数据
},500)
}
let data = getData();
console.log(data) //值为undefined,普通函数无法通过返回值返回异步数据。
二、回调函数
回调函数是将函数作为参数传递给另一个函数,通过回调函数可以获取异步数据,实例代码如下所示:
function getData( next ){ //参数为一个函数
setTimeout(() => {
let data = "hello world"; //模拟一个异步数据
next(data); //将数据传递给这个函数
},500)
}
getData((data) => {
console.log(data); //可以获取到异步数据
})
接下来制作一个案例巩固这个例子:定义两个异步获取数据的函数,一个是getFirstName,另一个是getLastName,将两个函数获取到的数据连接成为一个名字,实例代码如下所示:
function getFirstName( next ){
setTimeout(() => {
let data = "李";
next(data);
},500)
}
function getLastName( next ){
setTimeout(() => {
let data = "小明";
next(data);
},500)
}
getFirstName(first => {
getLastName( last => {
let name = first + last;
console.log(name);
})
})
虽然成功获取到了数据,但是通过上面的例子,我们发现,多个异步数据使用回调函数来嵌套,代码的维护成本会越来越高,接下来,我们使用es2015的语法来逐步优化这个案例。
三、promise对象
在ES2015中新加入了Promise对象,Promise对象用来解决异步问题,关于异步问题,我们会在第8章详细讲解,本章只要概括性地了解Promise对象的语法即可,创建一个Promise对象的语法如下所示:
// resolve的作用是把异步获取的数据结果传递出来。
let getData = new Promise((resolve) => {
let data = "string data";
resolve(data);
})
Promise构造函数的参数是一个函数,而函数的形参resolve同样也是一个函数,调用resolve可以将数据从promise对象中传递出来。
使用Promise对象,模拟刚才的异步获取数据,实例代码如下所示:
// promise的构造函数,需要传递一个参数
function getLastName(){
return new Promise(function(resolve){
// resolve也是一个函数
setTimeout(() => {
let data = "小明"
resolve(data);
},500)
});
}
// 客户端代码
let promise = getLastName();
promise.then(function(d){
console.log(d);
})
使用Promise对象,定义刚才案例中的两个函数,一个是getFirstName,另一个是getLastName,将两个函数获取到的值链接到一起。
四、async函数
async函数
async函数与Promise对象结合使用,可以优雅地处理异步问题,声明async函数的语法如下所示:
async function fun(){
//async函数内部可以使用await关键字
}
我们用一个实际的案例来讲解async函数与Promise对象的用法。
在Promise对象的示例中,我们用resolve函数将数据传递出来之后,然后可以用async函数中的await关键字接收数据,完整的实例代码如下所示。
// resolve的作用是把异步获取的数据结果传递出来。
let getData = new Promise((resolve) => {
let data = "string data";
resolve(data);
})
async function showData(){
let data = await getData;
console.log(data)
}
showData();
await后面跟着一个Promise对象,可以获取到Promise对象内部resolve传递出来的数据,这这里需要注意的是:
await关键字必须写在async函数内部。
第21节:倒计时效果
一、倒计时效果需求分析
在电商网站中我们经常会遇到倒计时抢购的网页效果,在页面中会有一个倒计时的始终,当时间距离目标时间越近,时钟的时间就越小。需要统计日、时、分、秒。当最后一秒结束后,一个抢购按钮从不可点击变成可点击。
二、需求分析
通过需求的描述,我们知道程序中有两个时间,一个是当前时间,一个是目标时间,当前时间是不断变化的,目标时间不会变化。倒计时始终记录的是当前时间和目标时间的时间差。那么如何计算两个时间的时间差呢,我们可以通过时间戳来实现
var target = new Date("2017-8-5 22:30");
var targetTime = target.getTime();
function getTimeNow(){
var now = new Date();
var nowTime = now.getTime();
console.log(targetTime - nowTime);
}
setInterval(getTimeNow,1000);
我们已经输出了距离目标时间的时间戳,现在我们需要一个函数将毫秒数转换成天、小时、分钟和秒。
var ms = 17420000; //定义一个毫秒数
function getResultTime(ms){
var days = Math.floor(ms / (24 * 60 * 60 * 1000)); //计算出天数
var ms1 = ms % (24 * 60 * 60 * 1000); //计算出天后还剩多少毫秒
var hours = Math.floor(ms1 / (60 * 60 * 1000)); //计算出小时
var ms2 = ms % (60 * 60 * 1000); //计算出小时候还有多少毫秒
var minutes = Math.floor(ms2 / (60 * 1000)); //计算出分钟
var ms3 = ms % (60 * 1000); //计算出分钟后还有多少毫秒
var seconds = Math.floor(ms3 / 1000); //计算出秒数
return { //返回值是一个对象,这个对象包括了日、小时、分钟、秒
days:days,
hours:hours,
minutes:minutes,
seconds:seconds
}
}
var time = getResultTime(ms);
console.log(time.days);
console.log(time.hours);
console.log(time.minutes);
console.log(time.seconds);
下面我们可以通过这个行数,将我们得到的距离目标时间的毫秒数,计算出距离目标时间的天、小时、分钟和秒,并将时间更新到页面上。 示例代码如下
<h1></h1>
<script>
var h1 = document.querySelector("h1");
var target = new Date("2017-8-5 22:30");
var targetTime = target.getTime();
var ms;
function getTimeNow(){
var now = new Date();
var nowTime = now.getTime();
ms = targetTime - nowTime;
var time = getResultTime(ms);
var timeResult = time.days + "天" + time.hours + "小时" + time.minutes + "分钟" + time.seconds + "秒";
h1.innerHTML = timeResult;
}
setInterval(getTimeNow,1000);
function getResultTime(ms){
var days = Math.floor(ms / (24 * 60 * 60 * 1000));
var ms1 = ms % (24 * 60 * 60 * 1000);
var hours = Math.floor(ms1 / (60 * 60 * 1000));
var ms2 = ms % (60 * 60 * 1000);
var minutes = Math.floor(ms2 / (60 * 1000));
var ms3 = ms % (60 * 1000);
var seconds = Math.floor(ms3 / 1000);
return {
days:days,
hours:hours,
minutes:minutes,
seconds:seconds
}
}
</script>
评论区