Javascript高级程序设计

1.高级函数

①var isArray = value instanceof Array;

上述代码要返回true,value必须是一个数组,而且还必须与Array构造函数在同个全局作用域中。(别忘了,Array是window的属性)。如果value是在另个frame里定义的数组,那么以上代码就会返回false

在任何值上调用Object原生的toString()方法,都会返回[object NativeConstructorName]格式的字符串。每个类在内部都有一个[[Class]]属性,这个属性就指定了上述字符串的构造函数名。

alert(Object.prototype.toString.call(value));     //"[object Array]"

由于原生数组的构造函数名与全局作用域无关,所以使用toString()都能保证返回一致的值。因此验证是否是一个数组:

  1. function isArray(value){
  2. return Object.prototyoe.toString.call(value) == [object Array];
  3. }

 

②作用域安全的构造函数

  1. function Person(name, age, job){
  2. if (this instanceof Person){
  3. this.name = name;
  4. this.age = age;
  5. this.job = job;
  6. } else {
  7. return new Person(name, age, job);
  8. }
  9. }
  10.  
  11. var person1 = Person("Nicholas", 29, "Software Engineer");
  12. alert(window.name); //""
  13. alert(person1.name); //"Nicholas"
  14.  
  15. var person2 = new Person("Shelby", 34, "Ergonomist");
  16. alert(person2.name); //"Shelby"


想一想,如果没验证this是否为Person,person1中的this就会解析成window对象。由于window.name属性是用于识别链接目标和frame的,所以这里对该属性的偶然覆盖可能会导致该页面上出现错误。window.name的作用是什么?详情戳:window.name实现的跨域

③惰性载入函数

  1. function createXHR(){
  2. if (typeof XMLHttpRequest != "undefined"){
  3. return new XMLHttpRequest();
  4. } else if (typeof ActiveXObject != "undefined"){
  5. if (typeof arguments.callee.activeXString != "string"){
  6. var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
  7. "MSXML2.XMLHttp"],
  8. i, len;
  9.  
  10. for (i=0,len=versions.length; i < len; i++){
  11. try {
  12. new ActiveXObject(versions[i]);
  13. arguments.callee.activeXString = versions[i];
  14. break;
  15. } catch (ex){
  16. //skip
  17. }
  18. }
  19. }
  20.  
  21. return new ActiveXObject(arguments.callee.activeXString);
  22. } else {
  23. throw new Error("No XHR object available.");
  24. }
  25. }

像上面那样,每次调用createXHR(),它都要对浏览器所支持的能力仔细检查,所以如果if语言不必每次都执行,那么代码可以运行地更快一些,解决方案就是称之为惰性载入的技巧

惰性函数表示函数执行的分支仅会发生一次。有两种实现惰性载入的方式。

第一种就是在函数被调用时再处理函数。在第一次调用的过程中,该函数会被覆盖为另外一个按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支。

  1. function createXHR(){
  2. if (typeof XMLHttpRequest != "undefined"){
  3. createXHR = function(){
  4. return new XMLHttpRequest();
  5. };
  6. } else if (typeof ActiveXObject != "undefined"){
  7. createXHR = function(){
  8. if (typeof arguments.callee.activeXString != "string"){
  9. var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
  10. "MSXML2.XMLHttp"],
  11. i, len;
  12.  
  13. for (i=0,len=versions.length; i < len; i++){
  14. try {
  15. new ActiveXObject(versions[i]);
  16. arguments.callee.activeXString = versions[i];
  17. } catch (ex){
  18. //skip
  19. }
  20. }
  21. }
  22.  
  23. return new ActiveXObject(arguments.callee.activeXString);
  24. };
  25. } else {
  26. createXHR = function(){
  27. throw new Error("No XHR object available.");
  28. };
  29. }
  30.  
  31. return createXHR();
  32. }
  33.  
  34. var xhr1 = createXHR();
  35. var xhr2 = createXHR();

这个堕入载入的createXHR()钟,if语句的每一个分支都会为creatXHR()变量赋值,有效覆盖了原有的函数。最后一步便是调用新赋的函数。下次调用createXHR()的时候,就会直接调用被分配的函数,这样就不用再次执行if语句了。

第二种实现惰性载入的方式是在声明函数时就指定适当的函数。这样,第一次调用函数时就不会损失性能了,而在代码首次加载时会损失一点性能。

  1. var createXHR = (function(){
  2. if (typeof XMLHttpRequest != "undefined"){
  3. return function(){
  4. return new XMLHttpRequest();
  5. };
  6. } else if (typeof ActiveXObject != "undefined"){
  7. return function(){
  8. if (typeof arguments.callee.activeXString != "string"){
  9. var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
  10. "MSXML2.XMLHttp"],
  11. i, len;
  12.  
  13. for (i=0,len=versions.length; i < len; i++){
  14. try {
  15. new ActiveXObject(versions[i]);
  16. arguments.callee.activeXString = versions[i];
  17. break;
  18. } catch (ex){
  19. //skip
  20. }
  21. }
  22. }
  23.  
  24. return new ActiveXObject(arguments.callee.activeXString);
  25. };
  26. } else {
  27. return function(){
  28. throw new Error("No XHR object available.");
  29. };
  30. }
  31. })();
  32.  
  33. var xhr1 = createXHR();
  34. var xhr2 = createXHR();

总结:惰性载入函数的优点是只在执行分支代码时牺牲一点而性能。至于两种方式哪种更适合,就要看你的具体需求而定。不过这两种方式都能避免执行不必要的代码。

4.函数绑定

该技巧常常和回调函数与事件处理程序一起使用,以便在将函数作为变量传递的同时保留代码执行环境

函数绑定的bind方法是:fun.bind(binedObject),binedObject是为fun函数绑定的对象,但这原生的bind()方法的兼容性为:IE9+、Firefox、Chrome,得自己写一个bind()函数

  1. var handler = {
  2. message: "Event handled",
  3.  
  4. handleClick: function(event){
  5. alert(this.message + ":" + event.type);
  6. }
  7. };
  8.  
  9. var btn = document.getElementById("my-btn");
  10. EventUtil.addHandler(btn, "click", handler.handleClick));

 

这个this对象指向了DOM按钮而非handler(在IE中,this指向window),所以可以使用闭包来修改这个问题。

  1. function bind(fn, context){
  2. return function(){
  3. return fn.apply(context, arguments);
  4. };
  5. }
  6.  
  7. var handler = {
  8. message: "Event handled",
  9.  
  10. handleClick: function(event){
  11. alert(this.message + ":" + event.type);
  12. }
  13. };
  14.  
  15. var btn = document.getElementById("my-btn");
  16. EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler));

不过不懂的是为什么要在bind()函数的闭包函数也return,明明不写return也能正常执行的,而且返回的还是undefined

这个函数看似简单,但其功能是非常强大的。注意这里使用的arguments对象是内部函数的,而非bind()的。

函数绑定主要用于事件处理程序以及setTimeout()和setInterval()。然而,被绑定函数与普通函数相比有更多的开销,它们需要更多内存(闭包),同时也因为多重函数调用稍微慢一些,所以最好只在必要时使用。

 

⑤函数柯里化(function currying)(但说实话,我还是没搞懂要怎么用,好扯淡。。)

它用于创建已经设置好了一个或多个参数的函数。基本方法和函数绑定是一样的:使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的函数

柯里化函数通常由以下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数。其简单概念如下:

  1. function add(num1, num2){
  2. return num1 + num2;
  3. }
  4.  
  5. function curriedAdd(num2){
  6. return add(5, num2);
  7. ]
  8.  
  9. alert(add(2, 3)); //5
  10. alert(curriedAdd(3)); //8


下面是创建柯里化函数的通用方式:

  1. function curry(fn){
  2. var args = Array.prototype.slice.call(arguments, 1); //args存放外部函数参数,除了fn这个参数
  3. return function(){
  4. var innerArgs = Array.prototype.slice.call(arguments), //存放内部函数参数
  5. finalArgs = args.concat(innerArgs);
  6. return fn.apply(null, finalArgs);
  7. };
  8. }
  9.  
  10. function add(num1, num2){
  11. return num1 + num2;
  12. }
  13.  
  14. var curriedAdd = curry(add, 5);
  15. alert(curriedAdd(3)); //8
  16.  
  17. var curriedAdd2 = curry(add, 5, 12);
  18. alert(curriedAdd2()); //17


函数柯里化还常常作为函数绑定的一部分包含在其中

  1. function bind(fn, context){
  2. var args = Array.prototype.slice.call(arguments, 2); //注意这里要传除了函数参数fn和对象参数context
  3. return function(){
  4. var innerArgs = Array.prototype.slice.call(arguments),
  5. finalArgs = args.concat(innerArgs);
  6. return fn.apply(context, finalArgs); //注意这里要传入context参数
  7. };
  8. }
  9.  
  10. var handler = {
  11. message: "Event handled",
  12.  
  13. handleClick: function(name, event){
  14. alert(this.message + ":" + name + ":" + event.type);
  15. }
  16. };
  17.  
  18. var btn = document.getElementById("my-btn");
  19. EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler, "my-btn"));

JavaScript中的柯里化函数和绑定函数提供了强大的动态函数创建功能。使用bind()还是curry()要根据是否需要object对象响应来决定。它们都能用于创建复杂的算法和功能,当然两者都不应滥用,因为每个函数都会带来额外开销。

 

 

2.防篡改对象

注意的是:一旦把对象定义为防篡改,就无法撤销了。ECMAScript增加了几个方法,通过它们可以指定对象的行为

①不可扩展对象:第一保护级别

使用Object.preventExtensions(object)使你不能再给对象添加属性和方法,object为你要改变的对象。在非严格模式下,尝试,给对象添加新成员会导致静默失败,因此object.property将是undefined。而在严格模式下,尝试给不可扩展的对象添加新成员会导致错误。但不会对已有的对象成员造成应该。

另外可使用Object.isExtensible()方法确定对象是否可扩展

②密封的对象:第二保护级别

密封对象不可扩展,而且已有成员的[[Configurable]]特性将被设置为false,这将意味着不能删除属性和方法。但已有属性值是可修改的。在严格模式和非严格模式下的影响和不可扩展对象一样,导致错误或被忽略。

另外可使用Object.isSealed()可确定对象是否被密封了。

③冻结的对象:第三保护级别

最严格的防篡改级别。冻结的对象既不可扩展,又是密封的,而且对象数据属性的[ [ Writable ]]特性会被设置为false。如果设置[ [ Set] ]函数,访问器属性仍然是可写的。在严格模式和非严格模式下的影响和不可扩展对象一样,导致错误或被忽略。

可使用Object.isFrozen()方法确定是否检测冻结对象

对JavaScript库的作者而言,冻结对象是很很有用的。因为JavaScript库最怕有人意外(或有意)地修改了库中的核心对象。冻结(或密封)主要的库对象能够防止这些问题的发生。

 

3.高级定时器

虽然人们对JavaScript的定时器存在普遍的误解,认为它们是线程,其实JavaScript是运行在单线程的环境中的,而定时器仅仅只是计划在未来的某个时间执行。执行实际是不能保证的,因为在页面的声明周期内,不同时间可能有其他代码在控制进程。(如设定一个150ms的定时器不代表到了150ms代码就立刻执行,它表示会在150ms后会被加入到执行队列中)在页面下载完后的代码运行、时间处理程序、Ajax回调函数都必须使用同样的线程来执行。实际上,浏览器负责进行排序,指派某段代码在某个时间点运行的优先级。

①重复的定时器:setInterval()

重复的定时器问题在于,定时器代码可能在代码再次被添加到队列之前还没完全执行,结果导致定时器代码连续执行好几次,而之间没有停顿。幸好,JavaScript引擎够聪明,能避免这个问题。当使用setInterval()时,仅当没有该定时器代码实例时,才将定时器代码代码添加到队列中。这确保定时器代码加入到队列中的最小时间间隔为指定间隔。

这个规则有两个问题:(1)某些间隔会被跳过;(2)多个定时器的代码执行之间的间隔可能会被预期的小。

为了避免setInterval()的重复定时器的这两个缺点,你可以用如下模式使用链式setTimeout()调用。

  1. setTimeout(function(){
  2. //处理中
  3.  
  4. setTimeout(argumrnts.callee, interval);
  5.  
  6. }, interval);

这么做的好处是,在前一个定时器代码执行完之前,不会向队列中插入新的定时器代码,确保不会有任何缺失的间隔。而且它可以保证在下一次定时器代码执行之前,至少要等待指定的间隔,避免了连续的运行。应用如下:

  1. setTimeout(function(){
  2.  
  3. var div = document.getElementById("myDiv"),
  4. left = parseInt(div.style.left) + 5;
  5. div.style.left = left + "px";
  6.  
  7. if (left < 200){
  8. setTimeout(arguments.callee, 50);
  9. }
  10.  
  11. }, 50);

注意:每个浏览器窗口、标签页、或者frame都有各自的代码执行队列。这意味着,进行跨frame或者跨串口的定时调用,当代码同时执行的时候可能会导致竞争条件。无论何时需要使用这种通信类型,最好是在接受frame或者窗口中创建一个定时器来执行代码。

 

 

3.Yielding Processes(让出进程)

背景:运行在浏览器中的JavaScript都被分配了一个确定数量的资源。不同于桌面应该往往能够随意控制他们要的内存大小和处理器时间,JavaScript被严格限制了,以防止恶意的Web程序员把用户的计算机搞挂了。其中一个限制是长时间运行脚本的限制,如果代码运行超过特定的时间或特定语句数量就不让它继续执行。如果代码达到了这个限制,会弹出一个浏览器错误的对话框,告诉用户某个脚本会用过长的时间执行,询问是否其继续执行还是停止它。所有JavaScript开发人员的目标就是,确保用户永远不会在浏览器中看到这个令人费解的对话框。定时器是绕开此限制的方法之一。

脚本长时间运行的问题通常是由两个原因之一造成的:①过长的、过深嵌套的函数调用②进行大量处理的循环

  长时间运行的循环通常遵循以下模式:

  1. for(var i =0, len=data.length; i<len; i++){
  2. process(data[i]);
  3. }

如果process()要处理很久,那意味着这段时间内用户无法与页面交互的时间会很久

在展开该循环之前,你需要回答以下两个问题

【】该处理是否必须同步完成?

【】数据是否必须按顺序完成?

因此,当你发现某个循环占用了大量时间,同时对于上述两个问题,你的回答都是否,那么你就可以使用定时器分隔这个循环。这是一种叫做数据分组(array chunking)的技术,小块小块地处理数组,通常每次处理一块。

  1. var data = [12,123,1234,453,436,23,23,5,4123,45,346,5634,2234,345,342];
  2.  
  3. function chunk(array, process, context){
  4. setTimeout(function(){
  5. var item = array.shift();
  6. process.call(context, item);
  7.  
  8. if (array.length > 0){
  9. setTimeout(arguments.callee, 100);
  10. }
  11. }, 100);
  12. }
  13.  
  14. function printValue(item){
  15. var div = document.getElementById("myDiv");
  16. div.innerHTML += item + "<br>";
  17. }
  18.  
  19. chunk(data, printValue); //如果想保持原数组,chunck(data.concat(), printValue);



函数节流

背景:浏览器中某些计算和处理比其他的昂贵很多,例如DOM操作,消耗更多的内存和CPU时间

基本思想:某些代码不可以在没有间断的情况连续重复执行

实现:定时器

应用:只要代码是周期性执行,都应该使用节流,但是你不能控制请求执行的频率。

  1. function throttle(method, scope) {
  2. clearTimeout(method.tId);
  3. method.tId= setTimeout(function(){
  4. method.call(scope); //没有传入scope时,那么就在全局作用域内执行该方法
  5. }, 100);
  6. }


节流在resize事件中是最常见的。

  1. function resizeDiv(){
  2. var div = document.getElementById("myDiv");
  3. div.style.height = div.offsetWidth + "px";
  4. }
  5.  
  6. window.onresize = function(){
  7. throttle(resizeDiv);
  8. };

但要注意这函数节流在onscroll事件并不合适,因为onscroll事件要求滚动条每变化1像素就要求有所反应,要是有些滚动条变化没被响应,效果就没那么好了。

 

 

自定义事件

背景:事件是一种叫做观察者的设计模式,这是一种创建松散耦合代码的技术。对象可以发布事件,用来表示在该对象生命周期中某个有趣的时刻到了。然后其他对象可以观察该对象,等待这些有趣的时刻到来并通过运行代码来响应

观察者模式:由两类对象组成,主体和观察者。主题负责发布事件,同时观察者通过订阅这些事件来观察该主体。该模式的一个关键概念是主体并不知道观察者的任何事情,也就是说它可以独自存在并正常运作即使观察者不存在。从另一方面来说,观察者知道主体并能注册事件的回调函数(事件处理程序)。涉及DOM上时,DOM元素便是主体,你的事件处理代码便是观察者。

事件是与DOM交互的最常见的方式,但它们也可以用于非DOM代码中-----通过实现自定义事件。自定义事件背后的概念是创建一个管理事件的对象,让其他对象监听那些事件。实现此功能的基本模式可以如下定义:

  1. function EventTarget(){
  2. this.handlers = {};
  3. }
  4.  
  5. EventTarget.prototype = {
  6. constructor: EventTarget,
  7.  
  8. addHandler: function(type, handler){
  9. if (typeof this.handlers[type] == "undefined"){
  10. this.handlers[type] = [];
  11. }
  12.  
  13. this.handlers[type].push(handler);
  14. },
  15.  
  16. fire: function(event){
  17. if (!event.target){
  18. event.target = this;
  19. }
  20. if (this.handlers[event.type] instanceof Array){
  21. var handlers = this.handlers[event.type];
  22. for (var i=0, len=handlers.length; i < len; i++){
  23. handlers[i](event);
  24. }
  25. }
  26. },
  27.  
  28. removeHandler: function(type, handler){
  29. if (this.handlers[type] instanceof Array){
  30. var handlers = this.handlers[type];
  31. for (var i=0, len=handlers.length; i < len; i++){
  32. if (handlers[i] === handler){
  33. break;
  34. }
  35. }
  36.  
  37. handlers.splice(i, 1);
  38. }
  39. }
  40. };


简单的使用如下:

  1. function handleMessage(event){
  2. alert("Message received: " + event.message);
  3. }
  4.  
  5. var target = new EventTarget();
  6.  
  7. target.addHandler("message", handleMessage);
  8.  
  9. target.fire({ type: "message", message: "Hello world!"});
  10.  
  11. target.removeHandler("message", handleMessage);
  12.  
  13. target.fire({ type: "message", message: "Hello world!"});

应用:当代码中存在多个部分在特定时刻相互交互的情况,自定义事件就非常有用了。这时,如果每个对象都有对其他所有对象引用。那么整个代码就会紧密耦合,同时维护也变得很困难,因此对某个对象的修改也会影响其他对象。使用自定义事件有助于解耦相关对象,保护功能的隔绝。在很多情况中,触发事件的代码和监听事件代码是完全分离的。

 

拖放

因为没什么好说的,就直接贴代码了,下面是一个单例对象,并使用了模块模式来隐藏某些实现细节

  1. var DragDrop = function(){
  2.  
  3. var dragging = null,
  4. diffX = 0,
  5. diffY = 0;
  6.  
  7. function handleEvent(event){
  8.  
  9. //get event and target
  10. event = EventUtil.getEvent(event);
  11. var target = EventUtil.getTarget(event);
  12.  
  13. //determine the type of event
  14. switch(event.type){
  15. case "mousedown":
  16. if (target.className.indexOf("draggable") > -1){
  17. dragging = target;
  18. diffX = event.clientX - target.offsetLeft; //注意这里的event和target
  19. diffY = event.clientY - target.offsetTop;
  20. }
  21. break;
  22.  
  23. case "mousemove":
  24. if (dragging !== null){
  25.  
  26. //assign location
  27. dragging.style.left = (event.clientX - diffX) + "px";
  28. dragging.style.top = (event.clientY - diffY) + "px";
  29. }
  30. break;
  31.  
  32. case "mouseup":
  33. dragging = null;
  34. break;
  35. }
  36. };
  37.  
  38. //public interface
  39. return {
  40. enable: function(){
  41. EventUtil.addHandler(document, "mousedown", handleEvent);
  42. EventUtil.addHandler(document, "mousemove", handleEvent);
  43. EventUtil.addHandler(document, "mouseup", handleEvent);
  44. },
  45.  
  46. disable: function(){
  47. EventUtil.removeHandler(document, "mousedown", handleEvent);
  48. EventUtil.removeHandler(document, "mousemove", handleEvent);
  49. EventUtil.removeHandler(document, "mouseup", handleEvent);
  50. }
  51. }
  52. }();
  53.  
  54. DragDrop.enable();


添加自定义事件----拖放

