准备把最近阅读过的JavaScript 高级程序设计分享出来,可以为大家节省时间,也用来做自己的备忘。如果有不懂或者错误的地方,欢迎留言或者私信。
1 高级技巧
- 使用高级函数
- 防篡改对象
- Yielding Timers
1.1 安全的类型检测
在web开发中能够区分原生与非原生JavaScript对象非常难哟,只有这样才能确切知道某个对象到底有哪些功能。
typeof
由于它一些无法与指导额行为,经常会导致检测数据得到不靠谱的结果。
instanceof
对于一个网页,或者一个全局作用域而言,使用instanceof操作符就能得到满意的结果 。它的问题在于假定单一的全局执行环境,如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的Array构造函数。
if(value instanceof Array) {
//对数组执行某些操作
}
instanceof操作符存在多个全局作用域的情况下(例如包含多个iframe)
Array.isArray()
这个方法的目的是最终确定某个值到底是不是数组,而不管它是在哪个全局执行环境中创建的。
if(Array.isArray(value)){
//对数组执行某些操作
}
检测某个对象到底是原生对象还是开发人员自定义的对象。
浏览器开始原生支持JSON对象,因为很多人一直子啊使用Douglas Crockford的JSON库,而该库定义了一个全局JSON对象。于是开发人员很难确定页面中的JSON对象到底是不是原生的。
alert(Object.prototype.soString.call(value)) //[object Array]
由于原生数组的构造函数名与全局作用域无关,因此使用toString()就能保证返回一致的值。
function isArray (value) {
return Object.prototype.toString.call(value) == '[object Array]' //检测数组
}
function isFunction(value){
return Object.prototype.toString.call(value) == "[object Function]"; //检测函数
}
function isRegExp(value){
return Object.protoType.toString.call(value) == '[object Regexp]'; //检测正则表达式
}
不过要注意,对于在IE中以COM对象形式实现的任何函数,isFunction () 都将返回false(因为它们并非原生的JavaScript函数)
1.2 作用域安全的构造函数
多人在同一个页面上写JavaScript代码环境中,作用域安全构造函数就很有用。
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
var person = new Person('Nicholas', 29,'Software Engineer');
console.log (person.name) //Nicholas
var otherPerson = Person('Nicholas', 29,'Software Engineer');
console.log (window.name) //Nicholas
console.log (otherPerson.name) //报错
当和new操作符连用时,则会创建一个新的Person对象,同事会给它分配这些属性,当没有new操作符来调用该构造函数的情况时,由于该this对象是在运行时绑定的,所以直接调用Person(),this会映射到全局对象window上,导致错误对象的意外增加。
作用域安全的构造函数在进行任何更改前,首先确认this对象是正确类型的实例。如果不是,那么会创建新的实例并返回。
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);
}
}
var person1 = Person('Nicholas', 29, 'Software Engineer');
console.log(window.name); //''
console.log(person1.name) //Nicholas
var person2 = new Person('Shelby', 34, 'Ergonomist');
console.log(person2.name) //Shelby
调用Person构造函数时无论是否使用new操作符,都会返回一个Person 的新实例,这就避免了在全局独享上意外设置属性。
关于作用域安全的构造函数的贴心提示。实现这个模式后,你就锁定了可以使用构造函数的环境。
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 调用 Polygon的方法
this.width = width;
this.height = height;
this.getArea = function () {
return this.width * this.height
}
}
var rect = new Rectangle (5, 10)
console.log(rect.sides) //undefined
这段代码里,Polygon构造函数是作用域安全的,然而Rectangle构造函数不是,新创建一个Rectangle实例之后,这个实例应该通过Polygon.call() 来继承Polygon的sides属性。
由于Polygon返回的是一个Polygon的实例,Rectangle构造函数中的this对象并没有得到增长,同时Polygon.call() 返回的值也没有用到,所以Rectangle实例中就不会有sides属性。
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 调用 Polygon的方法
this.width = width;
this.height = height;
this.getArea = function () {
return this.width * this.height
}
}
Rectangle.prototype = new Polygon();
var rect = new Rectangle (5, 10)
console.log(rect.sides) //2
Rectangle实例同时也是一个Polygon实例,所以 Polygon.call会照原意执行,最终为Rectangle实例添加sides属性
1.3 惰性载入函数
因为浏览器之间行为的差异,多数JavaScript 代码包含了大量的 if 语句,将执行引导到正确的代码中
let num = 0
function createXHR(){
console.log('调用==>', num ++) // 打印2次
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.");
}
}
createXHR()
setTimeout(
() => {
createXHR()
}, 2000
)
每次调用createXHR()的时候,它都要对浏览器所支持的能力仔细检查,首先检查内置的XHR,然后测试有没有基于ActiveX的XHR,最后如果都没有发现的话,就抛出一个错误。
如果if语句不必每次执行,代码可以运行地更快一些,解决方案就是称之为惰性载入的技巧。
有2种实现惰性载入的方式
1、第一种就是在函数被调用时再处理函数。
let num = 0
function createXHR(){
console.log('调用 ===>',num++); //打印一次
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"],
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 {
createXHR = function () {
throw new Error("No XHR object available.");
}
}
console.log(createXHR);//function (){return new XMLHttpRequest()}
return createXHR()
}
createXHR()
setTimeout(
() => {
createXHR()
}, 2000
)
2、第二种就是在声明函数时就指定适当的函数。
let num = 0;
var createXHR = (function () {
console.log(num++);
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 XHRobject available'
)
}
}
})()
createXHR()
setTimeout(
() => {
createXHR()
}, 2000
)
第二个例子,技巧是创建一个匿名、自执行的函数,用于确定应该使用哪一个函数实现。
1.4函数绑定
函数绑定要创建一个函数,可以在特定的this 环境中以指定参数调用另一个函数。
bind
一个简单的bind()函数接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动传递过去。
只要是将某个函数指针以值的形式进行传递,同时该函数必须在特定环境中执行,被绑定函数的效用就凸显出来了。
然而,被绑定函数与普通函数相比有更多开销,他们需要更多内存。同时也因为多重函数调用稍微慢一点,所以最好只在必要时使用。
1.4函数柯里化
所谓"柯里化",就是把一个多参数的函数,转化为单参数函数
用于创建已经设置好了一个或多个参数的函数。函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。
区别在于:当函数被调用时,返回的函数还需要设置一些传入的参数。
柯里化函数通常由一下步骤动态创建:
调用另一个函数并为它传入要柯里化的函数和必要参数。
function add(num1, num2){
return num1 + num2
}
function curriedAdd(num2){
return add (5, num2)
}
alert(add(2,3))
alert(curriedAdd(3)) //与柯里化有异曲同工之妙
function curry (fn){
var args = Array.prototype.slice.call(arguments,1);
return function (){
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(null, finalArgs)
}
}
bind()函数提供了强大的动态函数创建功能。
2 防篡改对象
可以手工设置每个属性的[ [ Configurable ] ]、[ [ Writable ] ]、[ [ Enumerable ] ]、[ [ Value ] ]、[ [ Get ] ]、以及[ [ Set ] ]
不过注意,一旦把对象定义为防篡改,就无法撤销了。
2.1 不可拓展对象
默认情况下,所有对象都是可以扩展的。
Object.preventExtensions(obj)
var person = { name : 'Nicholas' }
Object.preventExtensions(person)
person.age = 29;
person.name = 'cher'
console.log( person.age ) // undefined 调用了Object.preventExtensions()方法后,就不能给person对象添加新属性和方法了。在非严格模式下,给对象添加新成员会导致静默失败,即为undefined,严格模式下,会报错。
console.log( person.name ) // cher 虽然不能给对象添加新成员,但是已有的成员不受影响,仍然可以修改和删除已有的成员。
Object.isExtensible(obj)
可确定对象是否可以扩展
2.2 密封的对象 sealed object
密封的对象不可扩展,而且已有成员[ [ Configurable ] ] 特性将被设置为false,这就意味着不能删除属性和方法。
Object.seal()
var person = { name : 'Nicholas' }
Object.seal(person);
person.age = 29;
alert(person.age); //undefined
delete person.name;
alert(person.name); //Nicholas
Object.isSealed()
可以确定独享是否被密封
2.3 冻结的对象frozen object
冻结的对象既不可扩展,又是密封的,而且对象数据属性的[[ Writable ]]特性会被设置为false。
如果定义[[ Set ]]函数,访问器属性仍然是可写的。
Object.freeze()
Object.isFrozen()
对于JavaScript库的作者而言,冻结对象是很有用的(封装),因为怕使用者更改库核心代码。
3 高级定时器 setTimeout setInterval
JavaScript是运行与单线程的环境中的。定时器仅仅只是计划代码在未来的某个时间执行。执行时机是不能保证的。
在JavaScript中没有任何代码是立刻执行的,但一旦进程空闲则尽快执行。
setInterval()创建的定时器确保了定时器代码规则的插入队列中。定时器代码可能在代码再次被添加到队列之前还没有完成执行。当使用setInterval()时,仅当没有该定时器的任何其他代码实例时,才将定时器加入到队列中的最小时间间隔为指定间隔。
3.1函数防抖
某些代码不可以在没有间断的情况连续重复执行。
在window.resize时建议使用。
function resiveDiv() {
console.log('resiveDiv调用了')
var div = document.getElementById('myDiv');
div.style.height = div.offsetWidth + 'px'
}
function debounce(method, context) {
clearTimeout(method.tId);
method.tId= setTimeout(function(){
method.call(context);
}, 100);
}
window.onresize = function () {
// debounce(resiveDiv)
resiveDiv() //即时调用了
}
function resiveDiv() {
console.log('resiveDiv调用了')
var div = document.getElementById('myDiv');
div.style.height = div.offsetWidth + 'px'
}
function debounce(method, context) {
clearTimeout(method.tId);
method.tId= setTimeout(function(){
method.call(context);
}, 100);
}
window.onresize = function () {
debounce(resiveDiv) //进行了函数防抖
}
3.2函数节流
// 函数节流
var canRun = true;
document.getElementById("throttle").onscroll = function(){
if(!canRun){
return
}
canRun = false
setTimeout( function () {
console.log("函数节流")
canRun = true
}, 500)
}
4 自定义事件
事件是JavaScript与浏览器交互的主要途径。
事件是一种叫做观察者的设计模式,这是一种创建松散耦合代码的技术。