js红宝书20-25
JSON
stringfy(JSON[,过滤器参数array,缩进和空白符数目num]),制定过滤器后输出的字符串将只包含对应属性
toJSON()给JSON对象定义该方法重写stringfy
序列化顺序
- 若存在toJSON方法且能通过它取得有效值,则调用该方法,否则返回对象本身
- 如果提供了过滤器,则应用过滤器过滤,传入值是第一步返回的
- 序列化
- 如果提供了缩进,再重新序列化
parse(JSON字符串,reviver(还原函数))
AJAX
IE7之前需要使用MSXML库中的一个ActiveX对象来实现的
IE7+,FireFox,Chrome,Safari通过new XMLHttpRequest()
新建XHR对象
方法:
-
open(请求类型,url, 是否异步)
URL是相对当前页面的,调用该方法启动一个请求以备用
-
send()发送请求,post时可以放入请求主体内容
-
abort()收到响应前取消请求
-
setRequestHeader(),在open后send前
-
getRequestHeader()
收到响应之后,响应数据自动填充XHR属性
- responseText:响应文本
- responseXML:content-type为“text/xml”,"appliction/XML"时保存XML DOM文档
- status:响应状态
- statusText:响应状态说明
异步请求下通过readstatechange事件检测readyState属性(open之前指定)
0:未初始化,尚未调用open
1:启动,已open未send
2:发送,已send未收到响应
3:接收,已接收部分
4:完成,全部接收完毕
XHR2.0
new FormData()可以直接序列化表单数据
timeout超时设定和ontimeout事件
进度事件
-
loadstart
-
progress
event.target是一个XHR对象,包含:
lengthComputable:进度信息是否可用
position:已接收字节数
totalSize:content-length指示的预期字节数
明显平常浏览器下载的时候我们就可以用这个信息展示进度条
-
error
-
abort
-
load
-
loadend
HTTP header和响应状态码
-
Accept 浏览器能够处理的内容类型
-
Accept-Charset 能显示的字符集
-
Accept-Encoding 压缩编码
-
Accept-Language 浏览器当前设置的语言
-
Connection 连接类型
-
Cookie
-
Host 发出请求的页面所在域
-
Referer 发出请求的页面的URI
-
User-Agent 用户代理字符串
跨域资源共享
CORS(cross-Origin Resource Sharing)
基本思想:使用自定义的HTTP头部让浏览器和服务器进行沟通,从而决定请求或者响应是成功还是失败
简单请求:
(1) 请求方法是以下三种方法之一:
- HEAD
- GET
- POST
(2)HTTP的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
此时浏览器自己设置下面的Origin字段
请求:Origin头部:页面源信息(协议,域名,端口)
响应:Access-Control-Allow-Origin:回发相同源信息,浏览器对比处理
复杂请求(Preflighted Requests)
此时浏览器先发送一个预检请求Option,包含下列头部
- Origin
- Access-Control-Request-Method 请求自身使用的方法
- Access-Control-Request-Headers(可选)自定义头部信息
服务器通过在响应中发送如下头部与浏览器沟通
- Access-Control-Allow-Origin
- Access-Control-Request-Method 允许的方法
- Access-Control-Request-Headers 允许的头部
- Access-Control-Max-Age 预检请求缓存多长时间
下面是从百度的控制台向ke.qq.com发送复杂请求的例子
var a = new XMLHttpRequest();
a.open("post","https://ke.qq.com",true);
a.setRequestHeader("myCookie","name=heavy");
a.send({});
点击network查看
设置withCredentials设置为True后则可以让请求带凭据(Cookie,HTTP认证及客户端SSL证明)
若服务器接受则返回 Access-Control-Allow-Credentials:true
其他跨域技术
图像PING
只能发送get请求,且只能单行通信
JSONP
- 回调函数
- 数据
这里直接去百度拿一个JSONP请求字符串来做实验
const bd_cb_dict3_1596362060709 = (response) => {
console.log(response);
}
var script = document.createElement("script");
script.src = "https://sp1.baidu.com/5b11fzupBgM18t7jm9iCKT-xh_/sensearch?wd=jsonp&cb=bd_cb_dict3_1596362060709&callback=bd_cb_dict3_1596362060709&_=1596362060216";
document.body.insertBefore(script,document.body.firstChild);
明显我们自己定义了一个bd_cb_dict3_1596362060709这样的函数来给服务器进行调用(服务器可以解析callback参数,然后动态执行代码),最终成功在控制台上打印了响应
引用一段jQuery的用法:https://www.cnblogs.com/dowinning/archive/2012/04/19/json-jsonp-jquery.html
jQuery(document).ready(function(){
$.ajax({
type: "get",
async: false,
url: "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998",
dataType: "jsonp",
jsonp: "callback",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)
jsonpCallback:"flightHandler",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据
success: function(json){
alert('您查询到航班信息:票价: ' + json.price + ' 元,余票: ' + json.tickets + ' 张。');
},
error: function(){
alert('fail');
}
});
});
Comet
更高级的ajax技术,也称“服务器推送”
-
长轮询
传统是浏览器定时向服务器发送请求,看看有没有更新的数据;
长轮询则颠倒了一下,页面发送一个到服务器的请求,然后服务器保持连接打开,直到有数据可以发送。发送完后关闭连接,随即浏览器又发起一个到服务器的请求。
-
流
浏览器向服务器发送请求,服务器周期性向浏览器发送数据
SSE(Server-sent Events)
支持短轮询,长轮询,流,服务器响应的是MIME类型必须是text/event-stream
new EventSource(url);该url必须和创建对象的页面同源
同样有readystate属性:0正在连接;1打开了连接;2关闭
三个事件:open,message(收到新事件时触发),error
若是在响应中加入了id的键值对,则eventSource对象会跟踪上一次触发的事件。如果连接断开,会向服务器发送Last-Event-ID
的特殊请求头。
Web Sockets
js创建一个Web Socket之后会发送HTTP请求,然后连接协议换成ws://,加密连接换成wss://
好处:自定协议,开销小;
new WebSocket(绝对url);
同样readystate:0正在连接1已经连接2正在关闭3已经关闭
在不支持Web Sockets的情况下使用XHR+SSE组合也能实现双工通信
高级技巧
安全的类型检测
Object.prototype.toString.call()方法
惰性载入函数
-
函数在被调用时才处理函数
IF-ELSE分支为函数赋值覆盖原函数,这样后面再运行该函数的时候运行的就是分支的函数了
-
声明函数时就制定适当的函数
使用匿名自执行函数声明函数变量,每个IF-ELSE分支返回对应function
函数绑定
简单的bind函数,创建闭包调用apply为bind的第一个参数也就是fn,绑定执行环境为context,绑定参数为arguments
function bind(fn,context) {
return function(){
return fn.apply(context,arguments);
}
}
Object.preventExtentions:不可扩展对象,不能增加属性或方法
Object.seal:密封对象,即设置[[Configurable]]为false,不能修改属性或方法
Object.freeze: 冻结对象,既是不可扩展又是密封的,且[[Writable]]也为false
函数节流
基本思想:某些代码不可以在没有间断的情况下连续重复执行=》设置定时器,连续触发时消除定时器
举个例子
高频的resize事件:
用户拖动改变浏览器窗口大小时,事件连续触发。此时若设置了节流,则事件回调函数会延迟比如100ms执行。
用户拖动了3s,假设resize无间断连续触发的话,会一直设置清除定时器直到用户停下操作,此时100ms定时器成功工作
柯里化
用于创建已经设置好了一个或者多个参数的函数
好处:帮助我们基于一个被转换函数,通过对参数的拆分实现不同功能的函数
function curry(fn){
var args = Array.prototype.slice.call(arguments,1);//获取fn需要传入的值,即fn后面的参数(这里就是我们设置好了参数)
return function(){
var innerArgs = Array.prototype.slice.call(arguments);//获取柯里化后的函数传入的参数
var finalArgs = args.concat(innerArgs);//最终的参数是两者拼接起来的数组
return fn.apply(null,finalArgs); // 执行函数
}
}
function add(num1, num2){
return num1 + num2;
}
var curriedAdd1 = curry(add,5);
curriedAdd1(2);
=> 7
var curriedAdd2 = curry(add,5,5);
curriedAdd2();
=> 10
看一下在掘金看到的一个currying函数:
来源:https://juejin.im/post/5b58b5c56fb9a04fa560ec4b
// ES6 的实现
function currying(func, args = []) {
let arity = func.length;// 检测func传入的参数
return function (..._args) {// 扩展运算符获取函数的所有参数
_args.unshift(...args);//参数头部插入外部函数参数,相当于上面的concat
if(_args.length < arity) { // 这里判断了合并后的参数数组的长度是否小于func定义的参数数量
return currying.call(null, func, _args);
}
return func(..._args); //参数数量相等的时候,此时相当于所有的参数都已经被用户设置好了,后面再待用传参时都是无效传参了
}
}
function add(num1, num2){
return num1 + num2;
}
var curriedAdd = curry(add);
var curriedAdd1 = curriedAdd(5);
curriedAdd1(2);
=> 7
var curriedAdd2 = curriedAdd1(5);
curriedAdd2();
=> 10
上面例子中传入一个add函数,-args.length此时为0,arity为2,此时返回一个currying(add),即相当于没设置任何参数的add函数
接着curriedAdd1设置一个5参数,_args.length此时为1,arity为2,相当于设置了一个参数为5的add函数
最后curriedAdd2在curriedAdd1基础上设置一个5参数,_args.length此时为2,arity为2,此时参数相等,返回了add函数(一个已经设置好所有参数的函数),调用执行可直接获取结果。
反柯里化
反柯里化的作用则在于扩大函数的适用性,使本来作为特定对象所拥有的功能函数可以被任意对象所使用。
function uncurring(fn) {
return function (...args) {
return fn.call(...args);
}
}
这个函数可以看到它返回了一个它自身call执行的函数,由于这里的call的第一个参数this值没有确定,可以让我们自己传this值。这也就扩大了这个函数他的普适性。比如这种场景下:我们对继承的需求仅仅是希望一个构造函数的实例能够使用另一个构造函数原型上的方法。
还是掘金的例子:
Function.prototype.uncurring = function(){
var self = this;
return function(){
return Function.prototype.call.apply(self,arguments);
}
}
这个函数的Function.prototype.call.apply
比较难理解。
再看下面掘金给出的例子,来过下这个函数:
// 构造函数
function F() {}
F.prototype.sayHi = function () {
return "I'm " + this.name + ", " + this.age + " years old.";
}
// 希望 sayHi 方法被任何对象使用
sayHi = F.prototype.sayHi.uncurring();
sayHi({ name: "Panda", age: 20}); // I'm Panda, 20 years old.
F.prototype.sayHi.uncurring()
执行时,此时uncurring
函数内部的this是指sayHi方法。它返回了一个Function.prototype.call.apply(self,arguments);
。apply将后面的{ name: "Panda", age: 20}
传给call,更改了call的this指向sayHi的作用域。此时变为F.prototype.sayHi.call({ name: "Panda", age: 20})
。call会将这个对象的this指针绑定到sayHi里面去!!!,此时再执行sayHi就是打印这个字面量对象的"I'm " + this.name + ", " + this.age + " years old.";
了!!
核心点跟上面一样还是这个call的第一个参数,是由我们调用时再去传参绑定的。这里只是多了个apply去修改我们call需要去绑定的方法为sayHi而已。
数据存储
Cookie
绑定在特定域名下,约为4KB。
发送的cookie值需要使用encodeURIComponent()进行编码,同理收到的需要使用decodeURIComponent
storage
- clear()
- getItem(name)
- key(index)
- removeItem(name)
- setItem(name,value)
会话存储数据sessionStorage:事务方法可以使用 begin和commit方法
持久保存客户端数据localStorage
动画循环
window.requestAnimationFrame()
告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
注意:若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()
每秒60次,也就是一次间隔1000ms/60约等于17ms
var start = null;
var element = document.getElementById('test');
element.style.position = 'absolute';
function step(timestamp) {
if (!start) start = timestamp;
var progress = timestamp - start;
element.style.left = Math.min(progress / 10, 200) + 'px';
if (progress < 2000) {
window.requestAnimationFrame(step);
}
}
window.requestAnimationFrame(step);
start为第一一次运行时的时间,timeStamp为本次运行的时间,相减确定间隔,当间隔>=2000也就是2s时停止动画。
读取拖放的文件
算是对他的实现是一直比较感兴趣的
H5拖动API+文件API
我们可以直接读取被拖动元素的event.dataTransfer.files
属性获取fileList
对象,遍历这个对象就可以获取文件信息。
如果想读取内容则使用fileReader
对象
构造函数:new FileReader()
方法:
readAsText(file, encoding)
以encoding的编码方式读取File保存在result属性readAsDataURI(file)
读取image等带有MIME属性类型的文件
document.body.innerHTML="<div id='output'>lallalalalalalalalalalalalsz</div>"
const handleEvent = event => {
var info="",
output = document.getElementById("output"),
files, i, len;
event.preventDefault();
if (event.type == "drop"){
files = event.dataTransfer.files;
i = -1;
len = files.length;
while(++i<len)
info += `${files[i].name}(${files[i].type},${files[i].size}bytes)<br>`;
output.innerHTML = info;
}
}
window.addEventListener("dragenter", handleEvent);
window.addEventListener("dragover", handleEvent);
window.addEventListener("drop", handleEvent);
将代码运行在控制台之后,拖动文件放入浏览器页面,可以看到如下信息: