Javascript高级程序设计重读

Javascript高级程序设计重读

基本概念

typeof null // object

特殊值null被认为是一个空的对象引用

if(car != null){
	// 对car进行某些操作
}

如果定义的变量准备在将来用于保存对象,最好将该变量初始化为null

null === undefined // false
null == undefined // true
数据类型 转换为true的值 转换为false的值
Boolean true false
String 任何非空字符串 “”
Number 任何非零数字 0和NaN
Object 任何对象 null
Undefined n/a undefined
NaN == NaN // false

NaN与任何值都不相等,包括自己。可以使用isNaN()来判断是否“不是数值”

String(null) // 'null'
null.toString() // cannot read property 'toString' of null
String(undefined) // 'undefined'
undefined.toString() // cannot read property 'toString' of null

null与undefined没有toString方法,但是可以用String转型函数来实现转型

var num = 20
--num // 19
num + 20 // 39

前置递增(递减)时,变量会在复制之前就被改变,相应的:

var num = 20
num-- + 20 // 40
num + 20 // 39

typeof可以确定一个变量是字符串、数值、布尔值还是undefined的最佳工具。但是当判断null和对象时都会返回’object’

typeof null // 'object'
typeof {} // 'object'
var a = {};
a instanceof Object // true

可以用instanceof来判断到底是对象还是null

var color = 'blue'
function getColor(){
	var color = 'red';
	return color;
}
color // 'blue'
getColor() // 'red'
color //’blue'

搜索过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符。如果在局部环境中找到 了该标识符,搜索过程停止,变量就绪。如果在局部环境中没有找到该变量名,则继续沿作用域链向上 搜索。搜索过程将一直追溯到全局环境的变量对象。

var colors = ['black', 'red', 'blue']
colors instanceof Array // true
Array.isArray(colors) // true

但是如果有两个框架意味着两个全局空间和两个Array构造函数,所以有另外一个方法Array.isArray(value)来判断是否为数组

colors.push('white') // 4
colors // ['red', 'blue', 'black', 'white']
colors.pop() // 'white'

数组模拟栈(后进先出),有push和pop方法,可以完成数组的推入和弹出。push可以推入多个参数,按左到右的顺序进行增加,pop则弹出最后一个参数。

colors.push('white', 'green')
colors.shift() // ['blue', 'black', 'white', 'green']
colors.unshift('red', 'brown') // ['red', 'brown', 'blue', 'black', 'white', 'green']

模拟队列(先进先出),有shift方法,实现在数组的第一个位置删除参数。使用unshift方法可以在数组的前端增加n个参数

console.log(sum(10, 10)) // 20
function sum(num1, num2){
	return num1 + num2
}

解析器会优先读取函数声明,使其在任何其他代码可用之前可用,如上例,函数定义在后,但是依然可以获取结果。(函数声明提升)

function fibonacci(n) {
    if(n==0 || n == 1)
        return n;
    return fibonacci(n-1) + fibonacci(n-2);
}

斐波那契数列优化:

function fibonacci(n){
	if(n == 0 || n == 1){
		return n;
	}
	return arguments.callee(n-1) + arguments.callee(n - 2)
}

Function中有一个arguments内部有callee,实际上可以调用当前函数,这样fibonacci运行完毕后会等待垃圾回收销毁,不会一直占用内存。

call与apply方法的区别在于接受参数的方式不同,call()需要在使用时将传递的参数全部列出来:

function sum(num1, num2){
	return num1 + num2
}
function callSum1(num1, num2){
	return sum.call(this, num1, num2)
}
function callSum2(num1, num2){
	return sum.apply(this, [num1, num2]);
}
function callSum3(num1, num2){
	return sum.apply(this, arguments);
}

如上,apply方法则接受arguments或者一个数组。也就是说apply方法最多接受两个参数,而call方法则可以接受多个参数。

var bar = function(){
	console.log(this.x);
}
var foo = {
	x:3
}
bar(); // undefined
var func = bar.bind(foo);
func(); // 3

如果bind有多个参数,那么第一个参数作为要被绑定的对象,之后的参数作为被调用的方法的参数使用

fucntion test(){
	return console.log.bind(console, ...arguments)()
}
test(1, 2, 3) // 1 2 3

当多次调用bind时,仅第一次有效

var bar = function(){
    console.log(this.x);
}
var foo = {
    x:3
}
var sed = {
    x:4
}
var func = bar.bind(foo).bind(sed);
func(); //?
 
var fiv = {
    x:5
}
var func = bar.bind(foo).bind(sed).bind(fiv);
func(); //?

apply,call,bind的区别

var obj = {
    x: 81,
};
 
var foo = {
    getX: function() {
        return this.x;
    }
}
 
console.log(foo.getX.bind(obj)());  //81
console.log(foo.getX.call(obj));    //81
console.log(foo.getX.apply(obj));   //81

都可以绑定this,call和apply是立即执行的,而bind需要在后边加圆括号来执行函数。bind可以应用在回调函数上。

var a = '123456789'
var b = a.substring(1, 4) // 234
var c = a.substr(1, 4) // 2345
var d = a.slice(1,4) // 234

substring和slice第二个参数是截取的终点,截取到的内容是终点前一个字符,substr方法第二个参数是截取的字符串的长度。

Math.floor()执行向下舍入;Math.round()四舍五入;Math.ceil()执行向上舍入

var name = 'window';
var obj = {
	name: 'obj',
	getName: function(){
		return function(){
			return this.name
		}	
   }
}
console.log(obj.getName()()) // window

在全局函数中,this指向的是window,当函数被作为某个对象的方法来调用时,this指向的是当前对象,匿名函数的执行环境具有全局性,因此有时候指向的是window。

事件

点击某个div标签会先发生时间捕获,然后到达目标接收事件,最后是事件冒泡。这里写图片描述

DOM0级事件处理程序

例如onclick事件:

var btn = document.getElementById("myBtn");
btn.onclick = function(){
	alert(this.id)
}

DOM0级事件处理的优点是简单、跨浏览器,但是缺点是这些代码运行以前不会指定事件处理程序,因此如果这些代码位于某个按钮后面,就有可能在一段时间内怎么点击都没有反应。
以此种方式添加的事件会在事件流的冒泡阶段被处理,也可以:

btn.onclick = null

通过以上方式删除事件处理程序

DOM2级事件处理程序

两分方法,处理指定和删除事件处理程序:addEventListener()和removeEventListener()。两个方法均接受三个参数:要处理的事件名,作为事件处理程序的函数和一个布尔值,最后的布尔值为false时,表示冒泡阶段调用该事件,为true时表示捕获阶段调用事件。

var btn = document.getElementById('myBtn')
btn.addEventListener('click', function(){
	alert(this.id);
}, false)
btn.addEventListener('click', function(){
	alert('hello world')
}, false)

如上代码可以在同一个按钮上添加多个事件,事件会按顺序执行。但使用以上方法书写代码时会造成无法移除事件的问题,由于移除时传入的参数与添加时使用的参数要一致,所以使用匿名函数时会造成无法移除事件。

var btn = document.getElementById('myBtn');
var handle= function(){
	alert(this.id)
}
btn.addEventListener('click', handle, false);
btn.removeEventListener('click', handle, false);

使用以上代码则可以解决无法移除监听click事件的问题。大多数情况是将处理程序添加到冒泡阶段,即第三个参数为false,只有需要在事件到达目标之前截获它的时候才设为true。

IE事件处理程序

IE实现了与DOM中类似的两个方法:attackEvent()和detachEvent()。IE8及更早版本只支持冒泡,所以此方式添加的事件处理程序都会被添加到冒泡阶段。

var btn = document.getElementById('myBtn');
var handle= function(){
	alert(this.id) // 此时this指向window
}
btn.attackEvent('onclick', handle, false);
btn.detachEvent('onclick', handle, false);

在IE中使用attachEvent()与使用DOM0级方法的主要区别在于事件处理程序的作用域。在使用DOM0级方法的情况下,事件处理程序会在其所属元素作用域内运行;在使用attachEvent()方法时,事件处理程序会在全局作用域中运行。
当添加多个事件处理程序时,与DOM方法不同的是,这些事件处理程序的执行程序是从后往前的。与DOM事件类似,detachEvent生效需要传入相同的参数。还有一点不同就是“onclick”而不是“click”

跨浏览器的事件处理程序

var EventUtil = {
	addHandler: function(element, type, handler){
		if(element.addEventListener){
			element.addEventListener(type, handler, false);
		} else if(element.attachEvent){
			ement.attachEvent("on" + type, handler);
		} else {
			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
		}
	}
}

preventDefault()和stopPropagation()

要阻止特定事件的默认行为,可以使用preventDefault()。例如,连接的默认行为是在被单击时导航到其href指定的url,如果想阻止导航这一默认行为,可以使用该方法取消它:

var link = document.getElementById('myLink');
link.onclick = function(event){
	event.preventDefault()
}

想立即停止事件在DOM层次中传播,即取消进一步的时间捕获或冒泡。使用stopPropagation()。例如:

var btn = document.getElementById('myBtn')
btn.onclick = function(event){
	alert("clicked");
	event.stopPropagation();
}
document.body.onclick = function(event){
	alert("body clicked")
}

以上代码如果没有event.stopPropagation()则会依次弹出两个alert。

跨浏览器的事件对象

var EventUtil = {
	addHandler: function(element, type, handler){
		if(element.addEventListener){
			element.addEventListener(type, handler, false);
		} else if(element.attachEvent){
			ement.attachEvent("on" + type, handler);
		} else {
			element['on' + type] = handler;
		}
	},
	getEvent: function(event){
		return event ? event : window.event
	},
	getTarget: function(event){
		return event.target || event.srcElement
	},
	preventDefault: function(event){
		if(event.preventDeafult){
			event.preventDefault();
		} else {
			event.returnValue = false;
		}
	},
	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
		}
	},
	stopPropagation: function(event){
		if(event.stopPropagation){
			event.stopPropagation();
		} else {
			event.cancelBubble = true;
		}
	}
}

