一、事件
1、事件流
(1)冒泡(IE)
(2)捕获(Netscape)
(3)DOM2事件流
2、事件处理
(1)html事件处理
第一种:直接在html中定义事件处理程序及包含的动作。
<input type="button" value="click me!" onclick="alert('clicked!')"/>
第二种:html中定义事件处理程序,执行的动作则调用其他地方定义的脚本。
<input type="button" value="click me!" onclick="showMessage()"/>
<script>
function showMessage(){
alert("clicked!");
}
</script>
(2)DOM0事件处理
将一个函数赋值给一个事件处理程序属性
<input id="myBtn" type="button" value="click me!"/>
<script>
var myBtn=document.getElementById("myBtn");
myBtn.onclick=function(){
alert("clicked!");
}
// 删除
myBtn.onclick = null
</script>
(3)DOM2事件处理
定义了2个方法:addEventListener()和removeEventListener()
3个参数:事件名、事件处理函数、布尔值(true表示捕获阶段调用事件处理函数,false表示冒泡阶段)
举个栗子:
<script type="text/javascript">
window.onload=function(){
var outer = document.getElementById("outer");
var inner = document.getElementById('inner');
var oBox=document.getElementById('box');
oBox.addEventListener("click",function(){
alert('1');
},true)
oBox.addEventListener("click",function(){
alert('4');
},false)
outer.addEventListener("click", function(){
alert("2");
}, true);
inner.addEventListener("click",function(){
alert('3.2')
},false)
inner.addEventListener("click", function(){
alert("3.1");
}, true);
}
</script>
<body>
<div id="box">
<div id="outer">
<div id="inner"></div>
</div>
</div>
</body>
点击inner,弹出顺序?
??????
??????
??????
1----2----3.2-----3.1---4
原因:box上的捕获事件最先执行,然后是outer上的捕获事件,然后是inner上先注册的事件,然后是inner上后注册的事件,最后是box上的冒泡事件
addEventListener()添加的时间只能通过removeEventListener()删除,addEventListener()添加的匿名函数无法移除
相关博客戳这里
(4)IE事件处理(IE浏览器和opera浏览器支持)
attachEvent()和detachEvent()
接受2个参数:事件名称、事件处理函数
ie8及以前只支持冒泡,所以事件处理程序都在冒泡阶段
注意:
1、IE中用attachEvent和DOM0的区别,前者的this指向全局作用域,后者指向其所属元素的作用域
2、attachEvent可以为同一元素添加多个事件处理程序,是以相反的顺序被触发
(5)跨浏览器事件处理
先判断是否有DOM2级,再判断IE,最后是DOM0
var eventUtil = {
//添加句柄
addHandler:function(element,type,handler){
if (element.addEventListener){ //DOM2级
element.addEventListener(type,handler,false);
}else if (element.attachEvent){ //IE
element.attachEvent('on' + type, handler);
}else { //DOM0级
element['on' + type] = handler;
}
},
//删除句柄
removeHandler:function(element,type,handler){
if (element.removeEventListener){
element.removeEventListener(type,handler,false);
}else if (element.detachEvent){
element.detachEvent('on' + type, handler);
}else {
element['on' + type] = null;
}
},
getEvent:function(event) {
//IE678中用window.event
return event? event: window.event;
},
getType:function(event) {
return event.type;
},
getElement:function(event){
return event.target || event.srcElement;
},
preventDefault :function(event){
if(event.preventDefault) {
event.preventDefault();
}else { // 兼容IE
event.returnValue = false;
}
},
stopPropagation: function(event){
if(event.stopPropagation){
event.stopPropagation();
}else { // 兼容IE
event.cancelBubble = true;
}
}
}
3、事件对象
(1)DOM事件对象
参考p355
(2)IE中事件对象
type,srcElement,returnValue,cancelBubble
(3)跨浏览器的事件对象
见(2.3 跨浏览器事件处理)
4、事件类型(p362自己看)
(1)UI事件
(2)焦点事件
(3)鼠标、滚轮事件
(4)文本、键盘事件
(5)合成时间
(6)变动事件
(7)h5事件
(8)设备事件
5、内存和性能
(1)事件委托
原理:红宝书原话---利用冒泡,指定一个事件处理程序,管理某一类型所有事件
个人理解:利用冒泡,将子元素触发的事件写到父元素上,再通过事件对象(如event.target等)判断该做何处理。
优点:减少与dom交互,提高性能
参考博客戳这里
(2)移除事件
移除带有事件的元素时,事件仍然与按钮保持引用关系。
在某些浏览器中,事件滞留在内存中,来回切换页面时,内存中滞留的对象数增加,因为内存没被释放。
二、JSON
JSON是一种数据格式,不支持变量、函数、对象实例
JSON中,字符串必须使用双引号,对象属性加双引号,末尾不能有分号
1、语法
(1)简单值:字符串、数、布尔、null(不支持undefined)
(2)对象
(3)数组
1、解析与序列化
(1)JSON.stringify()
a)过滤
第二个参数是数组
JSON.stringify(book, ["title", "author"])
// 过滤出book的title和author属性,返回的字符串只包含这俩属性以及他们对应的值
第二个参数是函数
var book = {
"title": "Jack",
"authors":[
"Nicholas"
],
"edition":3,
"year":2018
}
var jsonText = JSON.stringify(book, function(key, value){
switch(key) {
case "authors":
return value.join(',')
case "year":
return 5000
default:
return value
})
b)字符串缩进
stringify有第三个参数
自己看P567
c)toJSON
var book = {
"title": "Jack",
"authors":[
"Nicholas"
],
"edition":3,
"year":2018
toJSON:function(){
\\\\\\\
}
}
stringify顺序:
1、如果有toJSON且能取得有效值,先调用它;
2、如果stringify有第二个参数,用过滤器;
3、对2中的值进行序列化
4、如果有第三个参数,格式化
(2)JSON.parse()
还原函数
自己看p569
三、Ajax和Comet
Ajax:无需刷新页面从服务器获取数据的一种方法
1、XMLHttpRequest
(1)用法
a)启动一个请求以备发送
xhr.open("方法","URL","是否异步")
b)发送
send("要作为请求主体发送的数据,无数据则传入null")
c)响应数据(自动填充xhr)
responseText: 返回的文本
responseXML:如果响应内容类型是xml/text或application/xml,则是响应数据的XML DOM,否则为null
status:响应的http状态(200~300及304为成功)
statusText:HTTP状态的说明
异步:
在判断status之前,判断xhr.readyState值,4表示完成,接收到全部响应数据;
xhr.abort()在收到响应之前可以取消异步请求
(2)HTTP头
头部信息:Accept,Accept-Charset,Accept-Encoding,Accept-Language,Connection,Cookie,Host, Referer,User-Agent
xhr.setRequestHeader()可以设置自定义的头部信息,在open之后,send之前
(3)GET
注意查询字符串的格式
参考P576
(4)POST
P577自己看
2、跨域
同源策略:同域、同端口、同协议
CORS(Cross-Origin Resourse Sharing)
(1)额外Origin头
(2)服务器在Access-Control-Allow-Origin中返回相同的源信息
(1)IE对CORS的实现:引入XDR
P582自己看
(2)其他浏览器对CORS的实现:通过XMLHttpRequest实现
open中传入绝对URL
(3)跨浏览器的CORS
P585自己看
3、其他跨域技术
图像、JSONP、Comet
前两者利用img、script标签可以跨域,只能实现get
Comet是一种服务器向页面推送数据的技术(几乎实时)
两种实现comet方法:
1、长轮询:页面发送一个到服务器的请求,连接一直保持,直到有数据可以发送
2、http流:浏览器发送一个请求,连接一直保持,服务器周期性发送数据
p586自己看
相关博客戳跨域大全
3、Web Socket(新协议)
传递少量数据,比http开销小,适合移动应用,可跨域,只能发送纯文本数据
p592自己看
4、安全
CSRF(Cross-Site Request Forgery),跨站点请求伪造
(1)SSL连接
(2)每次请求都要附带经过相应算法计算的验证码
四、高级技巧
1、高级函数
(1)类型检测
var is Array = value instanceof Array
// 要返回true,value必须是一个数组,且必须与Array构造函数在同个全局作用域
利用[object NativeConstructorName]
function isArray(value){
return Object.prototype.toString.call(value) == "[object Array]"
}
还可以用来检测Function、RegExp、原生JSON
(2)作用域安全的构造函数
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
}
var person = new Person("Nicholas", 29, "Software Engineer");
var person = Person("Nicholas", 29, "Software Engineer");
alert(window.name); //"Nicholas"
alert(window.age); //29
alert(window.job);//"Software Engineer"
使用new操作符时,this指向新对象的实例;没使用new时,this会映射到全局window上
改写:
function Person(name, age, job){
if (this instanceof Person){
this.name = name;
this.age = age;
this.job = job;
} else {
return new Person(name, age, job);
}
}
注:使用构造函数窃取模式的继承且不用原型链,这个继承可能无效
function Polygon(sides){
if (this instanceof Polygon) {
this.sides = sides;
this.getArea = function(){
return 0; };
} else {
return new Polygon(sides);
}
}
function Rectangle(width, height){
Polygon.call(this, 2);
this.width = width;
this.height = height;
this.getArea = function(){
return this.width * this.height;
};
}
var rect = new Rectangle(5, 10);
alert(rect.sides); //undefined
原因:new一个Rectangle时, Polygon.call(this, 2)中的this不是Polygon的实例,所以会在Polygon中new一个Polygon
解决办法:加上原型链
function Polygon(sides){
if (this instanceof Polygon) {
this.sides = sides;
this.getArea = function(){
return 0; };
} else {
return new Polygon(sides);
}
}
function Rectangle(width, height){
Polygon.call(this, 2);
this.width = width;
this.height = height;
this.getArea = function(){
return this.width * this.height;
};
}
Rectangle.prototype = new Polygon();
var rect = new Rectangle(5, 10);
alert(rect.sides); //undefined
原因:一个Rectangle实例也是一个Polygon实例,所以new 一个Rectangle中,会把Rectangle中的this传给Polygon
(3)惰性载入函数
目的:减少判断语句的执行次数
function createXHR(){
if (typeof XMLHttpRequest != "undefined"){
return new XMLHttpRequest();
} else if (typeof ActiveXObject != "undefined"){
if (typeof arguments.callee.activeXString != "string"){
var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"],
i,len;
for (i=0,len=versions.length; i < len; i++){
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex){
//跳过
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
} else {
throw new Error("No XHR object available.");
}
}
方法(1)每个判断分支都定义一个函数覆盖原有函数,这样第一次调用会进行判断,之后调用就直接使用某个分支里的函数
function createXHR(){
if (typeof XMLHttpRequest != "undefined"){
createXHR = function(){
return new XMLHttpRequest();
};
} else if (typeof ActiveXObject != "undefined"){
createXHR = function(){
if (typeof arguments.callee.activeXString != "string"){
var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"],
for (i=0,len=versions.length; i < len; i++){
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex){
//skip }
}
}
return new ActiveXObject(arguments.callee.activeXString);
};
} else {
createXHR = function(){
throw new Error("No XHR object available.");
};
}
return createXHR();
}
方法(2)利用var声明自执行的匿名函数,每个判断分支中返回正确的函数定义,第一次加载时要判断,调用时不用。
var createXHR = (function(){
if (typeof XMLHttpRequest != "undefined"){
return funtion(){
return new XMLHttpRequest();
}
} else if (typeof ActiveXObject != "undefined"){
return function(){
if (typeof arguments.callee.activeXString != "string"){
var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"],
i, len;
for (i=0,len=versions.length; i < len; i++){
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex){
//skip
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
}
}else {
return function(){
throw new Error("No XHR object available.")
}
}
})()
(4)函数绑定:修改函数的执行上下文(this)
call(context,a,b,c...),call立即执行
bind(context,a,b,c...),bind返回的是函数,调用才执行
apply(context,[a,b,c....]),apply立即执行
栗子:
Math.min(23, 34, 24, 12, 35, 36, 14, 25)
arr=[23, 34, 24, 12, 35, 36, 14, 25]
var min = Math.min.apply(null, arr);
(5)函数currying
方法:使用一个闭包返回一个函数,返回的函数还需要设置参数
function curry(fn){
var args = Array.prototype.slice.call(arguments, 1); // 第一个参数是要柯里化的函数,所以从第二个参数开始slice,args包含了来自外部的参数
return function(){
var innerArgs = Array.prototype.slice.call(arguments); //存放来自内部函数的参数
var finalArgs = args.concat(innerArgs);
return fn.apply(null, finalArgs);// 将拼接好的参数传给fn
};
}
//可以分开传参
function add(num1, num2){
return num1 + num2;
}
var curriedAdd = curry(add, 5);
alert(curriedAdd(3)); //8
//也可以一次性传参
function add(num1, num2){
return num1 + num2;
}
var curriedAdd = curry(add, 5, 12);
alert(curriedAdd()); //17
2、防篡改对象
p606自己看
3、高级定时器
js是单线程,浏览器负责排序,指派某段代码在某个时间点运行的优先级。
js有一个主执行进程,还有一个代码队列。
给队列添加代码不代表它立即执行,只代表它会尽快执行(进程空闲时);定时器意味着隔了某段时间后代码被添加到队列,不带表某段时间后立即执行。
重复定时器和数组分块自己看p610
函数节流:某些代码不可以在没有间断的情况下连续重复执行
function throttle(method, context) {
clearTimeout(method.tId);
method.tId= setTimeout(function(){
method.call(context);
}, 100);
}
参考博客:函数节流与防抖
4、自定义事件:有助于解耦
发布-订阅模式模式
p616自己看
参考博客这里
五、最佳实践
1、可维护性
(1)(可理解、直观、可适应、可扩展、可调试)
函数和方法---注释
大段代码---注释
复杂算法---注释
变量名:避免拼音,英文应该直观清楚,合乎逻辑;变量名为名词,函数应以动词开始。
(2)松散耦合
p659自己看
(3)编程实践
1、尊重对象所有权:不是自己的对象不要乱改
2、避免全局变量
3、避免与null比较
4、使用常量:抽取出来单独定义
2、性能
(1)避免全局查找
(2)算法复杂度
*使用变量和数据比访问对象上的属性更有效率,经常用的对象属性要存在局部变量中。
优化循环:
1、--代替++,更高效(原因)
2、简化终止条件
3、简化循环体
4、合理使用后测试循环如do-while
*针对大数据集,展开循环更快:duff装置
p670自己看
其他:js原生方法比自己写快、switch比复杂if-else快、位运算符较快
(3)最小语句数(能少写就少写)
1、少用几个var,合在一起写
2、++或==尽量合并语句,不要单独写
3、使用字面量创建和初始化数组
(4)优化dom交互
1、最小化现场更新:减少对dom的更新次数,使用文档片段(fragment)构建dom结构,全部创建好后使用appendChild()添加
2、innerHTML:对于大的dom,用innerHTML效率更高。
使用innerHTML设置某个值时,后台会创建一个HTML解析器,使用内部的DOM调用来创建DOM结构,而不是基于js的DOM改写,所以快很多。
3、使用事件代理:利用冒泡,将目标的事情在祖先节点上处理,这样减少dom间的交互,增强性能。
4、HTMLCollection: 带有方法的 HTML 元素的集合
以下情况会返回HTMLCollection对象
(1)调用getElementsByTagName()
(2)获取元素的childNodes属性
(3)获取元素的attributes属性
(4)访问了特殊的集合,如document.forms, document.images等
优化方法:将循环中的数组长度计算移至for循环的初始化部分
栗子1:
var images = document.getElementsByTagName("img"),
i, len;
// 要这样
for(i=0,len=images.length; i<len; i++){
//处理arr[i]
}
//而不是这样
for(i=0; i<images.length; i++){
}
栗子2:
// 比如这样,循环内可以减少访问images的HTMLCollection
var images = document.getElementsByTagName("img"),
image,
i, len;
for(i=0,len=images.length; i<len; i++){
image = images[i]
}