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 = '';
View Code

是不同的,下面那种操作会被忽略

 

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
View Code

 

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 }
View Code

 

函数

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
View Code

这也是call和apply的使用场景吧,改变this指向

 

2.直接调用方法的时候即使是在其他函数内部this也是指向全局对象window

1 Foo.method = function() {
2     function test() {
3         // this 将会被设置为全局对象
4     }
5     test();
6 }
View Code

不过一般在这里不会使用this,因为没啥用处

 

3.方法别名的时候this可能也会被改变

1 var test = someObject.methodTest;
2 test();//test内this指向window
View Code

 

闭包和引用

1.

1 for(var i = 0; i < 10; i++) {
2     setTimeout(function() {
3         console.log(i);  
4     }, 1000);
5 }
View Code

上面的代码不会输出数字 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 }
View Code

原理都差不多,就是通过匿名自执行函数,把数据当做函数形参传入函数内部,这样就有了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 };
View Code

这里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 };
View Code

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 }
View Code

上面代码中,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}
View Code

但是因为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{....}
View Code

 

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 }
View Code

坏处就是没有用到原型链,所有东西都不是共享的.

 

作用域与命名空间

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();
View Code

 

1 // 译者注:来自 Nettuts+ 的一段代码,生动的阐述了 JavaScript 中变量声明提升规则
2 var myvar = 'my value';  
3 
4 (function() {  
5     alert(myvar); // undefined  
6     var myvar = 'local value';  
7 })();  
View Code

这段代码没有输出全局变量的值而是输出了undefined,这正是因为变量声明的提升的结果,也就是说后面的声明var myvar被提升到了方法最顶部.

 

3.匿名自执行函数的写法:

 1 (function() {
 2     // 函数创建一个命名空间
 3 
 4     window.foo = function() {
 5         // 对外公开的函数,创建了闭包
 6     };
 7 
 8 })(); // 立即执行此匿名函数
 9 
10 // 另外两种方式
11 +function(){}();
12 (function(){}());
View Code

 

数组

数组便利与属性

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
View Code

 

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
View Code

另外和前面类似,只有显示设置了值,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
View Code

 

类型

相等与比较

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
View Code

 

 

 

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
View Code

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
View Code

实际上我感觉也没啥用处.因为自己写的对象是什么类型肯定清楚,而给别人提供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
View Code

 

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
View Code

 

类型转换

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
View Code

对象比较的时候会先调用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';
View Code

 

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
View Code

 

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);
View Code

在看miniui压缩过的代码的时候我发现有个一元运算符void也可以返回undefined.

1 void 0 === undefined; //true
View Code

 

其它

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();
View Code

 

2.setInterval 的堆调用(??): 

当回调函数的执行被阻塞时,setInterval 仍然会发布更多的回调指令。在很小的定时间隔情况下,这会导致回调函数被堆积起来。

1 function foo(){
2     // 阻塞执行 1 秒
3 }
4 setInterval(foo, 100);
View Code

上面代码中,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();
View Code

这样的话相当阻塞的时候不会计算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();
View Code

由于 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)
View Code

 

posted @ 2016-08-12 15:19  abcwt112  阅读(292)  评论(1编辑  收藏  举报