首页 > 代码库 > JavaScript Gerden概述

JavaScript Gerden概述

1.对象

1.1 对象使用和属性

JavaScript中所有变量都是对象,除了null和undefined

1.2 对象作为数据类型

JavaScript对象可以作为哈希表使用,主要用来保存命名的键和值的对应关系

1.3 访问属性

点操作符和中括号操作符

中括号操作符在下面2种情况下依然有效 1.动态设置属性 2.属性名不是一个有效的变量名

1.4 删除属性

删除属性的唯一方法是使用delete操作符;设置属性为undefined或者null并不能真正的删除属性,只是移除了属性和值的关联

1.5 属性名的语法

对象的属性名可以使用字符串或者普通字符(非关键词)声明。

2.原型

2.1 原型继承

注:简单的使用Bar.prototype = Foo.prototype将会导致两个对象共享相同的原型。

function Foo(){

this.value = http://www.mamicode.com/42;

}

Foo.ptototype = {

method : function() {}

};

function Bar() {}

//设置Bar的prototype属性为Foo的实例对象

Bar.prototype = new Foo();

Bar.prototype.foo = "Hello World";

//修正Bar.prototype.constructor为Bar本身

Bar.prototype.constructor = Bar;

注:不要使用Bar.prototype = Foo, 因为这不会执行Foo的原型,而是指向函数Foo。因此原型链将会回溯到Function.prototype而不是Foo.prototype。

2.2 属性查找

查找对象属性时,JavaScript会向上遍历原型链,直到找到给定名称的属性为止。

到查找到达原型链的顶部(Object.prototype),但是仍然没有找到指定的属性,就会返回undefined。

2.3 原型属性

当原型属性用来创建原型链时,可以把任何类型的值赋给它。然而将原子类型赋给prototype的操作将会被忽略。

2.4 性能

如果一个属性在原型链的顶端,则对于查找时间将带来不利影响。

当使用 for - in 循环遍历对象的属性时,原型链上的所有属性都将被访问。

2.5 扩展内置类型的原型

一个错误特性被经常使用,那就是扩展Object.prototype或者其他内置类型的原型对象。

这种技术会破坏封装。

扩展内置类型的唯一理由是为了和新的JavaScript保持一致。比如Arrary.forEach。

3.函数

3.1 函数声明

函数声明会在执行前被解析(hoisted),因此它存在于当前上下文的任意一个地方,即使在函数定义体的上面被调用也是对的。

foo();

function foo(){}

3.2 函数赋值表达式

foo;

foo();

var foo = function(){};

由于var定义了一个声明语句,对变量foo的解析是在代码运行之前,因此foo变量在代码运行时已经被定义过了。

但是由于赋值语句只在运行时执行,因此在相应代码执行之前,foo的值缺省为undefined。

3.3 hasOwnProperty函数

hasOwnProperty是JavaScript中唯一一个处理属性但是不需要查找原型链的方法。

JavaScript不会保护hasOwnProperty被非法占用,因此如果一个对象碰巧存在这个属性,就需要使用外部的hasOwnProperty函数来获取正确的结果。

{}.hasOwnProperty.call(foo, ‘bar‘);

4.this的工作原理

4.1 全局范围

当在全部范围内使用this,它将会指向全局对象。(浏览器中运行的JavaScript脚本,这个全局对象是window)

4.2 函数调用

foo();

此时this指向全局对象。

ES5 注意: 在严格模式下(strict mode),不存在全局变量。这种情况下this将会是undefined。

        4.3 方法调用

this指向调用该方法的对象

4.4 调用构造函数

this指向新创建的对象。

4.5 call和apply

call 或者 apply 方法时,函数内的 this将会被显式设置为函数调用的第一个参数,若为null则指向全局对象

4.6 方法的赋值表达式

将一个方法赋值给一个变量。

var test = someObject.methodTest;

test();

此时,test就像一个普通的函数被调用;因此,函数内的this将不再被指向到someObject对象。(晚绑定)

5.闭包和引用

5.1 模拟私有变量

function Counter(start) {

var count = start;

return {

increment: function() {

count++;

},

get: function() {

return count;

}

}

}

var foo = Counter(4);

foo.increment();

foo.get(); // 5

这里,Counter函数返回两个闭包,函数increment和函数get。这两个函数都维持着对外部作用域Counter的引用,因此总可以访问此作用域内定义的变量count。

5.2 不可以在外部访问私有变量

因为JavaScript中不可以对作用域进行引用或赋值,因此没有办法在外部访问count变量。唯一的途径就是通过那两个闭包。

5.3 循环中的闭包

