JavaScript 秘密花园 学习心得
目的
记录一下学习心得,便于以后复习,内容是比较基础的...但是很多内容我还是不知道...
对象
对象使用和属性
1.JavaScript 中所有变量都可以当作对象使用,除了两个例外 null和dundefined。
数字其实也可以当做对象,只是因为.会被当成小数点,所以要这么写(2).toString(),即下一个括号
2.删除属性的唯一方法是使用 delete
操作符
所以delete操作符是有用的,只有用了delete以后in操作才会返回false
原型
1.当原型属性用来创建原型链时,可以把任何类型的值赋给它(prototype)。 然而将原子类型赋给 prototype 的操作将会被忽略。
所以说
1 function Foo() {} Foo.prototype = new String(); 2 function Foo() {} Foo.prototype = '';
是不同的,下面那种操作会被忽略
hasOwnProperty
函数
1.hasOwnProperty
是 JavaScript 中唯一一个处理属性但是不查找原型链的函数。
1 // 修改Object.prototype 2 Object.prototype.bar = 1; 3 var foo = {goo: undefined}; 4 5 foo.bar; // 1 6 'bar' in foo; // true 7 8 foo.hasOwnProperty('bar'); // false 9 foo.hasOwnProperty('goo'); // true
for in
循环
1.和 in
操作符一样,for in
循环同样在查找对象属性时遍历原型链上的所有属性。
如果要找出对象自己定义的属性.可以配合使用hasOwnProperty
1 // foo 变量是上例中的 2 for(var i in foo) { 3 if (foo.hasOwnProperty(i)) { 4 console.log(i); 5 } 6 }
函数
this
的工作原理
1.当在全部范围内使用 this
,它将会指向全局对象。浏览器里是window
函数调用foo();的时候相当于是window.foo(),所以也是window
test.foo();的时候是test
构造方法 new Foo()的时候是指向创建的对象
不过this和java里不一样,java里的时候编译的时候就能知道this指向哪个对象,但是js不行,因为即使在调用的时候也可以改变this.
比如:
1 function foo(a) {this.a = a} 2 var bar = {}; 3 foo.apply(bar, [1]); // bar.a = 1 4 foo.call(bar, 1); // bar.a = 1
这也是call和apply的使用场景吧,改变this指向
2.直接调用方法的时候即使是在其他函数内部this也是指向全局对象window
1 Foo.method = function() { 2 function test() { 3 // this 将会被设置为全局对象 4 } 5 test(); 6 }
不过一般在这里不会使用this,因为没啥用处
3.方法别名的时候this可能也会被改变
1 var test = someObject.methodTest; 2 test();//test内this指向window
闭包和引用
1.
1 for(var i = 0; i < 10; i++) { 2 setTimeout(function() { 3 console.log(i); 4 }, 1000); 5 }
上面的代码不会输出数字 0
到 9
,而是会输出数字 10
十次。
当 console.log
被调用的时候,匿名函数保持对外部变量 i
的引用,此时for
循环已经结束, i
的值被修改成了 10
.
然后timeout时间到了调用匿名函数的时候会访问那个i,所以会输出10次10
解决办法:
1 for(var i = 0; i < 10; i++) { 2 (function(e) { 3 setTimeout(function() { 4 console.log(e); 5 }, 1000); 6 })(i); 7 } 8 9 //或者 10 11 for(var i = 0; i < 10; i++) { 12 setTimeout((function(e) { 13 return function() { 14 console.log(e); 15 } 16 })(i), 1000) 17 }
原理都差不多,就是通过匿名自执行函数,把数据当做函数形参传入函数内部,这样就有了1份拷贝.
arguments
对象
1.使用 call
和 apply
,创建一个快速的解绑定包装器。(??)
1 function Foo() {} 2 3 Foo.prototype.method = function(a, b, c) { 4 console.log(this, a, b, c); 5 }; 6 7 // 创建一个解绑定的 "method" 8 // 输入参数为: this, arg1, arg2...argN 9 Foo.method = function() { 10 11 // 结果: Foo.prototype.method.call(this, arg1, arg2... argN) 12 Function.call.apply(Foo.prototype.method, arguments); 13 };
这里prototype上面的方法很想java里的父类的成员方法,而Foo.method很像java里类的静态方法.
这里之所以能使用Foo.method为对象设值,就是因为this可以动态的改变指向.
但是有几个地方我一直搞不懂.....
Function.call.apply === Function.apply === Foo.method.apply
为何替换Function.call.apply为Function.apply会报错?
而且一般都是这么用的吧:
1 Foo.method = function() { 2 var args = Array.prototype.slice.call(arguments); 3 Foo.prototype.method.apply(args[0], args.slice(1)); 4 };
apply的第一个参数是this的指向..但是调用Function.call.apply的时候却传入了一个方法..而Function.call.apply又等价于Foo.method.apply ....很奇怪..不过Foo.call.apply是显示native code,不知道是不是有什么特殊的地方.
2.有一种情况会显著的影响现代 JavaScript 引擎的性能。这就是使用 arguments.callee
。(??)
1 function foo() { 2 arguments.callee; // do something with this function object 3 arguments.callee.caller; // and the calling function object 4 } 5 6 function bigLoop() { 7 for(var i = 0; i < 100000; i++) { 8 foo(); // Would normally be inlined... 9 } 10 }
上面代码中,foo
不再是一个单纯的内联函数 inlining(译者注:这里指的是解析器可以做内联处理), 因为它需要知道它自己和它的调用者。 这不仅抵消了内联函数带来的性能提升,而且破坏了封装,因此现在函数可能要依赖于特定的上下文。
因此强烈建议大家不要使用 arguments.callee
和它的属性。
arguments.callee不知道有和用处,因为在函数内部应该能够知道自己叫什么名字吧...不过arguments.callee.caller能够在运行期获取调用它的函数,这个似乎有点用处,与java里的反射有点像(只是一点点..)
不知道实际生产中有什么通途..
构造方法
1.显式的 return
表达式将会影响返回结果,但仅限于返回的是一个对象。
1 function Bar() { 2 return 2; 3 } 4 new Bar(); // 返回新创建的对象 Bar {} 5 6 function Test() { 7 this.value = 2; 8 9 return { 10 foo: 1 11 }; 12 } 13 new Test(); // 返回的对象 Object {foo: 1}
但是因为return返回了字面值对象,所以这个对象的原型并不是指向构造方法的prototype. 比如
1 function Test() { 2 this.value = 2; 3 4 return { 5 foo: 1 6 }; 7 } 8 Test.prototype.bar = 2 9 new Test(); //Object {foo: 1} 而不是 Test{....}
2.通过工厂模式创建对象
1 function Foo() { 2 var obj = {}; 3 obj.value = 'blub'; 4 5 var private = 2; 6 obj.someMethod = function(value) { 7 this.value = value; 8 } 9 10 obj.getPrivate = function() { 11 return private; 12 } 13 return obj; 14 }
坏处就是没有用到原型链,所有东西都不是共享的.
作用域与命名空间
1.javascript没有块级作用域,比如说for的{}
2.变量声明提升(Hoisting): JavaScript 会提升变量声明。这意味着 var
表达式和 function
声明都将会被提升到当前作用域的顶部。
1 bar(); 2 var bar = function() {}; 3 var someValue = 42; 4 5 test(); 6 function test(data) { 7 if (false) { 8 goo = 1; 9 10 } else { 11 var goo = 2; 12 } 13 for(var i = 0; i < 100; i++) { 14 var e = data[i]; 15 } 16 } 17 18 //等价于 19 20 // var 表达式被移动到这里 21 var bar, someValue; // 缺省值是 'undefined' 22 23 // 函数声明也会提升 24 function test(data) { 25 var goo, i, e; // 没有块级作用域,这些变量被移动到函数顶部 26 if (false) { 27 goo = 1; 28 29 } else { 30 goo = 2; 31 } 32 for(i = 0; i < 100; i++) { 33 e = data[i]; 34 } 35 } 36 37 bar(); // 出错:TypeError,因为 bar 依然是 'undefined' 38 someValue = 42; // 赋值语句不会被提升规则(hoisting)影响 39 bar = function() {}; 40 41 test();
1 // 译者注:来自 Nettuts+ 的一段代码,生动的阐述了 JavaScript 中变量声明提升规则 2 var myvar = 'my value'; 3 4 (function() { 5 alert(myvar); // undefined 6 var myvar = 'local value'; 7 })();
这段代码没有输出全局变量的值而是输出了undefined,这正是因为变量声明的提升的结果,也就是说后面的声明var myvar被提升到了方法最顶部.
3.匿名自执行函数的写法:
1 (function() { 2 // 函数创建一个命名空间 3 4 window.foo = function() { 5 // 对外公开的函数,创建了闭包 6 }; 7 8 })(); // 立即执行此匿名函数 9 10 // 另外两种方式 11 +function(){}(); 12 (function(){}());
数组
数组便利与属性
1.只有显式设置了数组的值,用in才会返回true
1 var foo = []; 2 5 in foo; // 不管在 Firebug 或者 Chrome 都返回 false 3 foo[5] = undefined; 4 5 in foo; // 不管在 Firebug 或者 Chrome 都返回 true 5 1 in foo; //false
Array
构造函数
1.推荐使用字面值[]来创建数组,而不是构造方法,因为构造方法有点模棱两可.
1 [1, 2, 3]; // 结果: [1, 2, 3] 2 new Array(1, 2, 3); // 结果: [1, 2, 3] 3 4 [3]; // 结果: [3] 5 new Array(3); // 结果: [] 6 new Array('3') // 结果: ['3'] 7 8 // 译者注:因此下面的代码将会使人很迷惑 9 new Array(3, 4, 5); // 结果: [3, 4, 5] 10 new Array(3) // 结果: [],此数组长度为 3
另外和前面类似,只有显示设置了值,in才会返回true
1 var arr = new Array(3);//虽然创建了数组,console里打印显示[undefined X 3],但是其实并未设置值 2 arr[1]; // undefined 3 1 in arr; // false, 数组还没有生成 4 arr[1] = undefined; 5 1 in arr; // true
类型
相等与比较
1. ==比较对象是不方便的,因为可能会发生类型转化,可以使用===代替
1 "" == "0" // false 2 0 == "" // true 3 0 == "0" // true 4 false == "false" // false 5 false == "0" // true 6 false == undefined // false 7 false == null // false 8 null == undefined // true 9 " \t\r\n" == 0 // true 10 11 12 "" === "0" // false 13 0 === "" // false 14 0 === "0" // false 15 false === "false" // false 16 false === "0" // false 17 false === undefined // false 18 false === null // false 19 null === undefined // false 20 " \t\r\n" === 0 // false
typeof
操作符
1.typeof关键字实际中只有1个用途,用来判断对象是不是有定义.而不是用来检查对象的类型,因为几乎不可能从它们那里得到想要的结果。
绝大部分返回结果都是object
1 Value Class Type 2 ------------------------------------- 3 "foo" String string 4 new String("foo") String object 5 1.2 Number number 6 new Number(1.2) Number object 7 true Boolean boolean 8 new Boolean(true) Boolean object 9 new Date() Date object 10 new Error() Error object 11 [1,2,3] Array object 12 new Array(1, 2, 3) Array object 13 new Function("") Function function 14 /abc/g RegExp object (function in Nitro/V8) 15 new RegExp("meow") RegExp object (function in Nitro/V8) 16 {} Object object 17 new Object() Object object
class的值可以通过调用Object.prototype.toString并改变this来获得.
1 function is(type, obj) { 2 var clas = Object.prototype.toString.call(obj).slice(8, -1); 3 return obj !== undefined && obj !== null && clas === type; 4 } 5 6 is('String', 'test'); // true 7 is('String', new String('test')); // true
实际上我感觉也没啥用处.因为自己写的对象是什么类型肯定清楚,而给别人提供API的时候别人传错类型那报错很正常,好像也没啥比要专门去检查..不过这也是一种判断类型的方法把.不过这个class不能用于判断自定义对象,因为会返回Object
instanceof
操作符
1.从instanceof操作符可以看出javascript就是通过原型链来实现继承的
1 function Foo() {} 2 function Bar() {} 3 Bar.prototype = new Foo(); 4 5 new Bar() instanceof Bar; // true 6 new Bar() instanceof Foo; // true 7 8 // 如果仅仅设置 Bar.prototype 为函数 Foo 本身,而不是 Foo 构造函数的一个实例 9 Bar.prototype = Foo; 10 new Bar() instanceof Foo; // false 11 new Bar() instanceof Function; // true
2.instanceof 比较自定义对象还是很方便的,但是比较内置对象比较坑爹
1 new String('foo') instanceof String; // true 2 new String('foo') instanceof Object; // true 3 4 'foo' instanceof String; // false 5 'foo' instanceof Object; // false
类型转换
1.==比较对象会发生类型转化
1 var a = { 2 toString : function(){ 3 return '123'; 4 } 5 } 6 a == '123'; //true 7 8 var a = { 9 toString : function(){ 10 return '123'; 11 }, 12 valueOf : function(){ 13 return '456'; 14 } 15 } 16 a == '123'; //false 17 a == '456'; //true
对象比较的时候会先调用toString方法转化成字符串,但是如果有valueOf方法的话会用valueOf代替toString()方法
2.其他一些转化
1 // 下面的比较结果是:true 2 new Number(10) == 10; // Number.toString() 返回的字符串被再次转换为数字 3 4 10 == '10'; // 字符串被转换为数字 5 10 == '+10 '; // 同上 6 10 == '010'; // 同上 7 isNaN(null) == false; // null 被转换为数字 0 8 // 0 当然不是一个 NaN(译者注:否定之否定) 9 10 // 下面的比较结果是:false 11 10 == 010; 12 10 == '-10';
3.对象在比较的时候可能都转化成一种类型再进行比较会比较清楚明晰
转化成String可以用空字符串加上其他变量: '' + {}
转化成数字可以使用一元运算符加号: + '10'
转化成布尔值可以使用叹号: !!{}
核心
为什么不要使用 eval
1.eval
只在被直接调用并且调用函数就是 eval
本身时,才在当前作用域中执行。
1 var foo = 1; 2 function test() { 3 var foo = 2; 4 eval('foo = 3'); 5 return foo; 6 } 7 test(); // 3 8 foo; // 1 9 10 11 12 var foo = 1; 13 function test() { 14 var foo = 2; 15 var bar = eval; 16 bar('foo = 3'); 17 return foo; 18 } 19 test(); // 2 20 foo; // 3
undefined
和 null
1.undefined的值是可以被被修改的,所以在jquery源码的时候看到会用到下面这样的写法保证undefined就是undefined
1 var undefined = 123; 2 (function(something, foo, undefined) { 3 // 局部作用域里的 undefined 变量重新获得了 `undefined` 值 4 5 })('Hello World', 42); 6 7 8 //或者 9 10 var undefined = 123; 11 (function(something, foo) { 12 var undefined; 13 ... 14 15 })('Hello World', 42);
在看miniui压缩过的代码的时候我发现有个一元运算符void也可以返回undefined.
1 void 0 === undefined; //true
其它
setTimeout
和 setInterval
1.setTimeout方法作为第一个参数的函数将会在全局作用域中执行,因此函数内的 this
将会指向这个全局对象。
1 function Foo() { 2 this.value = 42; 3 this.method = function() { 4 // this 指向全局对象 5 console.log(this.value); // 输出:undefined 6 }; 7 setTimeout(this.method, 500); 8 } 9 new Foo();
2.setInterval
的堆调用(??):
当回调函数的执行被阻塞时,setInterval
仍然会发布更多的回调指令。在很小的定时间隔情况下,这会导致回调函数被堆积起来。
1 function foo(){ 2 // 阻塞执行 1 秒 3 } 4 setInterval(foo, 100);
上面代码中,foo
会执行一次随后被阻塞了一秒钟。
在 foo
被阻塞的时候,setInterval
仍然在组织将来对回调函数的调用。 因此,当第一次 foo
函数调用结束时,已经有 10 次函数调用在等待执行。
但是实际上我测试的时候foo的调用也是并发(setInterval方法导致)的,就是说每次foo被调用之间相隔100毫秒,而不是1.1秒,并不会因为你前面的foo阻塞了1秒,后面的setInterval方法调用foo的时候就会等你1秒.感觉和书上的说法有点出入..也有可能是我理解错书上的意思了...
3.处理可能的阻塞调用
最简单也是最容易控制的方案,是在回调函数内部使用 setTimeout
函数。
1 function foo(){ 2 // 阻塞执行 1 秒 3 setTimeout(foo, 100); 4 } 5 foo();
这样的话相当阻塞的时候不会计算setTimeout时间..
4.隐藏使用 eval
setTimeout
和 setInterval
也接受第一个参数为字符串的情况。 这个特性绝对不要使用,因为它在内部使用了 eval
。
1 function foo() { 2 // 将会被调用 3 } 4 5 function bar() { 6 function foo() { 7 // 不会被调用 8 } 9 setTimeout('foo()', 1000); 10 } 11 bar();
由于 eval
在这种情况下不是被直接调用,因此传递到 setTimeout
的字符串会自全局作用域中执行; 因此,上面的回调函数使用的不是定义在 bar
作用域中的局部变量 foo
。
上面那种情况一般不会发生,但是下面这种情况是可能的.
1 function foo(a, b, c) {} 2 3 // 不要这样做 4 setTimeout('foo(1,2, 3)', 1000) 5 6 // 可以使用匿名函数完成相同功能 7 setTimeout(function() { 8 foo(1, 2, 3); 9 }, 1000)