25.ajax : 前端向后端异步的取数据而无需刷新页面的技术
1 公司中的整体工作流程
1、项目开发的流程 每个职位该做的工作:
产品经理:提需求的 与客户沟通 画出原型图给程序员使用
UI设计师:美化 替换UI框架:antd element-ui easyui bootstrap
前端:所有看得见的
后端:看不见的(服务器)
测试:找bug
运维:项目上线 服务器维护
2、用户使用的整个流程
前端->后端:1前端需要向后端发送请求
后端<->数据库:2连接数据库、3进行数据库查询操作(拿着一些数据去查询)、4获取到对应的数据返回给后端;
后端->前端:5数据查询完成之后返回给前端、6前端拿到数据之后对数据进行渲染,显示在页面中,让用户可见
3、我们要解决的是前端和后端之间的问题,即前端怎么给后端发送请求?前端怎么接受后端发回来的数据?
前端通过IP找到后端的服务器给后端发请求 域名与IP地址做绑定 绑定之后通过DNS域名解析系统将域名解析成对应的IP,从而找到对应的IP
前端通过IP地址向后端发请求,同样后端通过IP地址向数据库发请求
4、之后项目上线时需要购买服务器空间和域名:服务器中的一块存储空间
云服务 端口号进行区分 阿里云 有些国外的非正规渠道不用钱
5、项目上线
端口号(服务器的不同位置):80(网页)、443、3306(数据库)
协议:http-80 https-443
本机地址:127.0.0.1 或域名:localhost
Xampp的使用:
打开后,启动Apache,点击start下面不报错则表示本机服务器启动成功。点击stop则停止服务器。
将数据上线:找到xampp安装的根目录--在安装路径的htdocs中,只需要将要上线的html复制到该htodcs中即可或者再根目录中再创建一个文件夹放进去,同时路径也有了变化。(这只是把本机当作服务器的小打小闹,真正工作中不是这样的)
用户在访问时:127.0.0.1/33-ajax.html 或者用域名localhost/33-ajax.html 由于将html文件放在了根目录下了直接写文件名路径即可,注意路径的正确性
在ajax出现之前只要想要刷新评论加载更多就得要在地址栏重新输入重新访问整体发请求,所有的数据都会得到刷新,而ajax出现之后就可以只刷新加载局部想要刷新的地方,其余的地方继续进行。
在地址栏输入回车是整体发请求,而我们想要局部发请求,直到ajax的出现解决了这一问题。
6、前端向后端发请求需要具备的条件:请求地址、请求类型、请求参数、后端返回内容的解释。这些内容写在接口文档中。
2 ajax使用
ajax是异步的,因为发送请求是很慢的。即使将ajax的使用写在前面也不一定会先执行。
1、使⽤ajax 的步骤
注意:前端和后端接口的一致性(请求参数的参数名由后端决定的一定要一致)、请求方式是get还是post由后端决定。
- //1.创建ajax核⼼对象:XMLHttpRequest() ajax说到底也是内置对象
- var xmlhttp=new XMLHttpRequest();
- //2.创建请求: ajax核心对象的方法open("请求方式get/post","请求 的服务器地址(域名或ip地址)绝对路径和相对路径", 是否异步(ajax是异步的true)) 这里使用的是相对路径
- //xmlhttp.open("post","my.php",true)
- xmlhttp.open("get","my.php?user=982395099&psd=123456",true)
- //如果请求⽅式为post,需要主动设置请求头,如果是get则不需要加
- //xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded")
- //3.发送请求参数,请求方式是由后端决定的
- post请求:ajax核心对象的send方法,key=value形式的字符串 多个参数之间以&连接
- //xmlhttp.send("username=xuchao&psd=123456");
- get请求 :请求参数不能写在send⾥⾯,要写在请求地址后⾯,以?连接,并且将 send方法的参数设为null
- xmlhttp.send(null);
- //4.接收响应 ajax核心对象的onreadystatechange事件。当ajax核心对象的请求的状态readyState是4响应完毕且服务端返回的状态码是ok200时才能正确的取出数据做操作 获取来自服务器端的文本数据时通过ajax核心对象的responseText属性
- //onreadystatechange事件 当请求的状态发生变化时触发
- // readyState (请求的状态) 0(尚未初始化,请求根本还没发) 1(正在发送请求)2(请求发送完毕) 3(正在响应,后端正在查询)4(响应完毕,请求结束后端已经发送回来了)
- // status(服务端返回的状态码) 404(找不到页面,地址写错了/地址更换了) 500(服务器错误崩溃,后端的锅) 200(ok) 301(请求缓存) 304(请求转发)
- xmlhttp.onreadystatechange=function(){
-
-
-
- if(xmlhttp.readyState==4&&xmlhttp.status==200){
- //responseText属性 可以服务器端返回的⽂本格式的数据,只能接收字符串
- var data=xmlhttp.responseText
- }
-
-
- }
2.get和post的区别:
(1)请求参数的位置不同,get的参数在请求地址后面,post的参数在send方法中;
(2)post需要设置请求头,而get不需要;
(3)get请求的请求参数会显示在地址栏里,而post请求看不见;
(4)get请求更快更简单 ,
(5)get有参数数量的限制,而post不会 ;
(6)post请求更安全更稳定 (表单提交推荐用post)(包含未知的⽤户输⼊的时候) 没有参数数量限制
- json:是一种数据格式,前后端之间传的是json,JavaScript Object Notation JavaScript对象表示法 独立于语言的 前后端都可以使用 语法与JS中创建对象的代码相同
在JSON中属性名必须要加引号,因为在前端中虽然不严格,但在其他的语言中是很严格的。
接收请求时的responseText属性只是能得到文本类型的数据,通过JSON得到的是对象形式的字符串,是无法直接访问其中的属性的。
JSON比XML(也是一种数据格式)更小、更快、更易解析。
JSON.parse(JSON格式的字符串):把JSON格式的字符串转成对象/JSON。
JSON.stringify(str):将对象/JSON转换成JSON格式的字符串。-----常用于实现深复制
可以通过JSON的格式转换实现深拷贝: :使用JSON.stringify把对象转换为字符串类型的,基本数据类型的复制本身就是深复制,再使用JSON.parse将复制之后的字符串转换为对象类型,这时再修改该对象,之前的对象的内容是不会发生变化的。
。
var obj = { hobby: "打篮球", car: "bmw" }//对象
var a = JSON.stringify(obj)//将对象转换为字符出串
var b = a//字符串复制,基本数据类型的复制本身就是深复制
b = JSON.parse(b)//将字符串转换为对象
console.log(b)
b.car = "benz"//更改b的car,obj的car不受影响
console.log(obj, b)
3 ajax的使用过程案例
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div>我是一个上线的项目</div>
<!-- 我的爱好由用户输入 -->
我喜欢<input type="text" id="like">
<h1 id="txt"></h1>
<script>
var txt = document.getElementById("txt")
var like = document.getElementById("like")
//ajax请求不应该一进来就发送,应该等用户填写完毕之后再发送
like.onblur = function () {
//1 创建ajax核心对象
var xmlhttp = new XMLHttpRequest()
//2 创建请求
//get请求
//参数的内容由用户输入拿到
//xmlhttp.open("get", "my.php?hobby=" + like.value, true)
//post请求
xmlhttp.open("post", "my.php", true)//请求地址为相对路径
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
//3 发送请求
//get请求
// xmlhttp.send(null)
//post请求
xmlhttp.send("hobby=" + like.value)
//4 接收请求
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var data = xmlhttp.responseText//拿到后端传过来的数据
//下面是做的DOM操作
txt.innerText = data
// console.log(data)
}
}
}
</script>
</body>
</html>
后端接口
<?php
// php语言对;非常严谨,每一句话后面都要有;
// 接收前端发过来的参数 要求接收的参数与前端的参数名要一致
// php变量声明没有关键字 要用$打头
hobby=_REQUEST["hobby"];
if($hobby=="打篮球"){
echo "我爱打篮球";
}else if($hobby=="踢足球"){
echo "我爱踢足球";
}else if($hobby=="听音乐"){
echo "我爱听音乐";
}else{
echo "我啥也不喜欢";
}
?>
4 ajax实战1---省市区联动
需要动态获取,不能直接在下拉框中写死,因为不是所有的省份都有,而且这些数据可能每天都会发生变化,动态的从后端的数据库中拿到再渲染到前端。
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<!-- 下拉框 他的value就是所选中项里面的内容-->
<!-- 这里的省市不能直接写死,应该从后端数据库动态获取 -->
<select id="province">
<option>--请选择省份--</option>
</select>
<select id="city">
<option>--请选择城市--</option>
</select>
<select id="district">
<option>--请选择区--</option>
</select>
<script>
/*
注意1:城市的ajax不是一进去就触发的,是当选择了省份值发生了变化时才会项后端发出请求
注意2:在每次切换省份时需要先将上次的城市清除掉,否则每次切换之后之前的内容也还在,只增未删
注意3:在循环删除上次的城市时:这里没有用document来获取元素对象,可以选择父级来找到合适的使用区域
注意4:在循环删除上次的城市时:要注意数组长度变化带来的问题,采用回退法或倒序循环来实现
注意5:在删除上次的城市时:不用循环删除上次的城市,直接将html的内容替换即可,更简单
*/
var province = document.getElementById("province")
var city = document.getElementById("city")
var district = document.getElementById("district")
// 1页面一进去就显示出省份,加载省份 省份的ajax
var xmlhttp = new XMLHttpRequest()
xmlhttp.open("get", "province.php", true)
xmlhttp.send(null)
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var provinces = xmlhttp.responseText
//下面做DOM操作 增
// 字符串切割 字符串->数组
var provincesArr = provinces.split(",")//注意这里的切割符要与后端的大小写一致
for (var i = 0; i < provincesArr.length; i++) {
//DOM操作 增加多个元素对象 创建option标签,
var option = document.createElement("option")
province.appendChild(option)
option.innerHTML = provincesArr[i]
}
}
}
// 2加载城市 城市的ajax
// 注意1:和省份的ajax一进来就触发是不一样的,城市是当选择了不同的省份时,省份的值发生变化时才会触发
province.onchange = function () {
//先把上次的城市删除
/*//法一:循环删除
//注意2:每次切换省份时需要先将上次的城市清除掉
var options = city.getElementsByTagName("option")
//注意3:这里没有用document来获取元素对象,可以选择合适的使用区域,
for (var i = 1; i < options.length; i++) {
city.removeChild(options[i])
//注意4:数组长度变化,回退法
i--
}*/
//法二:更好用,直接html内容替换
//注意5:不用循环删除上次的城市,直接将html的内容替换即可,更简单
city.innerHTML = "<option>--请选择城市--</option>"
district.innerHTML = "<option>--请选择区--</option>"
xmlhttp.open("post", "city.php", true)
//需要传输数据了用post,设置请求头
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
//发送请求参数,不能自己想怎么写就怎么写,要看后端怎么定义的变量名
xmlhttp.send("provinceValue=" + province.value)
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var cities = xmlhttp.responseText
var citiesArr = cities.split(",")//注意这里的切割符要与后端的大小写一致
for (var i = 0; i < citiesArr.length; i++) {
//DOM操作 增加多个元素对象 创建option标签,
var option = document.createElement("option")
city.appendChild(option)
option.innerHTML = citiesArr[i]
}
}
}
}
//3加载区
city.onchange = function () {
district.innerHTML = "<option>--请选择区--</option>"
xmlhttp.open("post", "district.php", true)
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
xmlhttp.send("cityValue=" + city.value)
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var districts = xmlhttp.responseText
var districtsArr = districts.split(",")
for (var i = 0; i < districtsArr.length; i++) {
var option = document.createElement("option")
district.appendChild(option)
option.innerHTML = districtsArr[i]
}
}
}
}
</script>
</body>
</html>
后端:
province.php:
<?php
echo "山东省,浙江省,江苏省,广东省";
?>
city.php:
<?php
// 接收前端传过来的省份值,从后端拿到省份后进行选择,再将选择的省份值传给后端,发出市的请求
province=_REQUEST["provinceValue"];
if($province=="山东省"){
echo "青岛市,烟台市,济南市,临沂市";
}else if($province=="浙江省"){
echo "杭州市,温州市,宁波市,台州市";
}else if($province=="江苏省"){
echo "南京市,苏州市,无锡市,扬州市";
}else if($province=="广东省"){
echo "东菀市,广州市,佛山市,韶关市";
}
?>
district.php:
<?php
$city = $_REQUEST["cityValue"];
if ($city == "青岛市") {
echo "市南区,市北区,黄岛区,崂山区";
} else if ($city == "烟台市") {
echo "芝罘区,福山区,牟平区,莱山区";
}else if ($city == "济南市") {
echo "市中区,历下区,天桥区,槐荫区";
} else if ($city == "临沂市") {
echo "兰山区,罗庄区,河东区,国家级经济技术开发区";
} else if ($city == "杭州市") {
echo "上城区,拱墅区,西湖区,滨江区";
} else if ($city == "温州市") {
echo "鹿城区,龙湾区,瓯海区,洞头区";
} else if ($city == "宁波市") {
echo "海曙区,江北区,北仑区,镇海区";
} else if ($city == "台州市") {
echo "黄岩区,路桥区,椒江区";
} else if ($city == "南京市") {
echo "玄武区,秦淮区,鼓楼区,建邺区";
} else if ($city == "苏州市") {
echo "吴中区,相城区,姑苏区,吴江区";
} else if ($city == "无锡市") {
echo "梁溪区,锡山区,惠山区,滨湖区";
} else if ($city == "扬州市") {
echo "广陵区,邗江区,江都区";
} else if ($city == "东菀市") {
echo "莞城区,东城区,南城区,万江区";
} else if ($city == "广州市") {
echo "越秀区,海珠区,荔湾区,天河区";
} else if ($city == "佛山市") {
echo "禅城区,顺德区,南海区,三水区";
} else if ($city == "韶关市") {
echo "浈江区,武江区,曲江区";
}
?>
5 ajax实战2,真实案例---天气预报查询
前端向后端发请求需要具备的条件:请求地址、请求类型、请求参数、后端返回内容的解释。
接口文档:写明了请求所需要知道数据(以上)的一个文档
接口:后端已经写好了的现成的应用,前端只需要发送请求就可以直接获取数据
你接口对(接)完了吗?即前后端是否跑通了
API:通常是指一些现成的封装好的方法,直接调用该方法就能实现某个功能。如防抖、节流函数
接口从某种意义上来说也是一种API。
高效查看错误,不用再每次console.log():检查--网络--点开出现的信息后,可以快速的查看传送的参数和服务器返回的内容。
标头:请求头的一些信息;
载荷:请求传送的参数,如果传送的参数出现问题的话,是不会正确显示的;
预览:相当于console.log
使用真实的接口实现天气查询:使用高德地图提供的现成的后端接口
百度搜索:高德地图开放平台--开发支持--Web服务--Web服务API--开发指南--API文档--天气查询 专门给软件开发者提供的接口
该案例也产生了跨域问题,只不过在后端的服务器上给我们使用者设置了白名单,让我们能够访问一些接口。
城市当前的天气状况:
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<input type="text" id="weather">
<h1 id="txt"></h1>
<script>
/* 这个为查看该城市当前的天气状况:
高德开放平台申请key的步骤:登录--控制台--应用管理--我的应用--创建新应用--应用名称随便写weather1--应用类型写天气--点击新建--点击添加--key名称随便写weather1--服务平台选择Web服务--提交,从而拿到key
注意1:在从API文档中复制请求地址时,不要复制?后面的请求参数
注意2:确保输入城市的天气信息是有内容的。
注意3:info.count是字符串类型的不是number类型的,需要类型转换:使用Number(info.count)将字符串类型的数字转换为数字类型的数字
*/
//当input表单失去焦点时触发发请求
var weather = document.getElementById("weather")
var txt = document.getElementById("txt")
weather.onblur = function () {
var xmlhttp = new XMLHttpRequest()
//请求地址,在API文档中的url
//注意1:复制时不要复制?后面的内容
xmlhttp.open("get", "https://restapi.amap.com/v3/weather/weatherInfo?key=e17d487bab9ea0753d34762321979db4\&city=" + weather.value, true)
xmlhttp.send(null)
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var info = JSON.parse(xmlhttp.responseText)
//注意3:info.count是字符串类型的数字,不是数字类型的数字,需要进行类型转换
if (Number(info.count) > 0) {
txt.innerHTML = info.lives[0].city + "的天气是" + info.lives[0].weather + ",温度是" + info.lives[0].temperature + "度"
} else {
txt.innerHTML = "所输入城市暂无天气信息"
}
//注意2:输入洛杉矶出现类型错误,属性与对象没有匹配上,发现info.count=0且info.lives是空没有数据,要考虑到这种情况
}
}
}
</script>
</body>
</html>
该城市的预测天气状况:注意对象与数组的多层嵌套的取值问题
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<!-- 作业1:ajax实战天气查询--天气预测
北京市2023-06-08,天气是多云,温度是34度
北京市2023-06-09,天气是多云,温度是34度
北京市2023-06-10,天气是多云,温度是31度
北京市2023-06-11,天气是多云,温度是31度
-->
<input type="text" id="weather">
<div id="txt"></div>
<script>
var weather = document.getElementById("weather")
var txt = document.getElementById("txt")
weather.onblur = function () {
txt.innerHTML = ""//清除上次的天气信息
var xmlhttp = new XMLHttpRequest()
xmlhttp.open("get", "https://restapi.amap.com/v3/weather/weatherInfo?key=e17d487bab9ea0753d34762321979db4\&city=" + weather.value + "&extensions=all", true)
xmlhttp.send(null)
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var info = JSON.parse(xmlhttp.responseText)
if (Number(info.count) > 0) {
var n = info.forecasts[0]
for (var i = 0; i < n.casts.length; i++) {
txt.innerHTML += n.city + n.casts[i].date + ",天气是" + n.casts[i].dayweather + ",温度是" + n.casts[i].daytemp + "度<br>"
}
} else {
txt.innerHTML = "所输入城市暂无天气信息"
}
}
}
}
</script>
</body>
</html>
6 跨域问题
没有必要把前端代码也放在服务器上,之前我们把前端和后端代码都放在了服务器上了,我们想要在本地打开前端代码,将创建请求的服务器地址写成绝对路径,这时就会产生跨域问题。
跨域:浏览器有一个安全策略(同源策略)
同源策略:同协议 同域名 同端口号。 前端和后端之间如果有一个不一样就无法正确发出请求
跨域问题的解决方案:
1、jsonp
2、请求代理:在本机上生成一个本地服务器,浏览器将请求发送给本地服务器,再由本地服务器将请求转发给目标服务器。
浏览器和服务器之间存在跨域问题,而服务器与服务器之间是没有跨域问题的。
3、后端设置白名单:后端可以设置让某些域名的允许访问,其他的都不能,但风险很大。
26.JS高级部分
1.关于改变this指向
call apply bind 用来改变this指向的,即我想调用别人的方法 。
使用方法:
(1)别人的方法.call(我/对象,参数1,参数2,...) 后面的对象去调用前面的方法,这时的this就指向了后面的对象;
(2) 别人的方法.apply(我/对象,[参数1,参数2,...])
apply与call的区别是apply的参数是数组的形式,在方法中的形参会自动对应数组中的每一项;而call是一堆参数。仅仅是传参数时的区别,在接收参数时一样。
(3) 别人的方法.bind(我/对象,参数1,参数2,...) bind返回的是一个函数 方便复用,什么时候用什么时候再调用即可。
call、apply、bind的区别:
-
- 传参形式的区别:call的参数是参数序列的形式;apply的参数是数组的形式;bind的参数是参数序列的形式。
- call和apply在使用别人的方法的同时帮助其调用了函数,而bind只是更改this的指向,不会帮助主动调用,bind返回的是一个函数,将其保存起来,等到需要的时候可以多次调用。
<script>
var obj = {
name: "李磊",
age: 18,
intr: function () {
console.log("我叫" + this.name + "小爷今年" + this.age + "岁")
}
}
var obj2 = {
name: "韩梅梅",
age: 17,
intr: function () {
console.log("我叫" + this.name + "老娘今年" + this.age + "岁")
},
dance: function (m, n) {
console.log(this.name + "会跳舞" + m, n)
}
}
obj.intr()//谁调用this就指向谁,这时的this指向obj
obj2.intr()//这时的this指向obj2
//输出结果:我叫李磊小爷今年18岁 我叫韩梅梅老娘今年17岁
obj.intr.call(obj2)//这时的this指向发生了变化,指向了obj2。输出结果:
//输出结果:我叫韩梅梅小爷今年17岁
obj2.dance.call(obj, 100, 200)
//输出结果:李磊会跳舞100 200
obj2.dance.apply(obj, [100, 200])
//输出结果:李磊会跳舞100 200
var fn = obj.intr.bind(obj2)
fn()//bind返回的是一个函数,需要手动调用
//输出结果:我叫韩梅梅小爷今年17岁
fn()//可多次调用
fn()
</script>
2.关于new关键字的实现原理源码(在工作中是不需要自己手动创建new):使用到了改变this指向
arguments关键字代表的是函数的所有参数,用在函数内部,即使函数没有定义形参,只要调用函数时传送了实参,通过arguments关键字都可以拿到所有的实参,是类数组结构,有角标,可以通过角标访问。
new关键字的原理:从源码的角度来说new关键字做了哪⼏件事?
1.创建了⼀个空对象并取出构造函数和参数(使用arguments类数组+更改this指向让arguments使用数组的截取子数组slice方法(数组的方法都是定义在原型上的)) var obj={}
2.修改对象obj的隐式原型的指向为创造其的构造函数的原型,并更改构造函数的原型对象的constructor属性指回构造函数(可以看一下原型链的图)
3.改变this指向 把构造函数中的this从指向全局的window改为调用函数的对象obj,通过apply,因为取出来的参数的格式是数组类型的,适合用apply
4给该对象加属性 this.name="⼩明" this.age=18 这一步在_new函数中不用再写了,因为在构造函数中已经写了
5.返回⼀个全新的对象 return obj
调用:_new(构造函数,函数1,函数2,...)
<script>
//自己封装一个new函数
/* function _new(构造函数, 参数1, 参数2) {
}*/
//构造函数
function Student(name, age) {
this.name = name;
this.age = age
}
function Child(like, hate, money) {
this.like = like
this.hate = hate
this.money = money
}
//在之前调用构造函数,我们是这样的:
//new Studnet("李磊",18)
//因为我们没办法完全与系统中的new函数完全一样,因此需要我们自定义一个new函数,去调用构造函数
// _new(Studnet, "李磊", 18)
/*
注意1、每个构造函数的参数个数不确定,无法直接确定_new函数中的形参,参数不固定
解决方法:使用arguments关键字直接获取所有的实参,可以不定义形参。但是它只是类数组,有角标可通过角标访问,但不能使用数组的方法
注意2、obj的原型指向没改
ll和hmm的原型应该指向其构造函数的原型,而现在直接指向了Object,需要更改他的指向
*/
function _new() {
//1 创建一个空对象
var obj = {}
//取出实参,分别拿到构造函数和参数
//注意1:取出参数,如果arguments是一个数组的话可以直接使用截取子数组slice,可它不是数组。我没有但是我还想用,使用call改变this指向,让我去调用别人的方法
// 截取子数组,数组的方法都是定义在原型上的
var params = Array.prototype.slice.call(arguments, 1)
//取出构造函数
var fn = arguments[0]
//注意2:更改对象的隐式原型的指向为其构造函数的原型,⼀个对象的隐式原型默认指向创建该对象的构造函数/父级的原型
obj.proto = fn.prototype
//注意2:构造函数的原型的constructor属性指回构造函数
obj.proto.constructor = fn
//2 改变this指向,把构造函数中的this指向obj,由于我们的参数取出的正好是一个数组,因此选用apply
fn.apply(obj, params)
//3 在构造函数中加了属性和值了
//4 返回一个全新的对象
return obj
}
var ll = _new(Student, "李雷", 18)
var hmm = _new(Student, "韩梅梅", 17)
console.log(ll, hmm)//输出的是一个对象
var c = _new(Child, "哭", "大人", 180)
console.log(c)//输出对象
</script>
3.instanceof 用法及源码(区分具体的对象) (在工作中是不需要自己手动创建的)
1 instanceof用法
typeof :用于检测数据类型,但是无法区分对象,数组和对象都是对象,使用Array.isArray()来区分数组和对象;
Array.isArray():区分数组和对象。
instanceof :用于区分具体是哪种对象,也能用于区分数组和对象。
用法:子对象 instanceof 父对象 (即子对象是不是父对象的一个实例) 父对象可以是父亲的父亲,顺着原型链往上找即可,不一定非得是直接子集。
(无法区分对象到底是哪种具体的对象,到底是是日期对象还是正则对象还是Student对象还是ajax对象还是Math对象。---使用instanceof来区分。)
function Student(name, age) {
this.name = name;
this.age = age
}
var ll = new Student("李雷", 18)
console.log(ll instanceof Student)//输出结果:true
console.log(ll instanceof Object)//输出结果:true
console.log(Student instanceof Function)//输出结果:true
console.log(ll instanceof Function)//输出结果:false
/* 李雷不是函数,只是普通的对象,不是Function的子级,如果他是函数的话就是Function的子集。
李雷->Student->Object
Student->Function->Object
*/
var arr = [1, 2, 3]
console.log(arr instanceof Array)//输出结果:true
console.log(ll instanceof Array)//输出结果:false
var now = new Date()
console.log(now instanceof Date)//true
var reg = /^\d{9}$/
console.log(reg instanceof RegExp)//true
2 instanceof源码(自己手动封装一个instanceof)
核心思想:判断子级child是否为其父级parent的实例对象就是判断子级的隐式原型是否指向父级的原型对象或者child的父级的隐式原型指向parent的原型对象,而null是没有隐式原型的,因此要求子级不是null.
<script>
/*自己手动封装一个instanceof方法,之后传入子对象和父对象即可判断
原理:child的隐式原型是不是指向parent的原型对象或parent是child父级的父级(需要循环比较)
任何对象都有隐式原型,只有null没有隐式原型
*/
function _instanceof(child, parent) {
var left = child.proto;//自己的隐式原型
var right = parent.prototype;//父级的原型对象
while (true) {
//子级是null,则它没有隐式原型
if (left === null) {
return false;
}
//如果left不等于right,此时并不能确定返回false,因为parent可以是child父级的父级,,需要进行循环比较
if (left === right) {
return true
}
//把子级变成父级,检测父级与父级的父级
left = left.proto
}
}
console.log(_instanceof(new Date(), Date))//true
console.log(_instanceof([], Date))//false
</script>
4.递归
递归:函数自己调用自己,注意避免死循环。
为了避免死循环递归必须要满足:①循环变量/参数向着退出的趋势变化;②满足退出的条件要有return。
<script>
function fn(n) {
console.log(++n)
if (n === 10) { return }
fn(n)
}
fn(0)//输出结果:输出1-10
</script>
递归案例1:求和
// 求1...n的和
function sum(n) {
if (n === 1) { return 1 }
return sum(n - 1) + n
}
console.log(sum(5))
// sum = 5+4+3+2+1
递归案例2:阶乘
//阶乘n!
function fn(n) {
if (n === 1) { return 1 }
if (n === 0) { return 1 }
return fn(n - 1) * n
}
console.log(fn(5))
// 5!=5*4*3*2*1
5.深复制
解决深复制的方法:
1、JSON.stringfy(字符串类型的对象) JSON.parse(对象类型的字符串)
优点:又简单又好用,且bug少。
弊端:处理不了日期 。(日期对象转换为字符串后想再转回对象实现不了)
但是在实际开发中,基本不会出现属性值为日期的对象,使用该方法足够了。
var obj = {
name: "黎明",
age: 18,
like: { movie: "阿凡达", music: "发如雪", a: [1, 2, 3], date: new Date() }
}
// JSON.stringfy() JSON.parse()
//先将obj对象转换为字符串类型的 基本数据类型的都是深复制
var obj2 = JSON.stringify(obj)
//再将字符串类型的obj2转换为对象类型
obj2 = JSON.parse(obj2)
obj.like.music = "天堂"
console.log(obj)
console.log(obj2)
2、ES6的扩展运算符:((该方法只能实现一维的深复制,当是二维数组时就实现不了了)
let arr = [1, 2, 3]
let arr2 = [...arr]
arr[1] = "hrllo"
console.log(arr, arr2)
// arr:[1, 'hrllo', 3] arr2:[1, 2, 3]
3、手写递归实现深复制:对对象进行循环复制属性和属性值,如果属性值是引用数据类型的数据,则对该数据再进行循环复制(使用递归实现)。
优点:没有bug,可完全实现深复制。
for in 循环时不仅会循环自身的属性,还会循环继承的属性,而我们在深复制时只想要复制对象自己的属性。解决方法:需要使用 对象.hasOwnProperty("属性名")方法判断是否是对象自己的属性。
var obj = {
name: "黎明",
age: 18,
like: { movie: "阿凡达", music: "发如雪", a: [1, 2, 3], date: new Date() }}
// 递归实现深复制
//与2的类似。如果属性值是引用属性类型的数据,我们对这些属性值再次多次的进行循环赋值即可,使用递归。
//传入一个对象进来之后,给这个对象深复制一份
/*
bug1:for in 循环时不仅会循环自身的属性,还会循环继承的属性,而我们在深复制时只想要复制对象自己的属性。
解决方法:需要使用 对象.hasOwnPerperty("属性名")方法判断是否是对象自己的属性。
bug2:日期无法实现深复制,日期变成了空对象。因为日期属性没有键值对,无法进行for in 循环
解决方法:不进行for in 循环了 直接进行复制赋值 把它当成原始类型
在实际开发中一般遇不到对象属性值是日期对象的,日期一般不会以对象类型的传过来,一般是
①字符串:"2023-6-11" 直接new Date("2023-6-11")
②时间戳:new Date().getTime()
*/
function deepClone(obj) {
//设置初始值。由于我们不知道要初始化的是一个对象还是一个数组,我们不能直接初始化的时候初始成空对象,要根据具体的数组和对象进行初始化
var obj2 = obj instanceof Array ? [] : {};
//如果传进来的是基本数据类型的直接就是深复制,不用做这些操作,当传进来的是引用数据类型的时才进行下面的操作
//如果使用typedef(obj)==="object"的话,传进来的null也是object类型的,要求obj得存在
if (obj && typeof (obj) === "object") {
for (var key in obj) {
/*bug1:由于for in循环不仅循环对象自己本身的属性,还会循环该对象继承属性
解决方法:使用Object的prototype原型的属性hasOwnProperty()方法判断该属性是否为对象自己本身的属性
*/
//当该属性为该对象自己的属性时才进行复制操作,如果是从父级继承来的则不需要管
if (obj.hasOwnProperty(key)) {
//若当前循环的属性值还是对象类型的,则需要对属性值再进行递归深复制;如果不是对象而是基本数据类型的,则直接进行复制操作
//bug2:由于日期对象比较特殊,它没有键值对,只是一个字符串,走不了循环,可以把它当成原始数据类型直接走复制复制的操作,但是在面试中不要提到日期对象,否则会很业余
if (obj[key] && typeof (obj[key]) === "object" && (!(obj[key] instanceof Date))) {
obj2[key] = deepClone(obj[key])
} else {
obj2[key] = obj[key]
}
}
}
}
return obj2
}
var b = deepClone(obj)
b.like.music = "天堂"
b.like.a[0] = 9
console.log(obj)
console.log(b)
function Student(name, age) {
this.name = name
this.age = age
}
Student.prototype.car = "bmw"
var ll = new Student("李雷", 18)
var hmm = deepClone(ll)
console.log(ll)
console.log(hmm)
27.BOM(browser object model)浏览器对象模型
1、screen对象:是属于window的内容,返回屏幕的一些信息。
screen.availWidth、screen.availHeight屏幕的可用宽/高度。
console.log(screen, screen.availWidth, screen.availHeight)
2、location对象:获取当前页面的地址,并将浏览器重定向到新的页面。页面跳转。
location.hostname 返回web主机的域名
location.pathname 返回当前页面的路径和文件名
location.port 返回主机的端口号80/443
location.protocol 返回所使用的web协议http:/https:
location.href 返回当前页面的地址URL
也可以进行赋值实现页面的跳转操作。location.href="http:www.baidu.com"
<button id="btn">登录</button>
<script>
var btn = document.getElementById("btn")
btn.onclick = function () {
//先做登录成功的验证,之后
location.href = "http://www.baidu.com"
}
</script>
3、history对象:浏览器的历史。
histroy.back()方法:返回上一个页面,类似于浏览器的左箭头后退。
history.forward()方法:转到下一个页面,类似于浏览器的右箭头前进。
history.go(前进页面的个数)方法:go()里面的参数表示跳转页面的个数,1表示前进一个页面,-1表示后退一个页面,0表示刷新页面。
<body>
<button id="back">返回</button>
<script>
var back = document.getElementById("back")
back.onclick = function () {
history.back()
// history.go(0)
}
</script>
</body>
4、navigator对象:浏览器的信息