第2条:理解JavaScript的浮点数
1.js数字只有一种类型
2.见代码
/** * Created by Administrator on 2017/7/2. */ console.log("charpter2"); console.log(1-0.41); //0.5900000000000001 double不能精确计算 console.log((8).toString(2));//1000 数字转其他进制输出 console.log(parseInt("1001", 2));//9 其他进制转数字
第3条:当心隐式的类型转换
1. - * / % 在计算之前会把参数转化成数字, +可以是数字或者字符串,有字符串就优先字符串, 位运算符 ~ & | ^ << >> >>>会将参数转化成32位整数
2.见代码
1 console.log("charpter3"); 2 console.log("17" * 3); //51 3 console.log(NaN === NaN);//false 4 console.log(isNaN(NaN));//true 5 console.log(isNaN("haha"));//true 6 console.log(isNaN(""));//false 7 console.log(isNaN(null));//false 8 console.log(isNaN(undefined));//true 9 console.log(null === null);//true 10 console.log(undefined === undefined);//true 11 function isReallyNaN(a) { // 判断参数到底是不是NaN的最简单方法 12 return a !== a; 13 } 14 console.log("object : " + { 15 toString: function () { 16 return "object" 17 }, valueOf: function () { 18 return 123; 19 } 20 }); //优先选择valueOf作为返回值
3. if || && 会把参数转化成布尔值, js里有7个转化后为假的值: false 0 -0 null undefined NaN ""
4.检查参数是否为undefined可以用typeof(x) === "undefined" 或者 x===undefined
第4条:原始类型由于封装对象
1.js有5个原始值类型:boolean number string null undefined
2.原始值也可以调用对应的包装类的方法,但是对原始值设置属性没有什么用处,因为原始值会被隐式封装成1个新的对象,所以其实是在操作那个新的对象.
3.一些实验
1 console.log("charpter4"); 2 console.log(typeof null); //object, null比较特殊,和object一样 3 console.log(typeof undefined);//undefined 4 console.log(typeof NaN);//number 5 console.log(typeof true);//boolean 6 //String类和字符串还是有区别的 7 console.log(typeof "");//string 8 console.log(typeof new String());//object 9 console.log("123" === "123");//true 10 console.log(new String("123") == new String("123"));//false
第5条:避免对混合类型使用==运算符
1.将字符串转为数字可以使用Number()或者+运算符
2.实验结果:
1 console.log("charpter4"); 2 // == 比较的原则为 3 console.log(null == undefined); //true 1.null,undefined 与 null,undefined比较不转型直接返回true 4 console.log(null == 0); //false 2. null,undefined 与 非null,undefined 比较不转型直接返回false 5 6 // 3.非null,undefined原始类型与Date比较都转为数字,Date比较特殊,优先调用toString而不是valueOf 7 var dateToStringFunction = Date.prototype.toString; 8 Date.prototype.toString = function(){ 9 console.log("Date.toString"); 10 var result = dateToStringFunction.apply(this, arguments); 11 console.log(result);//Sun Jul 02 2017 18:12:42 GMT+0800 (中国标准时间) 12 return result; 13 } 14 var dateValueOfFunction = Date.prototype.valueOf; 15 Date.prototype.valueOf = function(){ 16 console.log("Date.valueOf"); 17 var result = dateValueOfFunction.apply(this, arguments); 18 console.log(result); 19 return result; 20 } 21 console.log(true == new Date()); //false 因为转化成数字 new Data().toString() 结果为NaN 22 //clean 23 Date.prototype.toString = dateToStringFunction; 24 Date.prototype.valueOf = dateValueOfFunction; 25 26 // 4.非null,undefined原始类型与非Date比较都转为数字,优先调用valueOf 27 var objectToStringFunction = Object.prototype.toString; 28 Object.prototype.toString = function(){ 29 console.log("Object.toString"); 30 var result = objectToStringFunction.apply(this, arguments); 31 console.log(result); 32 return result; 33 } 34 var objectValueOfFunction = Object.prototype.valueOf; 35 Object.prototype.valueOf = function(){ 36 console.log("Object.valueOf"); 37 var result = objectValueOfFunction.apply(this, arguments); 38 console.log(result); //Object 39 return result; 40 } 41 console.log(true == new Object()); //false 42 //clean 43 Object.prototype.toString = objectToStringFunction; 44 Object.prototype.valueOf = objectValueOfFunction; 45 46 //5.非null,undefined原始类型与非null,undefined原始类型比较转为数字 47 console.log(true == "1"); //true
3.==会产生隐式类型转化,所以还是自己强制转化然后使用===比较好理解.
第6条:了解分号插入的局限
1.js可以自动插入分号.规则是:①分号仅在}之前,一个或多个换行之后和程序输入的结尾被插入,所以一行有多处要插入分号的话是不能省略的.
②分号在随后的输入标记不能解析时插入,这可能会导致程序解析和想的不一样.比如
a=b ["r", "g", "b"].foEach(function(key)){ ......... }
会被解释成a = b["b"].forEach(......),因为b["r", "g", "b"]可以被解析(,分隔表达式从左向右执行,返回最后边的表达式的值)
③for不会作为for循环头部被插入,所以如果for头部有多行,要自己插入分号
2.一般还是自己插入;比较清楚简单,但是有时候js也会自动插入分号.比如return同一行最后.
return {};
等价于return ; {};程序不会报错.其他还有些限制产生式.但是我基本都会自动插入;并且不换行避免.
第7条:视字符串为16位代码单元序列
1.当字符串包含辅助平面的代码点时候,js将每个代码点表示为2个元素而不是1个.所以特殊字符应该算length=2,比如∮.
但是实际上我自己测试结果长度还是1.
不知道为什么..
第8条:尽量少用全局对象
console.log(); console.log("charpter8"); //console.log(a); //Uncaught ReferenceError: a is not defined console.log(this.a);//undefined
第9条:始终声明局部变量
1.JS给一个未绑定的变量赋值会简单的创建一个新的全局变量并赋值给它,而不是报错.
第10条:避免使用with
1.如题
2.在with代码块的内部,变量查找先开始找with对象的属性,和从原型链上继承下来的属性,没有再找外部作用域.
4.with需要搜素对象的原型链,所以运行速度比一般代码块慢.
第11条:熟练掌握闭包
1.闭包就是能够使用外部作用域定义的变量的函数
2.闭包比创建他们的函数有更长的声明周期
3.一个测试:
1 console.log(); 2 console.log("charpter11"); 3 (function () { 4 function f() { 5 var a = 1; 6 function add1() { 7 a++; 8 console.log(a); 9 } 10 function add2() { 11 a++; 12 console.log(a); 13 } 14 add1(); // 2 15 add2(); // 3 16 } 17 f(); 18 })();
第12条:理解变量声明提升
1.JS不支持块级作用域,变量定义的作用域并不是离其最近的代码块,而是包含它们的函数.
2.JS没有块级作用域的一个例外是异常.
第13条:熟练掌握闭包
1.闭包存储的是其外部变量的引用而不是值
2.可以用自执行函数来创建局部作用域
3.JS不支持块级作用域,但是可以使用函数作用域代替.
第14条:当心命名函数表达式笨拙的作用域
1.命名函数表达式由于会导致很多问题,所以并不值得使用
2.一些实验
console.log(); console.log("charpter14"); // 以下测试说明的我的360极速的chrome内核已经支持es5了. (function () { var constructor = function () { return null; }; var f = function f() { return console.log(constructor());//null }; f(); var f = function () { return console.log(constructor());//null }; f(); })();
第15条:当心命名函数表达式笨拙的作用域
1.因为没有块级作用域,所以声明在代码块中的函数其实还是相当于声明在外部作用域中的.但也不是所有环境都是如此,所以很容易出错.
2.ES5官方指定函数声明只能出现在其他函数或者程序的最外层.
3.可以使用var 声明和有条件的赋值语句代替有条件的函数声明.
4.一个实验
1 console.log(); 2 console.log("charpter15"); 3 (function () { 4 function f() { 5 return "global"; 6 } 7 8 function test(x) { 9 var g = f; 10 result = []; 11 if (x) { 12 g = function () { 13 return "local"; 14 } 15 result.push(g()); 16 } 17 result.push(g()); 18 return result; 19 } 20 console.log(test(false)); //["global"] 21 console.log(test(true)); // ["local", "local"] 22 })();
第17条:间接调用eval函数优于直接调用
1.绑定eval函数到另外一个变量名,通过该变量名调用函数会使代码失去对所有局部作用域的访问能力
2.测试:
1 var a = "global"; 2 console.log(); 3 console.log("charpter17"); 4 //直接调用eval就和普通函数一样,间接调用的话只能访问全局作用域 5 (function () { 6 var a = "outer"; 7 function test(){ 8 var a = "inner"; 9 console.log(eval("a")); //inner 10 } 11 test(); 12 13 function test2(){ 14 var a = "inner"; 15 console.log((0, eval)("a"));//global 16 } 17 test2(); 18 19 function test3(){ 20 var a = "inner"; 21 var f = eval; 22 console.log(f("a"));//global 23 } 24 test3(); 25 26 function test4(){ 27 var a = "inner"; 28 console.log((eval)("a"));//inner 29 } 30 test4(); 31 32 function test5(){ 33 //var a = "inner"; 34 console.log(eval("a")); //outer 35 } 36 test5(); 37 })();
3.尽可能间接调用eval函数,隐藏细节,同时也加快执行速度.
第18条:理解函数调用、方法调用以及构造函数调用之间的不同
1.在方法调用中是由调用表达式自身来确定this变量的绑定.绑定到this变量的对象被称为调用接收者.
2.通常,通过某个对象调用方法将查找该方法并将该对象作为该方法的接收者
3.一个非方法的函数调用会将全局对象作为接收者.
第19条:熟练掌握高阶函数
1.高阶函数式那些将函数作为参数活返回值的函数
2.需要引入高阶函数抽象的信号是出现重复或相似的代码.
第23条:永远不要修改arguments对象
1.如题,因为形参指向的是arguments中的元素,修改arguments实际上会修改形参
2.可以通过[].slice[arguments]来复制一份(浅克隆).
3.实验
1 console.log(); 2 console.log("charpter23"); 3 //obj 指向 arguments[0] . method指向 arguments[1] 4 (function () { 5 function callMethod(obj, method) { 6 var shift = [].shift; 7 shift.call(arguments); 8 shift.call(arguments); 9 return obj[method].apply(obj, arguments); 10 } 11 12 var obj = { 13 add : function (x, y) { 14 return x + y; 15 } 16 } 17 18 //callMethod(obj, "add", 17, 25); //Uncaught TypeError: Cannot read property 'apply' of undefined 19 })();
第25条:使用bind方法提取具有确定接收者的方法
1.在ES5中可以使用bind方法为方法绑定一个接收者
2.实验:
1 console.log(); 2 console.log("charpter25"); 3 (function () { 4 function f() { 5 console.log(this); 6 } 7 var f2 = f.bind({}); 8 f();//window 9 f2();//object 10 11 })();
第26条:使用bind方法实现函数柯里化
1.将函数与其参数的一个自己绑定的技术成为函数柯里化.
其实并不是很懂是啥意思,大概就是说本来参数是随便传入的,现在可能有些参数的值被固定了,剩下的参数随便传,可能是把一个非常通用的函数拆成部分业务通用的函数.
2实验:
1 console.log(); 2 console.log("charpter26"); 3 (function () { 4 function f(a, b, c) { 5 console.log(arguments); // ["a", "b", 1, 0, Array[3]] ......value=1 index=0 array=Array[3] 6 return a + b + c; 7 } 8 var a = [1, 2, 3]; 9 var b = a.map(f.bind(null, "a", "b")); //用bind简化函数调用,固定值可以直接写死. 10 console.log(b); //["ab1", "ab2", "ab3"] 11 12 //猜测bind的实现原理 13 function bind2() { 14 var arg0 = arguments; 15 var me = this; 16 return function () { 17 var arg = [].slice.apply(arg0, [1]); 18 arg.push.apply(arg, arguments) //后来发现array有个concat方法直接可以用....args.concat(arguments) 19 return me.apply(arg0[0], arg); 20 } 21 } 22 Function.prototype.bind2 = bind2; 23 //bind2 实验1 24 var b2 = a.map(f.bind2(null, "c", "d")); 25 console.log(b2); //["cd1", "cd2", "cd3"] 26 //bind2 实验2 27 var fun = console.log.bind2(console, "jet: "); 28 fun("hello world!"); //jet: hello world! 29 //bind2 实验3 30 var fun2 = function () { 31 console.log(this); 32 } 33 fun2.bind2(new Date())(); //Sun Jul 09 2017 16:12:55 GMT+0800 (中国标准时间) 34 })();
本来f方法里应该传3个参数a,b,c应该都是可变的,现在通过bind2直接写死了a和b,这样就只能变化c了.可能在部分业务可以减少很多代码.因为a和b的值是固定的.但是我感觉这样好不习惯..用bind感觉没有直接传函数清楚,可能我用的比较少.
25, 26两节总结下的话就是bind函数可以改变函数中的this.同时可以写死函数的部分参数值(其实是返回了一个新的函数来实现的).
第27条:使用闭包而不是字符串来封装代码
1.eval函数里面使用的变量有可能会是全局变量也有可能是局部变量,要看怎么使用,所以比较容易出问题
2.闭包可以访问外部作用域的局部变量,所以有局部变量肯定先找局部变量.
3.传字符串代码的代码很难写,编译期也很难优化.而闭包则很简单.
4.一些测试:
1 console.log(); 2 console.log("charpter27"); 3 var a = "global"; 4 (function () { 5 var a = "outer"; 6 function f(n) { 7 eval(n); 8 } 9 function log() { 10 var a = "inner"; 11 f("console.log(a)"); 12 } 13 log(); //outer 14 15 16 function f2(){ 17 console.log(a); 18 } 19 function log2() { 20 var a = "inner"; 21 f2(); 22 } 23 log2(); //outer 24 })();
第30条:理解prototype、getPrototypeOf和__prototype__之间的不同
1.在对象上获取原型对象可以通过Object.getPrototypeOf方法,或者obj.__proto__
2.js中类的本质是一个构造函数与一个用于在该类实例间共享方法的原型.
3.实验:
1 console.log(); 2 console.log("charpter30"); 3 (function () { 4 var a = {}; 5 console.log(Object.getPrototypeOf(a) === Object.prototype);//true 6 console.log(Object.getPrototypeOf("") === String.prototype);//true 7 8 function f(a,b) { 9 this.a = a; 10 this.b = b; 11 } 12 f.prototype.c = 1; 13 console.log(f.toString === Function.prototype.toString);//true 14 console.log(f.toString === Object.prototype.toString);//false 15 console.log(f.prototype.a);//undefined 构造函数构造出来的对象的属性不再原型对象上 16 //和java的原理一样,就是创建的时候对象有个指向原型的指针 17 var objF = new f(); 18 console.log(objF.c);//1 19 f.prototype.c = 2; 20 console.log(objF.c);//2 21 f.prototype = {}; 22 console.log(objF.c);//2 23 })();
第32条:始终不要修改__proto__属性
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create
1 //Shape - superclass 2 function Shape() { 3 this.x = 0; 4 this.y = 0; 5 } 6 7 Shape.prototype.move = function(x, y) { 8 this.x += x; 9 this.y += y; 10 console.info("Shape moved."); 11 }; 12 13 // Rectangle - subclass 14 function Rectangle() { 15 Shape.call(this); //call super constructor. 16 } 17 18 Rectangle.prototype = Object.create(Shape.prototype); 19 20 var rect = new Rectangle(); 21 22 rect instanceof Rectangle //true. 23 rect instanceof Shape //true. 24 25 rect.move(1, 1); //Outputs, "Shape moved."
Rectangle.prototype.__proto__ === Shape.prototype 为true.所以var a = Object.creatye(X)就是创建了一个对象a它的原型是X.
第33条:使用构造函数与new操作符无关
1 console.log(); 2 console.log("charpter33"); 3 (function () { 4 function f() { 5 return new String("123"); 6 } 7 console.log(new f()); //String {0: "1", 1: "2", 2: "3", length: 3, [[PrimitiveValue]]: "123"} return可以覆盖new返回的对象 8 })();
第37条:认识this变量的隐式绑定问题
1.在回调函数中this很难看出来到底指向什么对象.所以要清楚的表达有3种方式:
第一个就是回调函数API多传1个对象,调用方法的时候this绑定到这个对象.所以这个是要看api是怎么写的.
第二个就是用对象指向this,比如在外层var me = this.然后调用me.XXX
第三个使用bindFunction.prototype.bind方法的第一个参数可以绑定方法的this指针.
第38条:在子类的构造函数中调用父类的构造函数
1.js继承的步骤:
第一,子类的构造函数不中调用Parent.call(this,.....);
第二,Child.prototype = Object.create(Parent.prototype);
第41条:将原型视为实现细节
1.实验:
1 console.log(); 2 console.log("charpter41"); 3 (function () { 4 function f(x) { 5 this.a = x; 6 } 7 8 f.prototype.a2 = function () { 9 10 } 11 12 function f2(x, y) { 13 f.call(this, x); 14 this.b = y; 15 } 16 17 f2.prototype = Object.create(f.prototype); 18 f2.prototype.b2 = function () { 19 20 } 21 22 var test = new f2(); 23 console.log(test); 24 console.log(test.__proto__ === f2.prototype); // true 25 console.log(f2.prototype.__proto__ === f.prototype); // true 26 console.log(test.hasOwnProperty("b2")); // false 27 console.log(test.hasOwnProperty("b")); // true 28 console.log(test.hasOwnProperty("a")); // true 实例都是定义在对象里的 29 console.log(f2.prototype.hasOwnProperty("b2")); // true 30 })();
第43条:使用Object的直接实例构造轻量级字典
1.实验:
1 console.log(); 2 console.log("charpter43"); 3 //利用Object.defineProperty可以添加特殊属性 4 (function () { 5 var o = {}; // 创建一个新对象 6 // Example of an object property added with defineProperty with a data property descriptor 7 Object.defineProperty(o, "a", { 8 value: 37, 9 writable: true, 10 enumerable: false, 11 configurable: true 12 }); 13 14 var o2 = {a: 37}; 15 16 for (var i in o) { 17 console.log(i); //无输出,因为不能枚举a属性 18 } 19 for (var i in o2) { 20 console.log(i); //a 21 } 22 23 console.log({} instanceof Object);//true 24 console.log({}.__proto__ === Object.prototype);//true 25 })();
第44条:使用null原型以防止原型污染
1.实验:
1 console.log(); 2 console.log("charpter44"); 3 (function () { 4 function f() { 5 6 } 7 f.prototype =null; 8 var a = new f(); 9 console.log(Object.getPrototypeOf(a) === Object.prototype); // true 10 11 var b = Object.create(null); 12 console.log(Object.getPrototypeOf(b) === null); // true 利用Object.create(null);来创建空原型的对象 13 })();
第45条:使用hasOwnProperty方法以避免原型污染
1.实验
1 console.log(); 2 console.log("charpter45"); 3 (function () { 4 var a = {}; 5 console.log("toString" in a); //true 6 7 for (var i in a) { 8 console.log(i); //无输出,不能被遍历的属性用in还是返回true的 9 } 10 11 })();
第48条:避免在枚举期间修改对象
for in 循环可能在不同的js环境中选择不同的枚举顺序,甚至在同一个js环境中执行也不相同.
第49条:数组迭代要优先使用for循环而不是for...in循环
1.for...in循环始终枚举所有key,key始终是字符串.
第50条:迭代方法优于循环
1.循环只有一点优于迭代函数,那就是前者有控制流程,如break,continue.
第51条:在数组对象上服用通用的数组方法
1.复用数组方法传入的参数不一定就是要数组,也可以是类数组,只要满足条件
①有length属性,0-2^32-1
②有索引,小于length在0-2^32-2.
第58条:区分数组对象和类数组对象
1.API绝不应该重载于其他类型有重叠的类型,比如区分Array和Object就比较麻烦.
2.当浏览器跨frame通信的时候,一个frame中的数组不会继承自另一个frame的Array.prototype
3.实验
console.log(); console.log("charpter58"); (function () { console.log(typeof []); //object console.log(typeof {}); //object console.log([] instanceof Array); //true console.log(Array.isArray([])); //true })();
第60条:支持方法连
1.无状态的方法可以返回新的对象支持方法链,比如string的replace,有状态的方法使用返回this来支持方法链
第61条:不要阻塞I/O事件队列
1.js并发的接受事件,但会使用一个事件队列按顺序地处理事件处理程序.
我的理解:
1 console.log(); 2 console.log("charpter61"); 3 (function () { 4 //3 1 2 5 $.ajax({ 6 url: "/a.jsp", 7 success : function (data) { 8 for(var i=0; i< 10000000000; i++){ 9 10 } 11 console.log(1); 12 } 13 }); 14 $.ajax({ 15 url: "/b.jsp", 16 success : function (data) { 17 console.log(2); 18 } 19 }); 20 console.log(3); 21 })();
3,一定是在1和2之前的.但是1,2之间的顺序要看后台运行情况.可能是12,也可能是21,所以我的理解是ajax是异步执行的,请求收到返回结果以后看哪个ajax先注册时间处理程序哪个就先执行.
第62条:在异步序列中使用嵌套活命名的回调函数
1.实验
1 console.log(); 2 console.log("charpter62"); 3 (function () { 4 // 3 4 5 1 2 5 $.ajax({ 6 url: "/a.jsp", 7 success: function (data) { 8 for (var i = 0; i < 1000000000; i++) { 9 10 } 11 console.log(1); 12 } 13 }); 14 $.ajax({ 15 url: "/b.jsp", 16 success: function (data) { 17 console.log(2); 18 } 19 }); 20 console.log(3); 21 for (var i = 0; i < 3000000000; i++) { 22 23 } 24 $.ajax({ 25 async: false, 26 url: "/a.jsp", 27 success: function (data) { 28 for (var i = 0; i < 1000000000; i++) { 29 30 } 31 console.log(4); 32 } 33 }); 34 console.log(5); 35 })(); 36 37 (function () { 38 // 5 1 39 $.ajax({ 40 url: "/a.jsp", 41 success: function (data) { 42 console.log(1); 43 } 44 }); 45 for (var i = 0; i < 3000000000; i++) { 46 47 } 48 console.log(5); 49 })(); 50 51 (function () { 52 // 后台哪个断点先放开就输出哪个对应的值 53 $.ajax({ 54 url: "/TestServlet", 55 success: function (data) { 56 console.log(1); 57 } 58 }); 59 $.ajax({ 60 url: "/TestServlet2", 61 success: function (data) { 62 console.log(2); 63 } 64 }); 65 console.log(5); 66 })(); 67 68 (function () { 69 // 在打印2之前后台就会收到请求,但是就算返回了还是先输出2再输出1 70 $.ajax({ 71 url: "/TestServlet", 72 success: function (data) { 73 console.log(1); 74 } 75 }); 76 for (var i = 0; i < 3000000000; i++) { 77 78 } 79 console.log(2); 80 })();
前2个实验:
第一个实验3,4,5,1,2的输出说明了异步操作的回调函数肯定是后于主函数执行的.
第二个实验5,1的输出说明了只有等主函数执行完毕才会轮到回调函数的触发.即使之前ajax已经返回结果也不会直接调用回调函数.
第三个实验我在后台打了断点,先放开哪个断点就执行哪个回调函数,说明ajax请求是异步的,但是只有等资源返回以后才会注册回调函数给事件队列,并不因为第一个ajax写在第二个ajax上面,它的success就先执行.而是等资源返回以后才会注册success.所以当第二个断点先放开的时候会先注册第二个ajax的success函数到事件队列中.
第四个实验在js打印2之前后台断点就收到请求.所以ajax请求资源是立即的.但是回调函数是资源返回完以后才注册的.
第63条:当心丢弃错误
1.异步API直接使用trycatch是不对的,因为结果会立刻返回也不会有错误.一般异步API都回传一个处理错误的回调函数来处理出错的情况,或者向回调函数中额外传一个包装了错误的参数,如果这个参数不为null或者其他假值,说明发生了错误.
第64条:对异步循环使用递归
1.把异步API放到循环中调用只会一次启用N个异步操作,如果想异步操作同步进行,需要递归调用这些异步操作.
2.异步API是不会导致栈溢出的,因为调用会立刻返回,当回调函数被压入栈的时候栈肯定是空的.
第65条:不要在计算时阻塞事件队列
1.如果计算需要很长时间,使用递归和循环都会导致主线程被卡死,可以考虑使用异步的操作比如setTimeout来解决,因为是异步的,在异步过程中其他事件可以被响应.优化一点的话一次异步过程中可以使用循环计算N次操作而不是单次操作.
第66条:使用计数器来执行并行操作
1.多个异步操作的回调函数的顺序是不同的,所以如果要同步所有异步操作的结果再调用回调函数的话可以使用计数器,每个异步操作结束都把计数器数字-1,并判断计数器是否为0.
第67条:绝不要同步地调用异步的回调函数
1.同步的调用异步函数会导致执行顺序被改变,同时异步函数的递归调用是安全的,不会栈溢出,而同步的调用回调函数则不能保证.如果回调函数抛出异常就有可能会中断整个函数执行.
2.可以使用setTomeout(fun, 0)来异步调用fun函数.