JavaScript 的秘密花园

来源:互联网 发布:淘宝好评语100字 编辑:程序博客网 时间:2024/05/01 08:18

1、对象:

JS中所有的变量都是对象,除了两个例外 null和undefined .


一个常见的误解是数字的字面值(literal)不是对象。这是因为 JavaScript 解析器的一个错误, 它试图将点操作符解析为浮点数字面值的一部分。

ex: 2.toString()    //synaxError  原因是以为.是浮点数的点

转化方法:

2..toString(); // 第二个点号可以正常解析2 .toString(); // 注意点号前面的空格(2).toString(); // 2先被计算

有两种方式可以访问对象的属性,点操作符或者中括号操作符

var  s = { name : "h"}

s.name 和s["name"]均可以访问

但是中括号可以用于动态的访问,属性名中含有空格,属性名是JS关键字之类


删除属性唯一的方法是使用delete,设置属性为Null或者undefined并不能真正删除属性。仅仅是移除了属性和值的关联

2、Javascript原型链:

function Foo() {    this.value = 42;}Foo.prototype = {    method: function() {}};function Bar() {}// 设置Bar的prototype属性为Foo的实例对象Bar.prototype = new Foo();Bar.prototype.foo = 'Hello World';// 修正Bar.prototype.constructor为Bar本身Bar.prototype.constructor = Bar;var test = new Bar() // 创建Bar的一个新实例// 原型链test [Bar的实例]    Bar.prototype [Foo的实例]         { foo: 'Hello World' }        Foo.prototype            {method: ...};            Object.prototype                {toString: ... /* etc. */};

上面的例子中,test 对象从 Bar.prototype 和 Foo.prototype 继承下来;因此, 它能访问 Foo 的原型方法 method。同时,它也能够访问那个定义在原型上的 Foo 实例属性 value。 需要注意的是 new Bar() 不会创造出一个新的 Foo 实例,而是 重复使用它原型上的那个实例;因此,所有的 Bar 实例都会共享相同的 value 属性。


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


如果一个属性在原型链的上端,则对于查找时间将带来不利影响。特别的,试图获取一个不存在的属性将会遍历整个原型链。

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


所以:

在写复杂的 JavaScript 应用之前,充分理解原型链继承的工作方式是每个 JavaScript 程序员必修的功课。 要提防原型链过长带来的性能问题,并知道如何通过缩短原型链来提高性能。 更进一步,绝对不要扩展内置类型的原型,除非是为了和新的 JavaScript 引擎兼容。


for in循环

和 in 操作符一样,for in 循环同样在查找对象属性时遍历原型链上的所有属性。

// 修改 Object.prototypeObject.prototype.bar = 1;var foo = {moo: 2};for(var i in foo) {    console.log(i); // 输出两个属性:bar 和 moo}

hasownprotype:

// foo 变量是上例中的for(var i in foo) {    if (foo.hasOwnProperty(i)) {        console.log(i);    }}

函数的声明和表达式:

函数是JavaScript中的一等对象,这意味着可以把函数像其它值一样传递。 一个常见的用法是把匿名函数作为回调函数传递到异步函数中。

var s = function () {}

上述表达的意思是,将一个匿名函数赋值给变量s

foo; // 'undefined'foo(); // 出错:TypeErrorvar foo = function() {};

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

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


还有一种特别写法,将命名函数赋值给一个变量:

demo:

