bindReady: function() {
...
if ( document.addEventListener ) {
// Use the handy event callback
document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
// A fallback to window.onload, that will always work
window.addEventListener( "load", jQuery.ready, false );
// If IE event model is used
} else if ( document.attachEvent ) {
document.attachEvent( "onreadystatechange", DOMContentLoaded );
// A fallback to window.onload, that will always work
window.attachEvent( "onload", jQuery.ready );
get: function( elem, computed ) {
// IE uses filters for opacity
return ropacity.test( (
computed && elem.currentStyle ?
elem.currentStyle.filter : elem.style.filter) || "" ) ?
( parseFloat( RegExp.$1 ) / 100 ) + "" :
computed ? "1" : "";
},
set: function( elem, value ) {
var style = elem.style,
currentStyle = elem.currentStyle,
opacity = jQuery.isNumeric( value ) ?
"alpha(opacity=" + value * 100 + ")" : "",
filter = currentStyle && currentStyle.filter || style.filter || "";
// IE has trouble with opacity if it does not have layout
// Force it by setting the zoom level
style.zoom = 1;
// if setting opacity to 1, and no other filters
//exist - attempt to remove filter attribute #6652
if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) {
// Setting style.filter to null, "" & " " still leave
// "filter:" in the cssText if "filter:" is present at all,
// clearType is disabled, we want to avoid this style.removeAttribute
// is IE Only, but so apparently is this code path...
style.removeAttribute( "filter" );
// if there there is no filter style applied in a css rule, we are done
if ( currentStyle && !currentStyle.filter ) {
return;
}
}
// otherwise, set new filter values
style.filter = ralpha.test( filter ) ?
filter.replace( ralpha, opacity ) :
filter + " " + opacity;
}
};
// Book Factory singleton
var BookFactory = (function () {
var existingBooks = {}, existingBook;
return {
createBook: function ( title, author, genre, pageCount, publisherID, ISBN ) {
// Find out if a particular book meta-data combination has been created before
// !! or (bang bang) forces a boolean to be returned
existingBook = existingBooks[ISBN];
if ( !!existingBook ) {
return existingBook;
} else {
// if not, let's create a new instance of the book and store it
var book = new Book( title, author, genre, pageCount, publisherID, ISBN );
existingBooks[ISBN] = book;
return book;
}
}
};
})();
在这个工厂函数中,我们将会检查当前需要创建的书籍是否已经存在,如果存在直接返回书实例;否则进行调用 Book 构造函数进行创建。这保证了所有的书都是唯一的,而不存在重复。
More Info (Address) This is more informationEven More Info (Map)
我们集中将事件处理放到父容器上:
var stateManager = {
fly: function () {
var self = this;
$( "#container" )
.unbind()
.on( "click", "div.toggle", function ( e ) {
self.handleClick( e.target );
});
},
handleClick: function ( elem ) {
$( elem ).find( "span" ).toggle( "slow" );
}
};
如此类似,前面课程提到过的 React 合成事件的池化机制,都体现了异曲同工之妙。
代理模式在前端中的应用
代理模式大家应该都不陌生,ES next 提供的 Proxy 让我们实现代理模式变得更加容易。关于 Proxy 的使用这些基础内容这里不过多赘述,直接来看一些代理模式的应用场景。
我们对函数进行代理,对函数的返回结果进行缓存。在函数执行时,优先使用缓存值,否则返回执行计算值:
const getCacheProxy = (fn, cache = new Map()) =>
new Proxy(fn, {
apply(target, context, args) {
const argsString = args.join(' ')
if (cache.has(argsString)) {
return cache.get(argsString)
}
const result = fn(...args)
cache.set(argsString, result)
return result
}
})
另外一个类似的实现:
const createThrottleProxy = (fn, timer) => {
let last = Date.now() - timer
return new Proxy(fn, {
apply(target, context, args) {
if (Date.now() - last >= rate) {
fn(args)
last = Date.now()
}
}
})
};
这些内容在前面的课程都有渗透,相信读者已经不难理解了。我们再来看 jQuery 当中的例子:
$( "button" ).on( "click", function () {
// Within this function, "this" refers to the element that was clicked
$( this ).addClass( "active" );
});
通过 $( this ) 可以获取到当前触发事件的元素,但是:
$( "button" ).on( "click", function () {
setTimeout(function () {
// "this" doesn't refer to our element!
$( this ).addClass( "active" );
});
});
但是这里的 $( this ) 不再是预期之中的结果。为此,jQuery 提供了 .proxy() 方法,这是典型的代理模式体现。
$( "button" ).on( "click", function () {
setTimeout( $.proxy( function () {
// "this" now refers to our element as we wanted
$( this ).addClass( "active" );
}, this), 500);
// the last "this" we're passing tells $.proxy() that our DOM element
// is the value we want "this" to refer to.
});
来看一下 proxy 的实现:
// Bind a function to a context, optionally partially applying any
// arguments.
proxy: function( fn, context ) {
if ( typeof context === "string" ) {
var tmp = fn[ context ];
context = fn;
fn = tmp;
}
// Quick check to determine if target is callable, in the spec
// this throws a TypeError, but we will just return undefined.
if ( !jQuery.isFunction( fn ) ) {
return undefined;
}
// Simulated bind
var args = slice.call( arguments, 2 ),
proxy = function() {
return fn.apply( context, args.concat( slice.call( arguments ) ) );
};
// Set the guid of unique handler to the same of original handler, so it can be removed
proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
return proxy;
}
class Account {
next(account) {
this.successor = account
}
pay(amount) {
if (this.canPay(amount)) {
console.log(`Paid ${amount} using ${this.name}`)
} else if (this.successor) {
console.log(`Cannot pay using ${this.name}. Proceeding...`)
this.successor.pay(amount)
} else {
console.log('None of the accounts have enough balance')
}
}
canPay(amount) {
return this.balance >= amount
}
}
class Bank extends Account {
constructor(balance) {
super()
this.name = 'bank'
this.balance = balance
}
}
class Paypal extends Account {
constructor(balance) {
super()
this.name = 'Paypal'
this.balance = balance
}
}
class Bitcoin extends Account {
constructor(balance) {
super()
this.name = 'bitcoin'
this.balance = balance
}
}
在使用时,我们先给三个账户充钱:
const bank = new Bank(100) // Bank with balance 100
const paypal = new Paypal(200) // Paypal with balance 200
const bitcoin = new Bitcoin(300) // Bitcoin with balance 300
并按顺序优先调用银行付款、PayPal 付款、比特币付款:
bank.next(paypal)
paypal.next(bitcoin)
最终付款行为:
bank.pay(250)
输出:
Cannot pay using bank. Proceeding...
Cannot pay using Paypal. Proceeding...
Paid 250 using bitcoin