事件委托

<ul id="myLinks">
        <li id="goSomewhere">Go somewhere</li>
        <li id="doSomething">Do something</li>
        <li id="sayHi">Say Hi</li>
</ul>
document.getElementById('myLinks').addEventListener('click', function (event) {
    switch(event.target.id){
        case "doSomething":
            console.log('doSomething');
            break;
        case "goSomewhere":
            console.log("goSomewhere");
            break;
        case "sayHi":
            console.log("sayHi")
            break;
    }
})

由于所有项都是这个元素的子节点,所以最后事件冒泡会冒泡到myLinks上,所以可以通过检测id来判断当前点击的li。

如果可行的话可以考虑为document对象添加一个事件处理程序,优点如下:

  • document对象很快就可以访问,而且可以在生命周期的任何时间点添加处理程序
  • 在页面中设置事件处理程序所需时间更少
  • 整个页面占用的内存空间更少,能提升整体性能

模拟事件

可以在document上使用createEvent()方法创建event对象。这个方法接受一个参数,即表示要创建的事件类型的字符串。例如模拟鼠标事件:

var btn = document.getElementById('myBtn');
var event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
btn.dispatchEvent(event);

AJAX

为支持IE7早期版本,需要如下代码:

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"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){
					// jump
				}
			}
			return new ActiveXObject(arguments.callee.activeXString);
		}
	} else {
		throw new Error("No XHR object available.")
	}
}
var xhr = createXHR()
var xhr = createXHR();
xhr.onreadystatechange = function(){
	if(xhr.readyState == 4){
		if((chr.status >= 200 && chr.status < 300) || chr.status == 304){
			alert(xhr.responseText);
		} else {
			alert("Request was unsuccessful: " + xhr.status)
		}
	}
}
xhr.open("get", "example.txt", true); // 此处只是启动一个请求以备发送
xhr.send(null) //有写浏览器要求如果没有值此处必须为null

以上代码中的onreadystatechange可以监听请求/响应过程,需要在启动请求之前指定事件处理程序才能确保跨浏览器兼容性。

GET请求

var url = "example.php"
function addURLParam(url, name, value){
	url += (url.indexOf("?") == -1 ? "?" " "&";
	url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
	return url;
}
url = addURLParam(url, 'name', 'Tom');
xhr.open("get", url, false)

addURLParam方法可以帮助将键值对插入url并返回。

跨域

CORS的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是应该失败。
除了CORS之外还有图像Ping、JSONP、Comet、服务器发送事件和webSocket。

JSONP
function handleResponse(response){
	console.log("You’re at IP address " + response.ip + ", which is in " + response.city + ", " + response.region_name);
}
var script = document.createElement('script');
script.src = "http://freegeoip.net/json/?callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);

与图像Ping相比,优点在于能够直接响应文本,支持在浏览器与服务器之间双向通信。JSONP的不足之处在于,其一,JSONP是从其他域中加载代码执行,如果其他域不安全,很可能会夹带恶意代码,而此时除了完全放弃JSONP调用别无他法;其二,要确定JSONP请求是否失败不容易。

CSRF(跨站点请求伪造)

对于未被授权系统有权访问某个资源的情况,称之为CSRF。
解决方式是验证发送请求者是否有权访问相应的资源:

  1. 要求以SSL链接来访问可以通过XHR请求的资源
  2. 要求每一次请求都要附带经过相应算法计算得到的验证码

高级技巧

惰性载入函数

例如之前的createXHR()方法,每次调用这个方法都要经历一遍if语句,如果一个项目中创建多个AJAX请求那就意味着会经历多次if,减少if的经历可以提高性能。可以做出如下修改:

var createXHR = (function(){
	if (typeof XMLHttpRequest != "undefined"){
		return function(){
			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.");
		};
	}
})();

自执行函数可以在最开始遍历代码时就修改createXHR()方法为适应当前浏览器的方法,之后每次调用就不用再去经历if语句了。

高级定时器

Javascript试运行于单线程的环境中的,定时器仅仅只是计划代码在未来的某个时间执行,执行时机是不能保证的。

var btn = document.getElementById("my-btn");
btn.onclick = function(){
	setTimeout(function(){
		document.getElementById("message").style.visibility = "visible"
	}, 240)
	// other code
}

以上代码设置了一个250ms的定时器,点击按钮后,先将onclick事件处理程序加入队列,该处理程序执行后才设置定时器,再有250ms后指定的代码才被添加到队列中等待执行。也就是说,对setTimeout()的调用实际要晚点执行。
在这里插入图片描述

如上图所示,尽管在255ms处添加了定时器代码,但是还不能执行,由于onclick事件仍在运行,所以定时器代码最早能执行的时间点是在300ms

猜你喜欢

转载自blog.csdn.net/weixin_43170484/article/details/82587219