for(var i = 0; i < 10; i++ ){

setTimeout(function(){

console.log(i);

}, 1000);

}

当console.log被调用的时候,匿名函数保持对外部变量i的引用,此时for循环已经结束,i的值被修改成10。

为了得到想要的结果,需要在每次循环中创建变量i的拷贝。

5.3 避免引用错误

使用自执行匿名函数

for(var i = 0; i < 10; i++){

(function(e){

setTimeout(function(){

console.log(e);

}, 1000);

})(i);

}

6.arguments对象

6.1 基本概念

JavaScript中每个函数内都能访问arguments。它维护着所有传递到这个函数中的参数列表。

通过var关键字定义arguments或者将arguments声明为一个形式参数,都将导致原生的arguments不会被创建。

arguments变量不是一个数组,尽管在语法上有个数组相关的属性length。

6.2 转化为数组

Array.prototype.slice.call(arguments)

6.3 传递参数

function foo(){

bar.apply(null, arguments);

}

function bar(a, b, c){

//some work

}

6.4 性能真相

arguments对象总会被创建,除了两个特殊情况 - 作为局部变量声明和作为形式参数。

使用arguments.callee会显著的影响现代JavaScript引擎的性能。

ES5提示:在严格模式下,arguments.callee会报错TypeError,因为它已经被废除了。

7.构造函数

7.1基本概念

在构造函数内部,this指向新创建的对象Object。这个新创建的对象的prototype被指向到构造函数的prototype。

被调用的函数没有显示的return表达式,则隐式的会返回this对象。

显示的return表达式将会影响返回结果,但仅限于返回的是一个对象。

function Bar(){

return 2;

}

new Bar(); //返回新创建的对象 new Bar().constructor === Bar

function Foo(){

this.value = http://www.mamicode.com/2;

return {

foo : 1

};

}

new Foo(); //返回的对象 {foo:1}

7.2 通过工厂模式创建新对象

function Foo(){

var obj = {};

obj.value = http://www.mamicode.com/"blue";

var privateVal = 2;

obj.someMethod = function(value){

this.value = http://www.mamicode.com/value;

}

obj.getPrivate = function(){

return privateVal;

}

return obj;

}

优点:充分利用私有变量,比起使用构造函数方式不容易出错。

缺点:占用更多的内存,新创建的对象不能共享原型上的方法。

 为了实现继承,工厂方法需要从另外一个对象拷贝所有属性,或者把一个对象作为新创建对象的原型。

 放弃原型链仅仅是因为防止遗漏new带来的问题,这似乎和语言本身的思想想违背。

 

 

8.作用域与命名空间

8.1 函数作用域

JavaScript不支持块级作用域,而仅仅支持函数作用域。

注意: 如果不是在赋值语句中,而是在return表达式或者函数参数中,{...}

将会作为代码段解析, 而不是作为对象的字面语法解析。如果考虑到自动分

号插入,这可能会导致一些不易察觉的错误。如果return对象的左括号和return不在一行上就会出错。

8.2 局部变量

两种方式声明,一个事作为函数参数,另一个是通过var关键字声明。

8.3 变量声明提升

JavaScript会提升变量声明。这意味着var表达式和function声明都将会被提升到当前作用域的顶部。

8.4 名称解析顺序

当访问函数内的foo变量时,JavaScript会按照下面顺序查找:

1.当前作用域内是否有var foo的定义。

2.函数形式参数是否有使用foo名称的。

3.函数自身是否叫做foo。

4.回溯到上一级作用域,然后从 1 重新开始

8.5 命名空间

通过立即执行的匿名函数解决命名冲突的问题。

(function(){

//do some work

})();

9.数组

9.1 遍历

使用for - in循环,会查询对象原型链上的所有属性,因此需要使用hasOwnProperty函数来过滤,比普通for循环慢上好几倍。

9.2 length属性

length属性的getter方式会简单的返回数组的长度,而setter方式会截断数组。

var foo = [1, 2, 3, 4, 5, 6];

foo.length = 3;

foo; // [1, 2, 3]

foo.length = 6;

foo; // [1, 2, 3]

译者注: 在 Firebug 中查看此时 foo 的值是: [1, 2, 3, undefined, 

undefined, undefined]  但是这个结果并不准确,如果你在 Chrome 的控制台

查看 foo 的结果,你会发现是这样的: [1, 2, 3] 因为在 JavaScript 中

undefined 是一个变量,注意是变量不是关键字,因此上面两个结果的意义是

完全不相同的。

9.3 Array构造函数

[1, 2, 3]; // 结果: [1, 2, 3]

