PHP的使用
PHP 一门后端语言
-
为什么要学习一个后端语言呢?
-
目前市场上的需求,要求前端人员掌握一个后端语言
-
方便和后端开发人员进行交互
-
动态网页和静态网页
- 静态网页: 网页中的数据都被前端写死了的就是静态网页
- 动态网页: 网页中的数据来自远程服务端的就是动态网页
开发中的组织架构
- 就是开发项目中有哪些人
- 产品,UI,前端,后端,测试,运维
- 项目功能中的数据流向: 登录功能
+ 用户点击登录的时候,前端第一时间可以获取到用户名和密码
- 但是前端此时无法告知用户是否登录成功,前端将数据传递给后端
+ 后端接受到数据,后端此时也不知道用户登录成功还是失败
- 后端可以连接数据库,将获取到数据去数据库中比对,得到结果
- 然后将结果返回 给前端
+ 前端接收到后端返回的结果,我们将结果展示在页面中
+ 用户=>页面=>前端=>后端=>数据库=>后端=>前端
前端不能直接操作数据库
服务器
- 服务器: 提供服务的机器(计算机-电脑)
+ 其实就是一台电脑
+ 只不过在这台电脑上安装了一些特殊的软件(提供服务的软件)
+ 我们手中的电脑就可以作为服务器使用(本地服务器)
- 特殊的软件
+ 提供web服务的 服务器---nginx,apache...
- 可以让浏览器进行访问的
+ 提供数据库服务器 的服务器 mysql
- 提供数据存储查询服务
+ 我们安装的小皮软件(phpstudy)里面就包含了可以提供web服务和数据库服务的软件
- 小皮安装: 按照教程一步一步安装
- 小皮提供的环境:
- wamp: windows+apache+mysql+php
- wnmp: windows+nginx+mysql+php
- 小皮的使用:
- 注意: 如果之前安装过web服务器,和mysql服务器,则否卸载了
- 访问web服务器,通过浏览器的localhost访问
+ 访问到的是WWW目录下的文件内容
访问服务器
要有服务器(本地记得将小皮打开启动)
请求访问的地址
- 完整请求地址
`https://www.baidu.com:443/search/error.html#66`
+ `https://`
- http就是web请求的传输协议
- https也是web请求的传输协议
+ `www.baidu.com` 域名
- 最早期的时候访问服务器,都是通过ip地址
- ip地址由四组0~255的数字组成: 比如: 10.20.159.107
- ip地址不便于记忆
- 于是有一个群人,创建了一个万维网--将ip地址和字母一一对应
- 字母组成的就是我们现在看到的域名
+ `:443` 端口号
- 早期的服务器是只有一个盘符
- 这个盘符中有0~255的文件夹
- 每一个文件夹中有0~255的文件夹
- 256*256=65536个文件夹 这就是对应的端口号
- 一些常见服务的端口号
+ https: 443
+ http: 80
+ mysql: 3306
+ ftp: 21
+ `/search/error.html` 访问路径 端口号到?之间的内容
+ `https://www.baidu.com/s?ie=utf-8&f=3&rsv_bp=1&rsv_idx=1&tn=baidu&wd=阿里巴巴&inputT=7719&rsv_sug4=8992`
+ 比如上面的请求地址中: /s 就是访问路径
+ `ie=utf-8&f=3&rsv_bp=1&rsv_idx=1&tn=baidu&wd=阿里巴巴&inputT=7719&rsv_sug4=8992` 查询字符串
+ 查询字符串 是在请求服务器的时候,要给服务器传递的数据
+ `#66` 哈希字符串
+ 路由的跳转
+ 锚点
- `协议://域名:端口号/请求地址?查询字符串#哈希字符串`
后端 php语言
- `c++ java python php go nodejs .net`
我们学习后端语言需要
首先可以知道后端要做的事情,不需要我们去写后端,如果需要我们写的话,我们在原有的基础上可以修改使用
后端语言运行: 必须运行在服务器中,在小皮环境的服务器中运行
小皮环境中的php解析: web端请求了访问php文件,nginx服务器,接受到请求,加载对应的php模块来解析php语法的到结果
也就是在本地的小皮环境中,我们在浏览器中输入 localhost 会访问到WWW目录中的文件内容
php语法
php文件: 后缀是.php的文件,在此文件中可以书写php语法代码
php代码书写位置: `<?php php代码 ?>` 要求以 <?php
开头,要求以 ?>
结尾
<?php
# php 的代码写在这里
?>
注意: php语法中每一行代码的最后必须加上分号; 否则报错
php变量定义
通过$定义变量 $名字
使用变量直接 $名字
变量名的命名规则规范和js中的变量面一样
<?php
# 下面就是一个定义了一个变量,并且赋值为 100
# 变量名就是 $num
$num = 100;
$boo = true;
# 下面是一个字符串
$str = "你好 php";
?>
- php中的注释
+ 单行注释: // #
+ 多行注释: /* */
+ 变量名的命名规则规范和js中的变量面一样
- php中的注释
+ 单行注释: // #
+ 多行注释: /* */
- php中的数值和js中基本一致
- php中的字符串和js中也基本一致
+ 在php中双引号的字符串里面的变量可以解析
- 在php中字符串拼接,通过点进行拼接
$str = 'hello ';
$str2 = 'world';
$str3 = $str . $str2;
echo $str3;
# 得到的就是 hello world
+ 但是在js中通过+号进行字符串拼接
- php中的分支结构和js中基本一致
<?php
$boo = true;
if ($boo) {
echo '你好,欢迎观临!';
} else {
echo '您还没有登陆';
}
?>
- php中的循环结构和js中基本一致
<?php
$num = 5;
for ($i = 0; $i < $num; $i++) {
echo 'hello php';
}
?>
- php中的数组分为两种
+ 一种是索引数组: 键名都是索引
- `$arr = ['a','b','c']`
- (和js中的数组很像)
+ 一种是关联数组: 键名都是字符串
- `$arr = ['a'=>'a1','b'=>'b2','c'=>'c3']`
- (和js中的对象很像)
<?php
# 创建一个数组
$arr = array(1, 2, 3);
print_r($arr);
# Array ( [0] => 1 [1] => 2 [2] => 3 )
# 这个就类似于我们 js 中的数组,按照索引来的
# 创建一个关联数组
$arr2 = array('name' => 'Jack', 'age' => 18, 'gender' => '男')
print_r($arr2)
# Array ( [name] => Jack [age] => 18 [gender] => 男 )
# 这个就类似于我们 js 中的 对象,键值对的形式
?>
- php中的输出方法(都是在页面中输出)
- 1. echo 内容; 只能正确输出字符串,数值类型,在浏览器中可以识别字符串中html标签
html标签
- 2. print_r(内容); 可以输出任何数据类型,而且会输出数据类型是什么
- 3. var_dump(内容) 可以输出任何数据类型,而且会输出数据类型及数据大小
- 4. die(内容) 可以字符串和数值输出内容,并结束后续代码的执行
- php中的JSON格式数据转换
1. json_encode(数组或对象) 可以将数组或对象转为json格式的数据
json转换----json_decode(json格式数据) 将json数据转为对象
json转换----json_decode(json格式数据,true) 将json数据转为数组
- php导入文件
+ include(文件路径)
数据库服务
- 数据库服务 提供了数据库 数据的增删改查服务
- 数据库分类
+ 关系型数据库
- 多列数据之间是有关联的,可以进行联表操作,和事物操作
- 典型的关系型数据库 MySQL 数据库
+ 非关系型数据库
- 不能进行联表等操作
- 典型的非关系型数据库 mongoDB 数据库
- MySQL数据库
- 一个数据路服务可以有多个 数据库
- 一个数据库,由多个数据表组成
- 一个数据表,有表头(字段),多条数据组成
+ 字段: 用于约束这一列数据类型的
+ 常见字段类型:
- init 数值整型
- varchar 字符串
- datetime 时间日期
- 一个数据表中,一般都会且只能有一个 主键
+ 主键作用: 主要为了查找的速度快
- 操作数据库的工具:
+ navicate
+ phpmyadmin
php操作数据库
1. 连接数据库服务
语法: mysqli_connect(多个参数)
参数1: 要连接的数据服务 地址
本地数据库服务的地址是127.0.0.1或localhost
参数2: 连接的数据服务 用户名
本地数据库服务的默认用户名: root
参数3: 连接的数据服务 密码
本地数据库服务的默认密码: root
参数4: 连接上数据库服务后要操作的 数据库名称
返回值: 如果连接失败 则返回布尔值 false
如果连接成功,则返回一个连接资源对象
<?php
# 下面就是建立链接,$link 会得到一个链接信息
# $link = mysqli_connect('ip地址', '数据库用户名', '数据库密码');
# 连接并选择数据库
# $link = mysqli_connect('ip地址', '数据库用户名', '数据库密码',"数据库名称");
?>
2. 选择要操作的数据库
选择要操作的数据库,前提是在连接数据库服务的时候,有没有选择要操作的数据
语法: mysqli_select_db(数据库连接对象,要操作的数据库名称)
<?php
# 如果在连接的时候没有选择数据库,可以通过下面的方法选择数据库
# mysqli_select_db($link,'你要操作的库的名称');
?>
3. 创建SQL语句
sql语句是操作数据库的 特有语句
sql语句的书写规范:
- 1. 关键字大写
- 2. 表名和字段名使用 反引号包裹
- 3. 除了数值和日期及布尔的文本内容,都要使用引号包裹
-
我们拿到的结果是一个我们看不懂的处理信息
-
需要使用 mysql_fetch_row || mysql_fetch_assoc 解析一下结果才能看得懂
4. 执行SQL语句
语法: mysqli_query(数据库连接对象,要执行的sql语句)
返回值: 如果是查询的sql语句则返回 一个查询的结果资源对象
如果是修改,删除,新增的sql语句则返回布尔值
<?php
# 下面就是使用 sql 语句对数据库进行操作
# $res = mysqli_query($link,'你要执行的 sql 语句');
?>
这里有一个注意的点:
-
我们拿到的结果是一个我们看不懂的处理信息
-
需要使用 mysql_fetch_row || mysql_fetch_assoc 解析一下结果才能看得懂
5. 视情况而定,是否解析数据
因为查询的sql执行返回的是一个结果资源对象,不能够直接使用该对象,所以需要解析
语法1: mysqli_fetch_assoc(查询的结果对象)
每次执行都只会解析一条数据
返回值: 解析得到的数据是一个关联数组,如果解析不到内容 返回null
语法2:mysqli_fetch_row(查询的结果对象)
每次执行都只会解析一条数据
返回值: 解析得到的数据是一个索引数组,如果解析不到内容 返回null
6. 关闭数据库连接
全部用完以后我们最好是关闭一下数据库链接
语法: mysqli_close(数据库连接对象)
<?php
# mysqli_close($conn);
?>
常用的 sql 语句
1. 新增数据的SQL语句
语法1: INSERT INTO `表名` VALUE(必须和字段一一对应的数据)
语法2: INSERT INTO `表名`(多个字段名) VALUE(必须和前面的字段一一对应的数据)
<?php
# 向表中增加一条数据,再增加的时候主键不能由我们书写,而是 mysql 数据库自己递增
$sql = 'INSERT INTO `student` VALUES(null, "张三", 18, "男", 1913, 100)';
# 插入固定几个键的数据,其他的用默认值
$sql = 'INSERT INTO `student` (`name`, `age`) VALUES("李四", 22)';
?>
2. 删除数据的SQL语句
语法1: DELETE FROM `表名` 删除表中所有的数据
语法2: DELETE FROM `表名` WHERE 条件 删除表中符合条件的数据
条件: = > < >= <= 等单个条件
并列条件: 条件1 AND 条件2
或者条件: 条件1 OR 条件2
3. 修改数据的SQL语句
<?php
# 删除表中 id 为 100 的数据
$sql = 'DELETE FROM `student` WHERE `id`=100';
# 删除表中 name 为 张三 的数据
$sql = 'DELETE FROM `student` WHERE `name`="张三"'
?>
3. 修改数据的SQL语句
语法: UPDATE `表名` SET 修改字段的数据 WHERE 条件
<?php
# 更新一条 id 为 100 的数据中的 name 字段的值和 age 字段的值
$sql = 'UPDATE `student` SET `name`="张三", `age`=10 WHERE `id`=100'
# 更新数据的时候让所有的数据增加一些内容
$sql = 'UPDATE `student` SET `age`=age+1'
?>
4. 查询数据的SQL语句
语法: SELECT * FROM `表名` WHERE 条件
<?php
# 查询 student 这个表里面的所有数据
$sql = 'SELECT * FROM `student`';
# 查询 student 表中的数据里面 gender 为 男 的数据
$sql = 'SELECT * FROM `student` WHERE `gender`="男"';
# 查询 student 表中的数据里面 age 大于 18 的数据
$sql = 'SELECT * FROM `student` WHERE `age`>18';
# 查询 student 表中的数据里面 age 大于 18 且 gender 为 男 的数据
$sql = 'SELECT * FROM `student` WHERE `age`>18 AND `gender`="男"';
# 查询 student 表中的数据里面 age 小于 22 或者 age 大于 28 的数据
$sql = 'SELECT * FROM `student` WHERE `age`<22 OR `age`>28';
# 查询 student 表中的数据里面从 第几条开始 查询多少条
$sql = 'SELECT * FROM `student` LIMIT 0, 10';
# 先按照条件筛选出数据以后再进行分页查询
# 下面是查询表中所有 age>18 且 性别为男的所有数据,查出来以后从第 10 条开始查 10 条
$sql = 'SELECT * FROM `student` WHERE `age`>18 AND `gender`="男" LIMIT 10, 10';
# 查询表的模糊查询
# 下面表示查询表中所有数据里面 name 字段中包含 "三" 字的数据
$sql = 'SELECT * FROM `student` WHERE `name` LIKE "%三%"';
# 查询排序,查询的时候按照某一个字段升序或降序排序
$sql = 'SELECT * FROM `student` ORDER BY `age` ASC';
$sql = 'SELECT * FROM `student` ORDER BY `age` DESC';
?>
HTTP
前后端通信交互的时候,数据应该怎么传递
也叫做 请求-响应 协议
+ 请求: 客户端给服务端发送的一个消息
+ 响应: 服务端给客户端返回的一个消息
- 因为只有请求了才可能会有响应, 所以http也是 短连接
- 因为每一次请求,对于服务端都是新的请求(不能和之前的请求联系起来),所以
http协议也叫做无状态协议
- 每一个请求-响应都必须经历一个完整的http协议
+ 客户端和服务端建立连接
+ 客户端向服务端发送请求
+ 服务端向客户端返回响应
+ 断开连接
发送一个请求
-
建立完链接以后就是发送请求的过程
-
我们的每一个请求都要把我们的所有信息都包含请求
-
每一个请求都会有一个
请求报文
-
在
请求报文
中会包含我们所有的请求信息(也就是我们要和服务端说的话都在里面) -
我们的请求报文中会包含几个东西
请求行
POST /user HTTP/1.1
# POST 请求方式
# /user 请求URL(不包含域名)
# HTTP/1.1 请求协议版本
请求头(请求头都是键值对的形式出现的)
user-agent: Mozilla/5.0 # 产生请求的浏览器信息
accept: application/json # 表示客户端希望接受的数据类型
Content-Type: application/x-www-form-urlencoded # 客户端发送的实体数据格式
Host: 127.0.0.1 # 请求的主机名(IP)
请求空行(请求头和请求主体之间要留一个空白行
user-agent: Mozilla/5.0 # 产生请求的浏览器信息
accept: application/json # 表示客户端希望接受的数据类型
Content-Type: application/x-www-form-urlencoded # 客户端发送的实体数据格式
Host: 127.0.0.1 # 请求的主机名(IP)
请求体(本次请求携带的数据)
# GET 请求是没有请求体数据的
# POST 请求才有请求体数据
完整的请求报文
POST /user HTTP/1.1 # 请求行
Host: www.user.com
Content-Type: application/x-www-form-urlencoded
accept: application/json
User-agent: Mozilla/5.0. # 以上是首部
#(此处必须有一空行) # 空行分割header和请求内容
name=world # 请求体
接受一个响应
客户端的请求发送到服务端以后
服务端进行对应的处理
会给我们返回一个响应
每一个响应都会有一个 响应报文
在 响应报文
中会包含我们所有的响应信息(也就是服务端在接受到客户端请求以后,给我们的回信)
我们的 响应报文
中会包含几个信息
状态行
HTTP/1.1 200 OK
# HTTP/1.1 服务器使用的 HTTP 协议版本
# 200 响应状态码
# OK 对响应状态码的简单解释
响应头
Date: Jan, 14 Aug 2019 12:42:30 GMT # 服务器时间
Server: Apache/2.4.23 (Win32) OpenSSL/1.0.2j PHP/5.4.45 # 服务器类型
Content-Type: text/html # 服务端给客户端的数据类型
Content-Length: 11 # 服务端给客户端的数据长度
响应体
hello world
# 服务端给客户端的响应数据
断开与服务端的链接
我们的断开链接是基于 TCP/IP
协议的 四次挥手
一个 HTTP 请求必须要包含的四个步骤就是
-
建立链接
-
发送请求
-
接受响应
-
断开链接
常见的 HTTP 响应状态码
-
100 ~ 199
-
200 ~ 299
-
300 ~ 399
-
400 ~ 499
-
500 ~ 599
100 ~ 199
-
100: 继续请求,前面的一部分内容服务端已经接受到了,正在等待后续内容
-
101: 请求者已经准备切换协议,服务器页表示同意
200 ~ 299
-
2 开头的都是表示成功,本次请求成功了,只不过不一样的状态码有不一样的含义(语义化)
-
200: 标准请求成功(一般表示服务端提供的是网页)
-
201: 创建成功(一般是注册的时候,表示新用户信息已经添加到数据库)
-
203: 表示服务器已经成功处理了请求,但是返回的信息可能来自另一源
-
204: 服务端已经成功处理了请求,但是没有任何数据返回
300 ~ 399
-
3 开头也是成功的一种,但是一般表示重定向
-
301: 永久重定向
-
302: 临时重定向
-
304: 使用的是缓存的数据
-
305: 使用代理
400 ~ 499
-
4 开头表示客户端出现错误了
-
400: 请求的语法服务端不认识
-
401: 未授权(你要登录的网站需要授权登录)
-
403: 服务器拒绝了你的请求
-
404: 服务器找不到你请求的 URL
-
407: 你的代理没有授权
-
408: 请求超时
-
410: 你请求的数据已经被服务端永久删除
500 ~ 599
-
5 开头的表示服务端出现了错误
-
500: 服务器内部错误
-
503: 服务器当前不可用(过载或者维护)
-
505: 请求的协议服务器不支持
常见的 HTTP 请求方式
不同的请求方式代表的含义不同
-
GET: 一般用于获取一些信息使用(获取列表)
-
POST: 一般用于发送一些数据给服务端(登录)
-
PUT: 一般用于发送一些数据给服务当让其添加新数据(注册)
-
DELETE: 一般用于删除某些数据
-
HEAD: 类似于 GET 的请求,只不过一般没有响应的具体内容,用于获取报文头
-
CONNECT: HTTP/1.1 中预留的方式,一般用于管道链接改变为代理的时候使用
-
PATCH: 是和 PUT 方式类似的一个方式,一般用于更新局部数据
-
OPTIONS: 允许客户端查看服务端性能
GET 请求
参数以 querystring
的形式发送,也就是直接拼接在 请求路径的后面
GET 请求会被浏览器主动缓存
GET 请求根据不同的浏览器对长度是有限制的
对参数的类型有限制,只接受 ASCII 码的格式
POST 请求
-
参数以
request body
的形式发送,也就是放在请求体中 -
POST 请求不会被浏览器主动缓存,除非手动设置
-
POST 请求理论上是没有限制的,除非服务端做了限制
-
对参数类型没有限制,理论上可以传递任意数据类型,只不过要和请求头对应
COOKIE
http协议是一种无状态协议 因为http的每次请求之前都是没有联系的,也就是说,用户请求同一网页两次,这两次的之间没有联系 在进一步解释就是:用户两次请求同一网页两次,两次请求不能保留访问的状态,导致服务器端不能识别这两次的访问是否是同一个人;这就是,http的无状态协议但是很多时候保留访问状态是必要的操作,比如:在首页登录之后,希望在浏览网站其他页面时,能保持这种已登录的状态。所以,出现了一个技术,叫做会话技术。会话中首先有了cookie。
cookie
是浏览器提供的一个存储空间
COOKIE 的存储形式
-
cookie 是以字符串的形式存储,在字符串中以
key=value
的形式出现 -
每一个
key=value
是一条数据 -
多个数据之间以
;
分割
// cookie 的形态
'a=100; b=200; c=300;'
COOKIE 的特点
-
存储大小有限制,一般是 4 KB 左右
-
数量有限制,一般是 50 条左右
-
有时效性,也就是有过期时间,一般是 会话级别(也就是浏览器关闭就过期了)
-
有域名限制,也就是说谁设置的谁才能读取
使用方式
读取 cookie 的内容使用 document.cookie
const cookie = document.cookie
console.log(cookie) // 就能得到当前 cookie 的值
设置 cookie 的内容使用 document.cookie
// 设置一个时效性为会话级别的 cookie
document.cookie = 'a=100'
// 设置一个有过期时间的 cookie
document.cookie = 'b=200;expires=Thu, 18 Dec 2043 12:00:00 GMT";'
// 上面这个 cookie 数据会在 2043 年 12 月 18 日 12 点以后过期,过期后会自动消失
删除 cookie 的内容使用 document.cookie
// 因为 cookie 不能直接删除
// 所以我们只能把某一条 cookie 的过期时间设置成当前时间之前
// 那么浏览器就会自动删除 cookie
document.cookie = 'b=200;expires=Thu, 18 Dec 2018 12:00:00 GMT";'
COOKIE 操作封装
-
因为 js 中没有专门操作 COOKIE 增删改查的方法
-
所以需要我们自己封装一个方法
设置 cookie
/**
* setCookie 用于设置 cookie
* @param {STRING} key 要设置的 cookie 名称
* @param {STRING} value 要设置的 cookie 内容
* @param {NUMBER} expires 过期时间
*/
function setCookie (key, value, expires) {
const time = new Date()
time.setTime(time.getTime() - 1000 * 60 * 60 * 24 * 8 + expires) // 用于设置过期时间
document.cookie = `${key}=${value};expires=${time};`
}
读取 cookie
/**
* getCookie 获取 cookie 中的某一个属性
* @param {STRING} key 你要查询的 cookie 属性
* @return {STRING} 你要查询的那个 cookie 属性的值
*/
function getCookie(key) {
const cookieArr = document.cookie.split('; ')
let value = ''
cookieArr.forEach(item => {
if (item.split('=')[0] === key) {
value = item.split('=')[1]
}
})
return value
}
删除 cookie
/**
* delCookie 删除 cookie 中的某一个属性
* @param {STRING} name 你要删除的某一个 cookie 属性的名称
*/
function delCookie(name) {
setCookie(name, 1, -1)
}
AJAS
概念
一般情况下,前端和后台进行交互都需要页面跳转才能获取到新的数据,然后重新跳转到一个页面,将数据加载到页面上。这对资源的消耗很大,用户体验感也不是很好。
所以js提供了一个不需要刷新页面就能加载后台数据的技术:AJAX
AJAX,全称:async javascript and XML
,是一个异步执行的和后台交互的技术。
AJAX的优势
-
不需要插件支持(一般浏览器且默认开启 JavaScript 即可)
-
用户体验极佳(不刷新页面即可获取可更新的数据)
-
提升 Web 程序的性能(在传递数据方面做到按需放松,不必整体提交)
-
减轻服务器和宽带的负担(将服务器的一些操作转移到客户端)
-
缺点:搜索引擎的支持度不够(因为搜索引擎爬虫 暂时 还不能理解 JS 引起变化数据的内容)
AJAX的书写
1.创建ajax对象
var AJAX=new XMLHttpRequest();
var AJAX = new ActiveXObject("Microsoft.XMLHTTP"); // 兼容ie678
var AJAX = new ActiveXObject("Msxml2.XMLHTTP"); // 更低版本ie
兼容所有浏览器的兼容写法
function createAJAX(){
var AJAX = null;
if(window.XMLHttpRequest){
AJAX=new XMLHttpRequest();
}else if(window.ActiveXObject){
AJAX = new ActiveXObject("Microsoft.XMLHTTP");
}else{
alert("请升级浏览器");
}
return AJAX;
}
2. 配置请求方式和请求地址
AJAX.open(请求方式,请求地址,true);
参数1:请求方式:get/post
参数2:请求地址
参数3:true是异步请求,false是同步请求
4.监听ajax请求状态的改变,绑定事件的处理程序,等待对方接起电话
ajax的onreadystatechange事件
ajax对象有一个readystatechange,触发时机: 当ajax对象状态码变化的时候会会触发
在此事件中判断 **ajax状态码为4且http状态码为200的时候就可以获取到响应体**
AJAX.onreadystatechange = function(){
if (AJAX.readyState == 4 && AJAX.status == 200) {
AJAX.responseText; // 从服务器返回的文本信息
}
};
说明:
onreadystatechange是状态改变事件,只要ajax状态发生改变就会触发这个事件 (状态码)
readyState是ajax对象请求的状态:
- 0:未初始化,对象已经建立,但是还未初始化,就是还未调用send方法
- 1:已经初始化,但是还没有调用send方法
- 2:发送数据,send方法已经调用,但是http头未知
- 3:数据接收中,已经接收部分数据,因为响应以及http头不全,这是通过responseText获取少部分数据会出现错误
- 4:请求完成,数据接收完毕,此时可以通过responseText获取完整的相应数据
responseText是接收服务器响应的字符串数据,如果是xml数据使用responseXML来接收,如果是json数据,其实也是使用字符串形式相应回来的,使用responseText接收以后,使用JSON.parse(str)或str.parseJSON()转为json对象处理
ajax的load事件---不兼容低版本IE
ajax有一个load事件,触发时机: 当ajax对象解析响应体完毕的时候触发
只能在主流浏览器中使用
let xhr = new XMLHttpRequest();
xhr.open(请求方式,请求地址)
xhr.send()
xhr.onload = function(){
console.log(xhr.responseText)
}
3.发送请求
ajax.send();
如果是post请求的话,需要在发送请求前,设置post请求头,模拟表单来传递数据给服务端(否则获取不到send中的参数)
注意: post请求携带参数,在发送请求之前,需要设置请求头 Content-type
- `Content-Type: application/x-www-form-urlencoded`
在ajax对象中有一个方法可以设置请求头
- `ajax对象.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')`
let xhr = new XMLHttpRequest();
xhr.open('post',请求地址)
xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded')
xhr.send(携带的查询字符串的参数)
xhr.onload = function(){
console.log(xhr.responseText)
}
ajax.setRequestHeader('content-type', 'application/x-www-form-urlencoded')
send发送请求的时候要携带请求参数(键1=值1&键2=值2)
ajax.send('name=Jack&age=18&gender=男')
js中手动抛出异常
语法: throw Error(异常信息)
! 注意 js中抛出异常会终端程序的执行
回调函数
概念:将一个函数当做参数传入另一个函数的时候,这个函数就叫回调函数
函数的形参,可以是任意数据类型,也可以是函数
回调函数: 函数a 在调用的时候传递了函数b
在函数a内部,通过形参的方式调用函数b
为什么要使用回调函数?
当我们执行一些异步操作的时候,需要在操作完成以后,做另外的一些事情,但是我们又没有办法预知这个异步操作什么时候结束,此时只能使用回调函数的形式来解决这个问题。
function a(f) {
console.log( 'a函数调用了' )
f();
}
function b() {
console.log( 'b函数调用了' )
};
a(b);
// 此处 b函数就是回调函数
回调函数用处:
1. 在异步代码,执行完毕后,要做的事情,我们使用回调函数
2. 在异步代码封装的时候,会用到回调函数来做异步之后要完成的事情(ajax函数,运动函数)
回调地狱
// 第一次ajax请求
ajax({
url:'./server/01-first.php',
success(res){
// 第二次ajax请求
ajax({
url:'./server/02-second.php',
data:{n:res},// 将第一次请求的响应作为第二次请求的参数
dataType:'json',
success(r){
// console.log( r ) // {n: "666", m: 777}
// 发起第三次ajax请求
ajax({
url:'./server/03-third.php',
data:{a:r.n,b:r.m},
success(res){
console.log( res ) // 1443
}
})
}
})
}
})
回调函数多层嵌套很容易就形成了回调地狱
回调地狱: 代码的可读性比较差,代码维护性性比较差
ES6 提出了解决回调地狱的解决方案 promise语法
Promise
promise是承诺的意思,表示他承诺帮你做这件事情,然后将结果给你。
- 如果你有异步代码要执行,交给promise来帮你执行,承诺给你一个结果
promise 有三个状态
- 1. pending 表示执行中
- 2. fulfilled(resolved) 表示成功
- 3. rejected 表示失败
状态变化:
1. 从执行中变为成功
2. 从执行中变为失败
注意: 不能从失败变为成功,或成功变为失败
Promise是内置的构造函数,可以创建promise对象
1. 语法:
new Promise(function(参数1,参数2){})
+ 参数1: 是一个回调函数(resolve), 调用resolve(),
+ 参数2: 也是一个回调函数(reject), 调用reject(),
则promise的状态会 从执行中变为失败
new Promise(function (resolve, reject) {
// resolve 表示成功的回调
// reject 表示失败的回调
}).then(function (res) {
// 成功的函数
}).catch(function (err) {
// 失败的函数
})
2. promise对象的两个方法
- then方法,参数是一个函数;当promise状态从执行中变为成功的时候,会执行这个函数
+ resolve()调用的时候,状态从执行变为成功,resolve(实参),调用时写的实参,传递给了then方法中函数的形参
- catch方法,参数是一个函数;当promise状态从执行中变为失败的时zheg候,会执行这个函数
+ reject()调用的时候,状态从执行变为失败,reject(实参),调用时写的实参,传递给了catch方法中函数的形参
- catch方法和then方法一般都是连续调用
! 3. 如果then方法函数中 返回的是一个新的promise对象,则可以连续调用then方法
4. 一个promise对象可以连贯调用多个then方法,但是一般只会在最后面调用一个catch方法
- 在Promise中如果代码执行过程中有异常抛出,则不会中断程序的执行,而是将异常信息传递给catch函数的形参,而且状态变为失败
5. promise执行中的异步问题
- new Promise(函数) 函数中的代码执行都是同步的
- then方法中的函数 是异步执行
then方法的连贯调用
1. 前一个then方法如果没有返回新的promise对象,则后续的then方法函数执行不能接收到参数的值
2. 前一个then方法如果有返回新的promise对象,而在新的Promise中的调用了resolve(实参),则后续的then方法执行,可以接收到实参
ASYNC/AWAIT
是ES7新增的语法----回调地狱的最终解决方案
async关键字 写在函数之前,用于修饰函数的关键字
- 作用: 为了可以在函数内使用await关键字
await 关键字 一般在async函数内修饰promise对象
- await 关键字修饰表达式或promise对象,可以让函数内的代码同步执行
+ await 修饰的表达式或promise对象没有执行完毕(状态变化),则不会执行后续的代码
- await 修饰的promise对象中resolve(实参),reject(实参),可以直接使用变量可接受对应的实参结果
- await 只会将函数内部 await之后的代码变为同步执行,但是对于async函数就是异步执行了
console.log( 'start' )
async function fn() {
console.log( 1 )
// let res = await new Promise(resolve=>resolve(666))
// res可以接受 promise执行的结果,就是resolve(实参) 实参
// console.log( res )
let r = await new Promise((resolve)=>{
setTimeout(()=>{
resolve(666)
},2000)
})
console.log( r )
console.log( 2 )
}
fn();
console.log( 'end' )
同源策略
同源策略是由浏览器给的
-
浏览器不允许我们向别人发送请求,只能向自己的服务器发送请求
-
当我们想向别人的服务器发送请求的时候,就会被浏览器阻止了
-
什么是 “别人的服务器” 呢?
-
当 请求协议/域名/端口号 有任意一个不同的时候,那么就算是别人的服务器
-
这个时候就会触发同源策略
-
我们管触发了 同源策略 的请求叫做跨域请求
正向代理
-
有一个客户端需要向一个非同源的服务器B发送请求
-
我们搭建一个和客户端同源的服务器A
-
当客户端发送请求的时候,由服务器A来接受
-
再由服务器A向服务器B发送请求,因为 同源策略是由浏览器给的,服务器之间没有
-
服务器B接受到请求以后,会处理请求,并把响应返回给服务器A
-
再由服务器A把响应给到客户端就可以了
-
我们就可以用这个方式来进行跨域请求了
反向代理
-
反向代理一般是用来做负载均衡的
-
当我请求一个服务器的时候,其实请求的是服务器端设置的代理服务器
-
由代理服务器把若干大量的请求分发给不同的服务器进行处理
-
再由服务器把响应给到代理服务器
-
代理服务器返回给客户端
实现一个跨域请求
跨域请求的实现方案
方案1: 跨域资源共享 cors
在我们要跨域请求的地址中 通过设置响应头,实现资源共享
方案2: 使用代理 nginx服务器代理
在nginx配置文件中设置,注意修改了配置文件需要重启服务器
方案3: 使用后端爬虫
php的后端爬虫方法 来请求需要跨域的地址
此处访问对应的同源后端文件就行
JSONP
在页面中有一些标签可以直接访问 别人服务器的资源(而不触发同源策略)
img标签的 src 属性,可以访问别家的资源,返回的响应当做 图片资源解析
link标签的 href 属性,可以访问别家的资源,返回的响应当做 css样式表解析
iframe标签的src 属性,可以访问别家的资源,返回的响应当做 html解析
script标签的src 属性,可以访问别家的资源,返回的响应当做 js解析
跨域jsonp: 并不是一项新的技术,而是利用了页面中script标签可以跨域请求资源的特点
一般跨域请求返回一个函数调用的字符串,在页面中当做js执行,调用已有的函数
script 标签的本质
-
浏览器给我们提供了一个
script
标签 -
它的本质就是请求一个外部资源,是不受到同源策略的影响的
-
同时
script
标签的src
属性,也是一种请求,也能被服务器接收到 -
并且:
-
script标签的src属性请求回来的东西是一个字符串,浏览器会把这个字符串当作 js 代码来执行
-
-
所以我们就可以利用这个
script
标签的src
属性来进行跨域请求了
使用jsonp实现跨域
1. 创建script标签
2. 给创建的script标签src属性添加跨域请求的地址
注意: 在请求地址后要携带,函数名作为参数
3. 将script追加到页面中
4. 当跨域请求接收到响应后删除该script标签
了解函数
+ 函数的定义阶段
1 在内存中开辟一个存储空间
2 把函数体内的代码当作“字符串”一模一样的放在这个空间中
== 碰到的变量都不进行解析
3 把这个空间地址赋值给函数名(变量名)
+ 函数的调用阶段
- 1 按照函数名(变量名)找到对应的存储空间
- 2 从新开辟一个函数的“执行空间”
- 3 在这个执行空间里面进行形参赋值
- 4 在这个执行空间里面进行预解析
- 5 把函数存储空间里面的代码复制一份一模一样的到执行空间执行一遍
- 6 执行完毕以后,这个开辟出来的“执行空间”就会销毁
函数的调用
+ 每一次函数调用都会开辟一个执行空间
+ 你调用一次,就开辟一个执行空间
+ 执行完毕代码销毁
+ 你再次调用的时候,再次开辟一个执行空间
+ 执行完毕代码销毁
一个不会销毁的函数执行空间
+ 刚才我们说过,一个函数在执行的时候
+ 会开辟一个执行空间
+ 所有的代码在这个执行空间里面执行
+ 执行完毕以后,这个函数执行空间销毁
在一个特殊的情况下
+ 函数的执行空间不会被销毁
什么是特殊的情况
+ 当函数内部返回一个“复杂数据类型”
+ 并且在函数外部有变量接受这个“复杂数据类型”的时候
+ 这个时候函数执行空间不会被销毁
闭包
了解闭包
+ 闭包的形成有三个必要条件(缺一不可)
1 在函数A内部直接或者间接返回一个函数B
2 B函数内部使用A函数的私有变量(私有数据)
3 A函数外部有一个变量接受着函数B
+ 就形成了一个不会销毁的函数空间
闭包空间
+ 我们管这个不会销毁的a函数的执行空间叫做闭包空间
+ 把函数a里面返回的函数b,叫做函数a的闭包函数
+ 官方给的定义有一句话:闭包=>函数内部的函数
函数执行空间不销毁
-
函数的 执行空间 会在函数执行完毕之后销毁
-
但是,一旦函数内部返回了一个 引用数据类型,并且 在函数外部有变量接受 的情况下
-
那么这个函数 执行空间 就不会销毁
function fn() {
const obj = {
name: 'Jack',
age: 18,
gender: '男'
}
return obj
}
const o = fn()
-
函数执行的时候,会生成一个函数 执行空间 (我们暂且叫他
xxff00
) -
代码在
xxff00
空间中执行 -
在
xxff00
这个空间中声名了一个 对象空间(xxff11
) -
在
xxff00
这个执行空间把xxff11
这个对象地址返回了 -
函数外部
0
接受的是一个对象的地址没错-
但是是一个在
xxff00
函数执行空间中的xxff11
对象地址 -
因为
o
变量一直在和这个对象地址关联着,所以xxff00
这个空间一直不会销毁
-
-
等到什么时候,执行一句代码
o = null
-
此时,
o
变量比在关联在xxff00
函数执行空间中的xxff11
对象地址 -
那么,这个时候函数执行空间
xxff00
就销毁了
-
不销毁的空间
-
闭包的第一个条件就是利用了不销毁空间的逻辑
-
只不过不是返回一个 对象数据类型
-
而是返回一个 函数数据类
function fn() {
return function () {}
}
const f = fn()
f
变量接受的就是一个 fn的执行空间 中的 函数
内部函数引用外部函数中的变量
-
涉及到两个函数
-
内部函数要查看或者使用着外部函数的变量
function fn() {
const num = 100
// 这个函数给一个名字,方便写笔记
return function a() {
console.log(num)
}
}
const f = fn()
-
fn()
的时候会生成一个xxff00
的执行空间 -
再
xxff00
这个执行空间内部,定义了一个a
函数的 存储空间xxff11
-
全局 f 变量接受的就是
xxff00
里面的xxff11
-
所以
xxff00
就是不会销毁的空间 -
因为
xxff00
不会销毁,所以,定义再里面的变量 num 也不会销毁 -
将来
f()
的时候,就能访问到 num 变量
闭包的特点
1 延长了变量的生命周期
== 优点: 因为执行空间不会销毁,变量也没有销毁
== 缺点: 因为执行空间不销毁,会一直存在在内存中
2 可以访问函数内部的私有变量
== 优点: 利用闭包可以访问函数内部的私有变量
== 缺点: 执行空间不会销毁,会一直存在在内存中
3 保护私有变量(只要是函数,就有这个特点)
== 优点: 保护私有变量不被外界访问
== 缺点: 如果想访问,必须要利用闭包函数
闭包函数的缺点“致命”
+ 因为有一段内存空间一直不会被销毁
+ 那么就会出现内存占用,如果过多,就会导致内存溢出
+ 那么就么结果就是内存泄漏
闭包的作用
+ 就是当你需要延长变量的声明周期的时候
+ 或者你需要访问一个函数内部的私有变量的时候
+ 你可以用闭包来解决
+ 前提
== 如果有其他的办法,尽量不要使用闭包
== 只有到没有办法的时候,才使用闭包函数
+ 闭包:慎用
间接返回一个函数
+ 直接返回的一个函数: return function(){}
+ 间接返回一个函数,return 一个对象或者数组
== 这个对象或数组里面有一个函数
使用
+ 当你只需要访问一个数据的时候,可以使用直接返回或者间接返回
+ 当你需要访问多个私有变量的时候
== 我们就需要使用间接返回的方式
== 返回一个对象内包含多个闭包函数
function fn(){
const num = 100;
const num2 = 200;
return {
// 对象的名称语义话比数去要强一些
getNum:function(){
console.log(num)
},
getNum2:function(){
console.log(num2)
}
}
}
const res = fn();
// res得到的是一个对象
// 这个对象里面有一个函数是fn的闭包函数
console.log(res)
res.getNum()
res.getNum2()
闭包的应用
+ 防抖和节流
+ 防抖和节流的作用都是防止多次调用函数
+ 假设用户一直触发某个函数,且每次触发函数的时间间隔小于1000ms
== 防抖的情况下,只会调用一次,能有有效的避免资源浪费,节省存储空间
== 节流的情况下每隔一定时间调用函数,相对保守,不易造成资源请求丢失
继承
-
继承是和构造函数相关的一个应用
-
是指,让一个构造函数去继承另一个构造函数的属性和方法
-
所以继承一定出现在 两个构造函数之
+ 出现在两个构造函数之间的关系
+ 当A构造函数的属性和方法能被B构造函数的实例使用了
+ 那么我们就说B继承自A构造函数
构造函数的意义
+ 构造函数的意义就是为了创建一个对象
+ 当函数和new关键字连用的时候,就拥有了创建对象的能力
+ 那么我们管这个函数叫做构造函数
+ 只是为了和普通函数区分一下,更明显的告诉使用的人
+ 需要和new关键字连用,所有我们把首字母大写
new关键字
+ new就是创建对象的过程
+ 也是实例化对象的过程
+ new创造出来的对象叫做构造函数的实例对象
原型继承
+ 继承 - 涉及两个构造函数
== 准备好的父类叫做Person
== 再写一个Student作为子类
== 当完成继承以后,Student继承自Person
== 那么s1可以使用name属性和sayHi方法
+ 原型继承
== 子类.prototype = 父类的实例
+ 原型继承的缺点
1 我想继承下来的属性没有继承在自己身上
== 而是在__proto__里面
== 当我访问的时候,就要去__proto__里面找
2 我继承的目的是为了继承属性和方法
== 我自己要使用name属性和age属性的值需要在两个地方传递
== 自己需要的多个参数在多个位置传递
== 对于代码的书写和维护和阅读都不是很好
借用构造函数继承(借用继承/call继承)
+ 继承 = 两个构造函数之间的关系
== 为了让子类的实例使用父类的属性和方法
继承方案
== 在子类的构造函数体内,借用父类的构造函数执行一下
== 并且强行改变父类的函数的this指向子类的实例
语法:
可以使用call方法在调用的时候改变本地的this指向
函数名.call(本次的this指向,实参1,实参2,...)
+ 借用构造函数继承的特点
== 优点
1 继承的属性写在了自己的身上
== 就不用去__proto__上找了
2 自己需要的两个属性的值,在一个构造函数传递
== 不像原型继承需要在两个地方传递
== 缺点
1 只能继承父类的属性
== 不能继承父类原型上的方法
== 写在构造函数体内的都能继承下来
组合继承
+ 继承 = 子类的实例可以使用父类的属性和方法
+ 组合 = 原型继承 + 借用构造函数继承
== 利用构造函数继承,把属性继承在自己身上
== 利用原型继承把父类的prototype上的方法继承下来
+ 缺点
1 子类的原型上面本来的方法就没有了
+ 必须要继承以后再添加
function Student() {
Person.call(this)
}
Student.prototype = new Person
使用delete可以删除对象本身的属性
语法: delete 对象名.本身的属性名
ES6的继承语法
+ es6有自己ide书写类的语法叫做class
+ es6也有自己的继承关键字 extends
继承
1 书写子类的时候
+ 写成: class 子类 extends 父类
==列: class Student extends Person{}
== 就继承了Person的原型
2 在子类的constructor里面书写
+ super() 来完成继承
+ super() 就相当于在借用父类的构造函数,并改变了this指向
+ super() 要写在子类的constructor的第一行
class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
sayHi(){
console.log('hi')
}
}
// 2 准备一个子类,直接继承父类
class Student extends Person{
constructor(gender,name,age){
super(name,age)
this.gender = gender;
}
play(){
console.log('play game')
}
}
const s1 = new Student('女','韩梅梅',90)
console.log(s1)
面向对象的方法补充
1 删除对象本身的属性
使用delete可以删除对象本身的属性
语法: delete 对象名.本身的属性名
const obj = {a:1,b:2,sayHi:function(){}};
delete obj.a;
delete obj.sayHi;
console.log(obj)
2 判断一个对象是否是某个构造函数的实例
语法: 实例对象 instanceof 构造函数
返回值: 布尔值
class Person {
constructor(name,age){
this.name = name;
this.age = age
}
sayHi(){
console.log('hi')
}
}
const p1 = new Person('lucy',12)
class Student extends Person{
constructor(gender,name,age){
super(name,age)
this.gender = gender
}
}
const s1 = new Student('男','jack',18)
console.log('p1是Person的实例吗?'+(p1 instanceof Person))//true
console.log('s1是Student的实例吗?'+(s1 instanceof Student))//true
console.log('s1是Person的实例吗?'+(s1 instanceof Person))//true
console.log('p1是Student的实例吗?'+(p1 instanceof Student))//flase
3 判断对象上是否包含某个属性名
方法1: in运算符 - 可以判断对象或者对象的原型链上是否有某个属性
语法: '属性名' in 对象
返回值: 布尔值
方法2: hasOwnProperty - 可以判断对象本身是否有某个属性
语法: 对象.hasOwnProperty('属性名')
返回值: 布尔值
4 对象的合并
const defaults = {a:1,b:2,c:3}
const options = {c:4,d:5}
把defaults和options合并成一个新的对象
如果有重复的属性,options优先
方法一:展开运算符
const result = {...defaults,...options}
方法二: Object.assign()
语法: Object.assign(合并到哪个对象上,要合并的对象1,要合并的对象2,...)
如果属性重复的,后面的覆盖前面的
返回值: 合并好的对象
const result = Object.assign({},defaults,options)
console.log(result)
//Objecta: 1b: 2c: 4d: 5[[Prototype]]: Object
5 Object.create()
作用: 创建一个新对象,使用现有的对象来作为新创建对象的原型
语法:
Object.create(原型对象)
创建空对象,并指定该空对象的原型,然后返回
const newObj = Object.create({sayHi:function(){},a:1,b:2})
console.log(newObj);
Object.create(原型对象,要添加的键值对)
创建空对象,并指定该空对象的原型,给空对象本身添加键值对,然后返回
const newObj2 = Object.create({a:1,b:2,c:3},{
name:{
value:'lucy'
},
age:{
value:12
}
})
console.log(newObj2)
定义对象的属性
现在我希望更新对象的属性值或者获取对象的属性值时候,会有通知
+ Object.defineProperty()
作用: 订阅对象的属性值的变化、获取
语法:
Object.defineProperty(对象,'属性名',{
get:function(){
// 这个函数会在有代码获取对象的属性值的时候执行
// get函数的返回值就是你获取到的属性值
},
set:function(val){
// val就是你设置属性值的时候 = 右边的值
// 这个函数会在有代码给对象的属性赋值的时候执行
}
})
了解设计模式
+ 什么是设计模式?
== 针对"特定问题"给出的简洁而优化的解决方案
+ 一个设计模式A
== 只能解决A类问题
== 针对B类型的问题,设计模式A解决不了
+ 同一个问题,在不同的位置,是不一定能用同一种方案解决
+ 设计模式: 只在特定的情况下,特定的时期,针对特定问题的问题使用
市面上常用的设计模式
+ 分为三大类
+ 创建型模式: 单例模式
+ 结构型模式: 组合模式
+ 行为型模式: 观察者模式 / 发布订阅模式
单例模式
+ 单:单一,一个
+ 例:实例(构造函数的实例对象)
+ 让一个构造函数一辈子只能有一个实例对象
+ 就可以用单例模式
单例模式的场景
+ 我的网站有很多的弹出提示框
+ 只不过不同的提示框,里面的文字或者样式不同
+ 弹出层alert()比较丑,用户体验不好
+ 我就使用我自己写的div盒子,当作弹出层
+ 在自己写的过程中,一个网站不可能只弹出一次
+ 不能每次弹出就创建一个div
+ 每次弹出的都是之前创建的div,只是文字改变了
info.onclick = function(){
new Box('警告')
}
error.onclick = function(){
new Box('错误')
}
success.onclick = function(){
new Box('成功')
}
class Box{
constructor(text){
this.init(text);
}
init(text){
this.div = document.createElement('div');
this.div.className = 'box';
this.div.innerHTML = text+"<button>x</button>";
this.div.querySelector('button').onclick = ()=>{
this.hide();
}
document.body.appendChild(this.div)
}
hide(){
this.div.style.display = 'none';
}
}
单例模式 - 核心代码
真实的构造函数
function Person(){
this.name = 'Jack'
}
// 全局
let instance = null;
// 让用的人以为是构造函数
function Singleton(){
if(instance===null){
instance = new Person();
}
return instance;
}
Singleton第一次调用的时候
+ if条件判断,判断instance===null
+ 现在自己作用域里面没有instance,去到父级作用域访问
+ 也就是拿到了全局作用域的instance
+ 现在全局的instance是一个null,if条件为true
+ 给instance赋值一个Person的实例(实例001)
+ 现在全局的instance的值就是实例001
+ return instance就是return 实例001
+ s1得到的就是实例001
let s1 = new Singleton()
Singleton第二次调用的时候
+ if条件判断,判断instance===null
+ 现在自己作用域里面没有instance,去到父级作用域访问
+ 也就是拿到了全局作用域的instance
+ 现在全局的instance是实例001,if条件为false
+ return instance就是return 实例001
+ s2得到的就是实例001
问题:
1 真正的构造函数暴露在外面,别人可能会去new真正的构造函数
2 假的构造函数的名字应该和真的构造函数的名字一样
解决
1 把真实的构造函数和全局变量保护起来
2 假的构造的名字要交Person
const Person = (function(){
// instance用于保存Abc的实例对象,是唯一的
let instance = null;
// Abc是真实的构造函数
function Abc(){
this.name = 'Jack'
}
return function(){
if(instance===null){
instance = new Abc();
}
return instance;
}
})()
const p1 = new Person();
const p2 = new Person();
console.log(p1==p2)//true
组合模式
列:+ 米家
== 你家里有空调,窗帘,热水器,灯...
== 当你会到家的时候,你一打开门
== 家里所有东西都启动了,灯会亮起来,热水器自动加热,窗帘拉起来,空调开启了
+ 组合模式
== 把若干启动方式一样的对象放在一起
== 准备一个总开关,总开关一启动,那么这些对象都启动了
+ 实现组合模式
== 天猫精灵里面有一个数组放的是所有能控制的对象.
== 天猫精灵需要一个方法,向数组里面添加内容
== 天猫精灵需要一个方法,能把数组里面的对象都启动了
+ 应用场景
+ 轮播图
== 基础版本的轮播图依赖定时器左右移动
== 一旦我切换以后,dom不动,定时器在动,等你切换回来的时候就出问题了
== 当我离开当前页面的时候,应该关闭定时器
== 等我再次回来的时候,开启定时器
== 一个页面只有一个轮播图,那没有问题
== 一旦一个页面多个轮播图的时候,我们就可以整一个组合模式
== 做一个总开关
== 离开页面的时候,所有的轮播图停止定时器
== 再做一个总开关
== 回到页面的时候,所有轮播图启动
// 空调构造函数用于创建空调
class KongTiao{
constructor(){
this.name = "空调"
}
init(){
console.log('空调开了')
}
}
// 热水器的构造函数用于创建热水器
class ReShuiQi{
constructor(){
this.name = "热水器"
}
init(){
console.log('热水器开了')
}
}
// 节能灯的构造函数用于创建节能灯
class Deng{
constructor(){
this.name = "节能灯"
}
init(){
console.log('节能灯开了')
}
}
// 天猫精灵的构造函数用于创建天猫精灵
class Tmall {
constructor(){
// 天猫精灵里面有一个数组放的是所有能控制的对象
this.arr = [];
}
// 天猫精灵需要一个方法,向数组里面添加内容
add(dianqi){
this.arr.push(dianqi)
}
// 天猫精灵需要一个方法,能把数组里面的对象都启动了
start(){
this.arr.forEach(dianqi=>dianqi.init())
}
}
// 买天猫精灵
const t1 = new Tmall();
// 买空调,买热水器,买灯,给天猫精灵管理
t1.add(new KongTiao()) //空调开了
t1.add(new ReShuiQi()) //热水器开了
t1.add(new Deng()) //节能灯开了
t1.start()
观察者模式
+ 又称 发布/订阅模式
+ 这个名字主要是通过实现思路的不同取的不同的名字
观察者角度
+ 就像我们现在的班主任一样
== 班主任,就业老师,讲师都有一个共同的能力叫做叫家长
== 他们就是暗中观察我们的人,一旦我们做的事情和写代码无关,就会触发技能
== 他们就是观察者,当被观察者一旦出现变化,立马触发他们的技能
被观察者角度
+ 就是一个学生
== 你的状态就应该是好好写代码
== 一旦你的状态改变成玩手机
== 立马就会通知正在观察你的人,观察你的人就会触发技能
+ 目的
== 让观察者 看着 被观察者, 只要数据改变了
== 就让观察者做一些事情