拖放功能还不能真正应用起来,除非能知道什么时候拖动开始了。从这点看,前面的代码没有提供任何方法表示拖动开始、正在拖动或者已经结束。这是,可以使用自定义事件来指示这几个事件的发生,让应用的其他部分与拖动功能进行交互。

  1. var DragDrop = function(){
  2.  
  3. var dragdrop = new EventTarget(),
  4. dragging = null,
  5. diffX = 0,
  6. diffY = 0;
  7.  
  8. function handleEvent(event){
  9.  
  10. //get event and target
  11. event = EventUtil.getEvent(event);
  12. var target = EventUtil.getTarget(event);
  13.  
  14. //determine the type of event
  15. switch(event.type){
  16. case "mousedown":
  17. if (target.className.indexOf("draggable") > -1){
  18. dragging = target;
  19. diffX = event.clientX - target.offsetLeft;
  20. diffY = event.clientY - target.offsetTop;
  21. dragdrop.fire({type:"dragstart", target: dragging, x: event.clientX, y: event.clientY});
  22. }
  23. break;
  24.  
  25. case "mousemove":
  26. if (dragging !== null){
  27.  
  28. //assign location
  29. dragging.style.left = (event.clientX - diffX) + "px";
  30. dragging.style.top = (event.clientY - diffY) + "px";
  31.  
  32. //fire custom event
  33. dragdrop.fire({type:"drag", target: dragging, x: event.clientX, y: event.clientY});
  34. }
  35. break;
  36.  
  37. case "mouseup":
  38. dragdrop.fire({type:"dragend", target: dragging, x: event.clientX, y: event.clientY});
  39. dragging = null;
  40. break;
  41. }
  42. };
  43.  
  44. //public interface
  45. dragdrop.enable = function(){
  46. EventUtil.addHandler(document, "mousedown", handleEvent);
  47. EventUtil.addHandler(document, "mousemove", handleEvent);
  48. EventUtil.addHandler(document, "mouseup", handleEvent);
  49. };
  50.  
  51. dragdrop.disable = function(){
  52. EventUtil.removeHandler(document, "mousedown", handleEvent);
  53. EventUtil.removeHandler(document, "mousemove", handleEvent);
  54. EventUtil.removeHandler(document, "mouseup", handleEvent);
  55. };
  56.  
  57. return dragdrop;
  58. }();
  59.  
  60. DragDrop.enable();
  61.  
  62. DragDrop.addHandler("dragstart", function(event){
  63. var status = document.getElementById("status");
  64. status.innerHTML = "Started dragging " + event.target.id;
  65. });
  66.  
  67. DragDrop.addHandler("drag", function(event){
  68. var status = document.getElementById("status");
  69. status.innerHTML += "<br>Dragged " + event.target.id + " to (" + event.x + "," + event.y + ")";
  70. });
  71.  
  72. DragDrop.addHandler("dragend", function(event){
  73. var status = document.getElementById("status");
  74. status.innerHTML += "<br>Dropped " + event.target.id + " at (" + event.x + "," + event.y + ")";
  75. });


这段代码定义了三个事件:dragstart、drag和dragend。和上面不支持事件的代码的区别还是很大的。为DragDrop添加自定义事件可以使这个对象更加健壮,它将可以在网络应用中处理复杂的拖放功能。

 

总结:①可以创建作用域安全的构造函数,确保在缺少new操作符时调用构造函数不会改变错误的环境对象

②可以使用惰性载入函数,将任何代码分支推迟到第一次调用函数的时候

③函数绑定可以让你创建始终在制定环境中运行的函数,同时函数函数柯里化可以让你创建已经填了某些参数的函数

④将绑定和函数柯里化结合起来,就能够给你一种在任意环境中以任意参数执行任意函数的方法

⑤定时器原理可以让你实现数组分块和函数节流的技术

⑥使用自定义事件有助于将不同部分的代码相互之间解耦,让维护更加容易,并减少引入错误的机会。

⑦将拖放行为和自定义事件结合起来可以创建一个可重复使用的框架,它能应用于各种不同的情况下。

这一章真的能学到很多,而且并不是空洞的知识,本人亲测,这些知识都能应用在实际中,棒棒哒。PS:看不懂就先放下,这本书第一次看不懂很正常,等有了一些项目实践后再看,会更加清晰,而且必须看很多遍,等书上的例子都应用到实际中用烂了,这本书你才算真正的看懂了。

posted on 2018-09-06 10:07  奔跑吧人生  阅读(186)  评论(0编辑  收藏  举报

导航