new Array(1, 2, 3); // 结果: [1, 2, 3]

[3]; // 结果: [3]

new Array(3); // 结果: [] 

new Array(‘3‘) // 结果: [‘3‘]

由于只有一个参数传递到构造函数中(译者注:指的是 new Array(3); 这种调

用方式),并且这个参数是数字,构造函数会返回一个 length 属性被设置为

此参数的空数组。 需要特别注意的是,此时只有 length 属性被设置,真正的

数组并没有生成。

10.相等于比较

10.1 等于操作符

等于操作符由两个等号组成:==

JavaScript 是弱类型语言,这就意味着,等于操作符会为了比较两个值而进行强制类型转换。

""           ==   "0"           // false

0            ==   ""            // true

0            ==   "0"           // true

false        ==   "false"       // false

false        ==   "0"           // true

false        ==   undefined     // false

false        ==   null          // false

null         ==   undefined     // true

" \t\r\n"    ==   0             // true

此外,强制类型转换也会带来性能消耗,比如一个字符串为了和一个数组进行比较,必须事先被强制转换为数字。

10.2 严格的等于操作符

严格的等于操作符由三个等号组成:===

不想普通的等于操作符,严格的等于操作符不会进行强制类型转换。

""           ===   "0"           // false

0            ===   ""            // false

0            ===   "0"           // false

false        ===   "false"       // false

false        ===   "0"           // false

false        ===   undefined     // false

false        ===   null          // false

null         ===   undefined     // false

" \t\r\n"    ===   0             // false 

10.3 比较对象

虽然 == 和 === 操作符都是等于操作符,但是当其中有一个操作数为对象时,行为就不同了。

{} === {};                   // false

new String(‘foo‘) === ‘foo‘; // false

new Number(10) === 10;       // false

var foo = {};

foo === foo;                 // true

这里等于操作符比较的不是值是否相等,而是是否属于同一个身份;也就是说,

只有对象的同一个实例才被认为是相等的。 这有点像 Python 中的 is 和 C 

中的指针比较。

11.typeof操作符

11.1 JavaScript类型表格

Value               Class      Type

-------------------------------------

"foo"               String     string

new String("foo")   String     object

1.2                 Number     number

new Number(1.2)     Number     object 

true                Boolean    boolean

new Boolean(true)   Boolean    object

new Date()          Date       object

new Error()         Error      object

[1,2,3]             Array      object

new Array(1, 2, 3)  Array      object

new Function("")    Function   function

/abc/g              RegExp     object (function in Nitro/V8)

new RegExp("meow")  RegExp     object (function in Nitro/V8)

{}                  Object     object

new Object()        Object     object

Class一列表示对象的内部属性[[Class]]的值。

为了获取对象的[[Class]],我们需要使用定义在 Object.prototype上的方法toString。

11.2 对象的类定义

JavaScript 标准文档中定义: [[Class]] 的值只可能是下面字符串中的一个:

Arguments, Array, Boolean, Date, Error, Function, JSON, Math, Number, Object, RegExp, String。

JavaScript标准文档只给出了一种获取[[Class]]值的方法,那就是使用Object.prototype.toString。

function is(type, obj) {

var clas = Object.prototype.toString.call(obj).slice(8, -1);

return obj !== undefined && obj !== null && clas === type;

}

is(‘String‘, ‘test‘); // true

is(‘String‘, new String(‘test‘)); // true

ES5 提示: 在 ECMAScript 5 中,为了方便,对 null 和 undefined 调用

Object.prototype.toString方法,其返回值由Object变成了Null和Undefined。

12.instanceof操作符

12.1 基本概念

instanceof操作符用来比较两个操作数的构造函数。只有在比较自定义的对象时才有意义。如果用来比较内置类型,将会和typeof操作符一样用处不大。

12.2 比较自定义对象

function Foo() {}

function Bar() {}

Bar.prototype = new Foo();

new Bar() instanceof Bar; // true

new Bar() instanceof Foo; // true

// 如果仅仅设置Bar.prototype为函数Foo本身,而不是Foo构造函数的一个实例

Bar.prototype = Foo;

new Bar() instanceof Foo; // false

12.3 instanceof比较内置类型

new String(‘foo‘) instanceof String; // true

new String(‘foo‘) instanceof Object; // true

‘foo‘ instanceof String; // false

‘foo‘ instanceof Object; // false

13.类型转换

13.1 内置类型的构造函数

new Number(10) === 10;     // False, 对象与数字的比较

Number(10) === 10;         // True, 数字与数字的比较

new Number(10) + 0 === 10; // True, 由于隐式的类型转换