var foo = function bar() {    bar(); // 正常运行}bar(); // 出错:ReferenceError


This的工作原理:

Javascript有一套完全不同于其他语言的this处理机制,五种不同情况下,this的指向个不相同.

1、全局对象:全部范围内使用this,会指向全局对象

2、函数调用:this也会指向全局对象

3、方法调用:this会指向被调用方

demo:

test.foo(); //this指向test

4、调用构造函数:

new f();如果函数倾向于和 new 关键词一块使用,则我们称这个函数是 构造函数。 在函数内部,this 指向新创建的对象。

5、显示的设置this:

function foo(a, b, c) {}var bar = {};foo.apply(bar, [1, 2, 3]); // 数组将会被扩展,如下所示foo.call(bar, 1, 2, 3); // 传递到foo的参数是:a = 1, b = 2, c = 3

当使用 Function.prototype 上的 call 或者 apply 方法时,函数内的 this将会被 显式设置为函数调用的第一个参数。

因此函数调用的规则在上例中已经不适用了,在foo 函数内 this 被设置成了bar


闭包是 JavaScript 一个非常重要的特性,这意味着当前作用域总是能够访问外部作用域中的变量。 因为 函数 是 JavaScript 中唯一拥有自身作用域的结构,因此闭包的创建依赖于函数。

function Counter(start) {    var count = start;    return {        increment: function() {            count++;        },        get: function() {            return count;        }    }}var foo = Counter(4);foo.increment();foo.get(); // 5

循环中的闭包

一个常见的错误出现在循环中使用闭包,假设我们需要在每次循环中调用循环序号

上面的代码不会输出数字 0 到 9,而是会输出数字 10 十次。

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

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


为了正确的获得循环序号,最好使用 匿名包裹器(译者注:其实就是我们通常说的自执行匿名函数)。

for(var i = 0; i < 10; i++) {    (function(e) {        setTimeout(function() {            console.log(e);          }, 1000);    })(i);}


JavaScript 中的构造函数和其它语言中的构造函数是不同的。 通过 new 关键字方式调用的函数都被认为是构造函数。

在构造函数内部 - 也就是被调用的函数内 - this 指向新创建的对象 Object。 这个新创建的对象的 prototype 被指向到构造函数的 prototype


命名空间

只有一个全局作用域导致的常见错误是命名冲突。在 JavaScript中,这可以通过 匿名包装器 轻松解决

(function() {    // 函数创建一个命名空间    window.foo = function() {        // 对外公开的函数,创建了闭包    };})(); // 立即执行此匿名函数

匿名函数被认为是 表达式;因此为了可调用性,它们首先会被执行。

( // 小括号内的函数首先被执行function() {}) // 并且返回函数对象() // 调用上面的执行结果,也就是函数对象

结论

推荐使用匿名包装器译者注:也就是自执行的匿名函数)来创建命名空间。这样不仅可以防止命名冲突, 而且有利于程序的模块化。

另外,使用全局变量被认为是不好的习惯。这样的代码倾向于产生错误和带来高的维护成本。



虽然在 JavaScript 中数组是对象,但是没有好的理由去使用 for in 循环 遍历数组。 相反,有一些好的理由不去使用 for in 遍历数组。

注意: JavaScript 中数组不是关联数组。 JavaScript 中只有对象 来管理键值的对应关系。但是关联数组是保持顺序的,而对象不是

由于 for in 循环会枚举原型链上的所有属性,唯一过滤这些属性的方式是使用 hasOwnProperty 函数, 因此会比普通的 for 循环慢上好多倍。

遍历

为了达到遍历数组的最佳性能,推荐使用经典的 for 循环。

数组的length属性

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

var arr = [1, 2, 3, 4, 5, 6];arr.length = 3;console.log(arr);arr.length = 6;console.log(arr);

[123uniquefunctioneachfunction]

[123uniquefunctioneachfunction]
arr
[1,2,3,undefined × 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, 4, 5); // 结果: [3, 4, 5] new Array(3) // 结果: [],此数组长度为 3

JS是弱语言,因此,在进行==比较的时候

会比较两边的操作符而自动进行类型转换


setTimeout只会调用一次。setInterval则每隔X秒会调用一次。

clearTimeout可以清空定时器

settimeout设置时候,建议传入匿名函数


绝对不要使用字符串作为 setTimeout 或者 setInterval 的第一个参数, 这么写的代码明显质量很差。当需要向回调函数传递参数时,可以创建一个匿名函数,在函数内执行真实的回调函数。

另外,应该避免使用 setInterval,因为它的定时执行不会被 JavaScript 阻塞。




0 0
原创粉丝点击