使用内置类型Number作为构造函数将会创建一个新的Number对象,而在不使用new关键字的Number函数更像是一个数字转换器.

最好的选择是把要比较的值显式的转换为三种可能的类型之一。

13.2 转换为字符串

‘‘ + 10 === ‘10‘; // true

将一个值加上空字符串可以轻松转换为字符串类型。

13.3 转换为数字

+‘10‘ === 10; // true

使用一元的加号操作符,可以把字符串转换为数字。

其他字符串转换为数字的常用方法:

+‘010‘ === 10

Number(‘010‘) === 10

parseInt(‘010‘, 10) === 10  // 用来转换为整数

+‘010.2‘ === 10.2

Number(‘010.2‘) === 10.2

parseInt(‘010.2‘, 10) === 10

13.4 转换为布尔型

通过使用否操作符两次,可以把一个值转换为布尔型。

!!‘foo‘;   // true

!!‘‘;      // false

!!‘0‘;     // true

!!‘1‘;     // true

!!‘-1‘     // true

!!{};      // true

!!true;    // true

14.eval

14.1 为什么不要使用eval

在任何情况下我们都应该避免使用eval函数。99.9%使用eval的场景都有不使用eval的解决方案。

14.2 安全问题

eval 也存在安全问题,因为它会执行任意传给它的代码,在代码字符串未知或者是来自一个不信任的源时,绝对不要使用eval函数。

15.undefined 和 null

15.1 undefined的值

undefined是一个值为undefined的类型。

下面的情况会返回 undefined 值:

        ?  访问未修改的全局变量 undefined。

        ?  由于没有定义 return 表达式的函数隐式返回。

        ?  return 表达式没有显式的返回任何内容。

        ?  访问不存在的属性。

        ?  函数参数没有被显式的传递值。

        ?  任何被设置为 undefined 值的变量。

15.2 处理undefined值的改变

由于全局变量undefined只是保存了undefined类型实际值的副本,因此对它赋新值不会改变类型undefined的值。


为了避免可能对 undefined 值的改变,一个常用的技巧是使用一个传递到匿名包装器的额外参数。在调用时,这个参数不会获取任何值。

var undefined = 123;

(function(something, foo, undefined) {

// 局部作用域里的 undefined 变量重新获得了 `undefined` 值

})(‘Hello World‘, 42);

另外一种达到相同目的方法是在函数内使用变量声明。

var undefined = 123;

(function(something, foo) {

var undefined;

...

})(‘Hello World‘, 42);

15.3 使用 null

JavaScript 中的 undefined 的使用场景类似于其它语言中的 null,实际上JavaScript 中的 null 是另外一种数据类型。

它在 JavaScript 内部有一些使用场景(比如声明原型链的终结Foo.prototype = null ),但是大多数情况下都可以使用 undefined 来代替。


16.setTimeout 和 setInterval

16.1 定时器

基于JavaScript引擎的计时策略,以及本质上的单线程运行方式,所以其它代码的运行可能会阻塞此线程。因此没法确保函数会在setTimeout指定的时刻被调用。

作为第一个参数的函数将会在全局作用域中执行,因此函数内的 this 将会指向这个全局对象。

function Foo() {

this.value = http://www.mamicode.com/42;

this.method = function() {

// this 指向全局对象

console.log(this.value); // 输出:undefined

};

setTimeout(this.method, 500);

}

new Foo();

注意: setTimeout的第一个参数是函数对象,一个常犯的错误是这样的setTimeout(foo(), 1000) , 这里回调函数是foo的返回值,而不是foo本身。

大部分情况下,这是一个潜在的错误,因为如果函数返回undefined,setTimeout也不会报错。

16.2 setInterval的堆调用

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

function foo(){

// 阻塞执行 1 秒

}

setInterval(foo, 100);

上面代码中,foo 会执行一次随后被阻塞了一分钟。

在 foo 被阻塞的时候,setInterval 仍然在组织将来对回调函数的调用。 因此,当第一次 foo 函数调用结束时,已经有10次函数调用在等待执行。

16.3 处理可能的阻塞调用

最简单也是最容易控制的方案,是在回调函数内部使用 setTimeout 函数

function foo(){

// 阻塞执行 1 秒

setTimeout(foo, 100);

}

foo();

16.4 手工清空定时器

可以通过将定时时产生的 ID 标识传递给clearTimeout或者clearInterval函数来清除定时。

16.5 隐藏使用 eval

setTimeout 和 setInterval 也接受第一个参数为字符串的情况。 这个特性绝对不要使用,因为它在内部使用了 eval。

详情可见:??J??????avaScript秘密花园?