JavaScript 语言精髓

6,026 阅读16分钟
原文链接: youbookee.com

对象

  • JavaScript 的简单数据类型包括数字、字符串、布尔值、null值和undefined值。
  • 其他所有的值都是对象。数字、字符串和布尔值不是对象,即使它们拥有方法,但是它们是不可变的。
  • JavaScript中的对象是可变的键控集合。
  • 对象是属性的容器,每个属性都拥有名字(包括空字符串在内的任意字符串)和值(除undefined值之外的所有值)
  • JavaScript的对象是无类型的。对于新属性的”名/值”没有限制。

对象字面量

对象字面量可以方便的创建新对象值的表示方法,一个对象字面量就是包围在一对花括号中的零个或多个”名/值”对。

属性名可以是空字符串在内的任何字符串,如果属性名不合法的JavaScript标志符或是保留字,则要用引号括住属性名

var empty_object = {};
var stooge = {
    "first-name": "Ye",
    "last-name": "Zixin",
    name: "Yezixin"
    };

检索

  • 要检索对象里包含的值,可以使用在[]后缀中括住一个字符串表达式的方式。
  • 如果字符串是一个字符串字面量并且是合法的JavaScript标识符且不是保留字,可以用.来代替,可读性更好也更紧凑,否则将会报错。
  • 访问一个不存在的成员变量将放回 undefined。
  • 可以用 ||来设置默认值
  • undefined的属性中取值将会抛出异常,可以使用 && 来避免
console.log(stooge["first-name"]); // Ye
console.log(stooge.first-name); // ReferenceError: name is not defined
console.log(stooge.name); // Yezixin
console.log(stooge["age"]); // undefined
console.log(stooge.sex);  // undefined
console.log(stooge.sex || "man");  // man
console.log(stooge.sex.model) // TypeError: Cannot read property 'model' of undefined
console.log(stooge.sex && stooge.sex.model) // undefined

更新

使用赋值语句来更新,如果没有拥有那个属性名,那么该属性将会被扩充到对象中

stooge.name = "Yannyezixin";
stooge["nickname"] = "Yann";

引用

对象通过引用来传递。永远不会被复制

var x = stooge;
x.nickname = 'shushu';
var nick = stooge.nickname; // x和stooge是指向同一个对象的引用,所以nick为'shushu';
var a = {}, b = {}, c = {}; // a,b,c指向不同空对象的引用
a = b = c = {} // 引用同一个空对象

原型

每个对象都连接到一个原型对象(可以理解父类),并且可以从中继承属性。所有通过对象字面量创建的对象都连接到 Object.prototype, 是JavaScript中的标配对象。

原型连接在更新是不起作用的,也就是对某个对象进行做出改变不会印象到对象的原型。只有在检索值才能起作用,在当前对象中找不到属性时会从它的原型对象中查找,原型对象没有则往上一层,及沿着原型链进行检索。

当添加属性到原型时,该属性会立即对所有基于该原型创建的对象可见

更多关于原型链可看下文

反射

  • 检查对象并确定对象有什么属性,只要检索并验证取得的值即可, typeof操作符可以确定属性的类型
  • 注意原型链中的任何属性都会产生值,包括对象自带的属性或方法。
  • 也可以使用hasOwnProperty方法,如果对象拥有独有的属性,将返回true,该方法不会检查原型链。
typeof stooge.name // 'string'
typeof stooge.name // 'number'
typeof stooge.sex // 'undefined'
if (typeof stooge.name == 'string') 
    console.log('typeof'); // 'typeof'
    typeof stooge.toString // 'function'
stooge.hasOwnProperty('name') // true
stooge.hasOwnProperty('toString') // false

枚举

  • for in 可以用来遍历对象中所有的属性名,包括函数跟原型中的属性,可以使用typeof跟hasOwnProperty来过滤掉
  • for in 遍历的属性名顺序是不确定的, 可以通过创建一个数组,元素为要遍历的属性名,再根据数组中顺序访问对象中的属性,即可得到想要的顺序
for (var name in stooge) {
    if (typeof name !== 'function') {
     ...
    }
    }
properties = [
    'first-name',
    'last-name',
    'name',
    'age'
    ];
for (var i = 0; i < properties.length; i += 1) {
    console.log(stooge[properties[i]])
    }

删除

  • delete运算符,不会触及原型链中的任何对象,不过可能会让原型链中的属性透现出来
stooge.nickname // 'Yann'
delete stooge.nickname
stooge.nickname // 'YannFather'

函数

函数对象

JavaScript中的函数就是对象。函数对象连接到一个Function.prototype(该原型对象本想连接到Object.prototype)。每个函数在创建时会附加两个隐藏的属性:函数的上下文和实现函数的代码。

JavaScript创建一个函数对象时,会给该对象设置一个“调用”属性。当JavaScript调用一个函数时,可理解为调用此函数的“调用”属性。

函数是对象,所以与其他JavaScript数据类型没有什么不同,可以像任何其他值一样被使用。函数可以保存在变量、对象和数组中。也可以当做参数传递给其它函数、也可以再返回函数。还可以拥有方法。

函数字面量

通过函数字面量来创建

//创建名为add的变量,并把函数赋值给该变量
var add = function (a, b) {
    return a + b;
    }

函数字面量包括四个部分

  • 保留字function
  • 函数名,可以被省略,上例子就是函数名省略了。(省略的函数即为匿名函数)
  • 参数
  • 花括号中的主体

函数字面量可以出现在任何地方。函数也可以定义在其他函数中。一个内部函数除了可以访问自己的参数和变量,同时它也能自由访问把它嵌套在其中的父函数的参数和变量。
通过函数字面量创建的函数对象包含一个连到外部上下文的连接。这被称为闭包closure

调用

调用一个函数会暂停当前函数的执行,传递控制权和参数给新函数。除了声明时定义的形参,每个函数还接受两个附加的参数:this 和 arguments。参数 this 的值取决于调用的模式:方法调用模式、函数调用模式、构造器调用模式、apply 调用模式

当实参个数与形参不匹配时,不会导致运行错误。实参多则超出的参数则被忽略,少则缺失的值被替换为undefined。

方法调用模式

当一个函数被保存为对象的一个属性时,我们称它为一个方法。当一个方法被调用时,this 被绑定该对象。

var myObject = {
    value: 0,
    increment: function (inc) {
        this.value += typeof inc === 'number' ? inc : 1;
    }
    };

this 到对象的绑定发生在调用的时候,这个延迟绑定使得函数可以对 this 高度复用。通过 this 可取得它们所属对象的上下文的方法称为公共方法。

函数调用模式

当一个函数并非一个对象的属性时,那么它是被当做一个函数来调用的。

以此模式调用函数时,this 被绑定到全局对象。(语言设计上的一个错误,this 此时应该绑定到外部函数的 this 变量)
解决方案:在方法内定义一个变量并赋值为 this,那么内部函数可以通过访问该变量访问到 this,约定该变量命名为 that。

myObject.double = function () {
    var that = this;
    var helper = function () {
        that.value = add(that.value, that.value);
    };
    helper();
    }

构造器调用模式

如果在一个函数前面带上 new 来调用,那么背地里将会创建一个连接到该函数的 prototype 成员的新对象,同时 this 会被绑定到那个对象上。

一个函数如果创建的目的就是希望结合 new 前缀来调用,那它就被称为构造器函数,约定保存在大写格式命名的变量里。

不推荐使用这种形式的构造器函数

// 创建一个名为Quo的构造器函数,构造一个带有status 属性的对象
var Quo = function (string) {
     this.status = string;
     }
// 给 Quo 的所有实例提供一个名为 get_status 的公共方法。
Quo.prototype.get_status = function () {
    return this.status;
    }
var myQuo = new Quo("xianyu");

Apply调用模式

apply 方法让我们构建一个参数数组传递给调用函数。它也允许我们选择 this 的值。apply 方法接受两个参数,第1个是要绑定给 this 的值,第2个是一个参数数组。

var array = [3, 4];
var sum = add.apply(null, array);
var statusObject = {
    status: 'A-OK'
    };
// 绑定 get_status 方法中 this 到 statusObject 对象。
var status = myQuo.get_status.apply(statusObject);
console.log(status); // A-OK

参数

当函数被调用时,会得到一个 arguments 数组(并不是真正的数组,拥有一个属性 length,但没有任何数组的方法),函数可以通过此参数访问所有它被调用时传递的参数列表,使得编写一个无须指定参数个数的函数成为可能:

var sum = function () {
     var i, sum = 0;
     for (i = 0; i < arguments.length; i += 1) {
         sum += arguments[i];
     }
     return sum;
     };
console.log(sum(1, 2, 3,4 ,5, 6)); //21

异常

JavaScript 提供了一套异常处理机制。异常是干扰程序的正常流程的不寻常的事故。

throw 语句终端函数的执行,抛出一个 exception 对象,包含识别异常类型的 name 属性和一个message 属性。一个 try 语句只会有一个捕获所有异常的 catch 代码块,你必须自己手动来判断异常的类型。

var add = function (a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw {
            name: 'TypeError',
            message: 'add needs numbers'
        }
    }
    return a + b;
    }
var try_it = function () {
    try {
        add("add");
    } catch (e) {
         console.log(e.name + ':' + e.message);
    }
    }
try_it();

扩充类型的功能

JavaScript允许给语言的基本类型扩充功能, 因为 JavaScript 原型继承的动态本质,新的方法立刻被赋予到所有的对象实例上,哪怕对象实例是在方法被增加之前就创建好了

// 下次给对象增加方法的时候可以省去prototype这几个字符
Object.prototype.method = function (name, func) {
    if (!this.prototype[name]) {
        this.prototype[name] = func;
    }
    return this;
    }
// 添加取整方法
Number.method('integer', function () {
     return Math[this < 0 ? 'ceil' : 'floor'](this);
     })
// 移除字符串首尾空白
String.method('trim', function () {
     return this.replace(/^\s+|\s+$/g, '');
     });
a = '  name  ';
console.log(a.trim()); // name

作用域

JavaScript 缺少块级作用域(块级作用域:一对花括号中的代码外部不可见),有函数作用域,函数内任意位置定义的变量在任意位置都可见。(可见我的另一篇博客 你应该知道的JavaScript )

闭包

  • 一个内部函数除了可以访问自己的参数和变量,同时它也能自由访问把它嵌套在其中的父函数的参数和变量。
    通过函数字面量创建的函数对象包含一个连到外部上下文的连接, 可以访问它被创建时所处的上下文环境,这被称为闭包closure

  • 内部函数拥有比它的外部函数更长的生命周期。

var myObject = {
    value: 0,
    increment: function (inc) {
        this.value += typeof inc === 'number' ? inc : 1;
    }
    };
// 如何保护value不会被非法更改?利用闭包
// 这里并没有把一个函数赋值给 myObject。是调用该函数后返回的结果赋值给它。
// 函数返回的对象里的方法可以访问 value。
var myObject = (function () {
    var value = 0;
    return {
        increment: function (inc) {
             value += typeof inc === 'number' ? inc : i;
        },
        getValue: function () {
             return value;
        }
    }
    }());
  • 内部函数能访问外部函数的实际变量而无须复制!
// 事件处理器绑定的是变量 i 本身而不是函数在创建时变量 i 的值, 每次点击 alert 的是 变量 i 最后的值
// 糟糕的例子
var add_the_handlers = function (nodes) {
     var i;
     for (i = 0; i < nodes.length; i += 1) {
         nodes[i].onclick = function (e) {
             alert(i);
         }
     }
     }
// 改进, 事件处理器绑定的是一个匿名函数返回的闭包,闭包绑定了一个seq参数
var add_the_handlers = function (nodes) {
     var i;
     for (i = 0; i < nodes.length; i += 1) {
         nodes[i].onclick = (function (seq) {
             return function (e) {
                  alert(seq);
             }
         }(i));
     }
     }

回调

常常在处理网络请求过程,如果发起的请求是同步请求由于网络的延迟或者阻塞会导致程序阻塞。

较好的做法是异步发起请求,并设置一个回调函数,当请求响应或者任务处理结束后, 会立即调用回调函数。

模块

JavaScript 函数模块是一个提供接口却隐藏状态与实现的函数或对象。可以使用函数和闭包来构造模块。

模块模式利用了函数作用域和闭包来创建被绑定对象与私有成员的关联,它的一般形式是:

  • 一个定义了私有变量和函数的函数;
  • 利用闭包创建可以访问私有变量和函数的特权函数;
  • 最后返回特权函数或者把他们保存到一个可访问的地方

下面是一个例子,构造一个产生唯一字符串的对象

var serial_maker = function () {
    var seq = 0;
    var prefix = '';
    return {
        set_prefix: function (p) {
            prefix = String(p);
        },
        set_seq: function (s) {
            seq = s;
        },
        gensym: function () {
            var result = prefix + seq;
            seq += 1
            return result;
        }
    };
    };
var seqer = serial_maker();
seqer.set_prefix('Q');
seqer.set_seq(10000);
console.log(seqer.gensym()); // Q10000
console.log(seqer.gensym()); // Q10001

级联

有一些方法没有返回值。例如一些设置或修改对象的某个状态却不返回任何职的方法就是一个典型的例子。如果让这些方法返回 this 而不是 undefined,就可以启用级联。在一个级联中,我们可以单独一条语句中一次调用同一个对象的很多方法。

例如 JQuery 中例子:

$('#id').css('color', 'red').css('width', 100).click(onClick);
// 如果没有级联, 我们要这样写
var div = $('#id');
div.css('color', 'red');
div.css('width', 100);
div.click(onClick);

柯里化

柯里化指的是讲一个多参数函数变成参数函数的过程。(其实就是Python中偏函数做的事情)

例如我们有一个函数 add (a, b), 实现a + b。这时我们想固定其中一个参数 a,只传入 b。我们可以利用lodash工具库中的parial方法来实现

var add = function (a, b) {
    return a + b;
    };
addCurry = _.partial(add, 10);
console.log(addCurry(20)); // 30
console.log(addCurry(10)); // 20

当然我们也可以自己实现柯里化方法

Function.method('curry', function () {
    var slice = Array.prototype.slice,
        args = slice.apply(arguments),
        that = this;
    return function () {
         return that.apply(null, args.concat(slice.apply(arguments)));
    };
    });
addCurry = add.curry(10);
console.log(addCurry(20)); // 30
console.log(addCurry(10)); // 20

继承

JavaScript 是一门弱类型语言,从不需要类型转换。对象继承关系变得无关紧要。对于一个对象来说重要的是它能做什么,而不是它从哪里来。

伪类

JavaScript 的原型存在诸多矛盾,某些复杂的语法看起来像那些基于类的语言,掩盖了它的原型机制。
伪类是通过构造器函数产生对象。

定义构造器

// 定义一个构造器,并扩充原型
var Mammal = function (name) {
    this.name = name;
    };
Mammal.prototype.get_name = function () {
    return this.name
    };
Mammal.prototype.says = function () {
    return this.saying || '';
    };
// 构造一个实例
var myMammal = new Mammal('My Mammal');
var name = myMammal.get_name();
console.log(name);

构造伪类来继承Mamml

// 构造伪类来继承Mammal, 通过定义它的构造器函数并替换它的原型
// 为一个 Manmmal 的实例来实现的
var Cat = function (name) {
     this.name = name;
     this.saying = 'meow';
     };
Cat.prototype = new Mammal();
// 扩充新原型对象
Cat.prototype.get_name = function () {
     return this.says() + ' ' + this.name;
     }
myCat = new Cat('Henrietta');
console.log(myCat.says()); // meow
console.log(myCat.get_name()); // meow Henrietta

上述的继承方法过于丑陋,我们定义一个inherits来实现

// 扩充Function原型对象
Object.prototype.method = function (name, func) {
    this.prototype[name] = func;
    return this;
    }
Function.method('inherits', function (Parent) {
    this.prototype = new Parent();
    return this;
    });
var Cat = function (name) {
    this.name = name;
    this.saying = 'meow';
    }
.inherits(Mammal)
.method('get_name', function () {
    return this.says() + ' ' + this.name;
    });
myCat = new Cat('Henrietta');
console.log(myCat.says()); // meow
console. log(myCat.get_name()); // meow Henrietta

观察上面的代码, 你会发现:没有私有环境,所有的属性都是公开的。无法访问 super(父类)的方法。

更糟糕的是,使用构造器函数时,当忘记加上 new 前缀是,那么会导致 this 绑定到全局对象上,并且这种情况没有任何警告。实际上此时就函数调用模式, 而不是构造器调用模式, 这是严重的语言设计错误,为了降低风险,约定所有构造器函数都命名成首字母大写的形式

var MyDemo = function (name) {
    this.name = name;
    };
// 缺少 new 前缀。
var myDemo = MyDemo('Yann');
console.log(myDemo.name); // TypeError
console.log(name); // Yann

“伪类”形式可以给不熟悉 JavaScript 的程序员提供便利,但是隐藏该语言的本质。在 JavaScript 中有着更多的选择去进行代码重用。

对象说明符

有时候构造器要接受一大串参数,但我们要记住参数的顺序非常困难,在这种情况下,最好的做法是在编写构造器让它接受一个简单的对象说明符。

var myObject = maker(f, l, m, c, s);
// 对象说明符
var myObject = maker({
    first: f,
    middle: m,
    last: l,
    state: s,
    city:c
    });

而且这样做可以更好的 JSON 一起工作。关于更多 JSON 请看我的另一个博文深入浅出 JSON

原型

基于原型的继承相比基于类的继承在概念上更为简单:一个新对象可以继承一个旧对象的属性。
一旦有了一个想要的对象,可以利用Object.create方法构造出更多的实例来。

var myMammal = {
    name: 'Herb the Mammal'
    };
var myCat = Object.create(myMammal);
console.log(myCat.name);

函数化

上面的两种继承模式的一个弱点就是没法保护隐私。对象的所有属性都是可见的,我们无法得到私有变量和私有函数。
下面是一个比较好的选择:应用模块模式, 也可以称为函数化模式

函数化模式有很大的灵活性。相比伪类模式不仅带来的工作更少,还让我们得到更好的封装和信息隐藏,以及访问父类方法的能力。

从构造一个生成对象开始,我们以小写字母开头命名,并不需要 new 前缀。该函数包括4个步骤。

  • 创建一个新对象(可以是构造一个对象字面量,或者 new 前缀去调用一个构造器函数,或者通过原型模式去构造一个已存在对象的新实例,或者调用一个会返回其他对象的函数)
  • 有选择的定义私有实例变量和方法
  • 给新对象扩充方法
  • 返回新对象

下面是一个根据上面的步骤而创建的函数化构造器

// spec 对象包含了构造器需要构造一个新实例的所有信息。它的内容可能会被
// 复制到私有变量中,或者被其他函数改变
var mammal = function (spec) {
    var that = {};
    var count = 0;
    var get_name = function () {
        return spec.name;
    }
    that.get_name = get_name;
    var says = function () {
        return spec.saying || '';
    }
    that.says = says;
    var add_count = function () {
        count += 1;
    }
    that.add_count = add_count;
    var get_count = function () {
        return count;
    }
    that.get_count = get_count;
    return that;
    }

下面实现一个另一个构造器来继承 mammal

var cat = function (spec) {
    spec.saying = spec.saying || 'meow';
    var that = mammal(spec);
    that.get_name = function () {
         return that.says() + ' ' + spec.name;
    }
    return that;
    }
var myCat = cat({name: 'Henrietta'});
myCat.add_count();
console.log(myCat.get_name()); // meow Henrietta
console.log(myCat.get_count()); // 1
var myCat1 = cat({name: 'Mam'});
console.log(myCat1.get_name()); // meow Mam
console.log(myCat1.get_count()); // 0

函数化模式还给我们提供了一个处理父类的方法,我们构造一个 super 方法,它取得一个方法名并返回调用那个方法的函数。该函数会调用原来的方法,属性有可能已经改变。

Object.method('super', function (name) {
    var that = this,
        method = that[name];
    return function () {
        return method.apply(that, arguments);
    };
    });
var coolcat = function (spec) {
    var that = cat(spec),
        super_get_name = that.super('get_name');
    get_name = function () {
         return 'super method get_name: ' + super_get_name();
    };
    that.get_name = get_name;
    return that;
    }
var myCoolcat = coolcat({name: 'Yann'});
var name = myCoolcat.get_name();
console.log(name); // super method get_name: meow Yann

数组

数组是一段线性分配的内存,它通过整数计算偏移并访问其中的元素。数组是性能出色的数据结构。不幸的是,JavaScript 没有像此类数组一样的数据结构

作为替代,JavaScript·提供一种拥有一些类数组特性的对象。把数组的下标转变为字符串,用其作为属性。明显比真正的数组慢但使用起来方便。

数组字面量

一个数组字面量是一对方括号中包围零个或多个用逗号分隔的值的表达式。数组字面量允许出现在任何表达式可以出现的地方。

数组内所有元素可以是任意混合类型的值。

var empty = [];
var numbers = [
    'zero', 'one', 'two', 'three', 'four', 'five', 'six'
    ];
empty[1]; // undefined
numbers[1]; // 'one'
empty.length; // 0
numbers.length; // 7

长度

JavaScript 数组的 length 是没有上界的。如果你用大于或等于当前 length 的数字作为下标来存储一个元素,那么 length 值会被增大以容纳新元素,并不会发生数组越界错误。

var myArray = [];
myArray.length; //0
myArray[100000] = true;
myArray.length; // 100001

[]后置下标运算法把它所包含的表达式转换成一个字符串,如果该表达式有 toString 方法便使用该方法的值。这个字符串将会被用作属性名。如果字符串看起来像大于等于这个数组当前的 length 且小于2 ^ 32的正整数,那么数组的 length 将会被重设为新的下标加1。即上例的情况。

你可以直接设置 length 的值。设置更大的 length 不会给数组分配更多的空间。而把 length 设小将会导致所有下标大于等于新length 的属性被删除。

删除

由于JavaScript的数组其实就是对象,所有 delete 运算符可以用来从数组中移除元素

delete numbers[2]; // numbers 为['zero', 'one', undefined, ...]

通过delete 运算符删除的元素会使得该元素仍然会保留最初的属性。可以 splice 来让这个被删除掉的元素移除掉并让后面的元素往前移动。

// 第一个参数为数组中的一个序号(即下标),第二个参数为个数
numbers.splice(2, 1); // numbers 为['zero', 'one', 'three', ...]

因为被删除属性后面的每个属性必须被移除并以一个新的键值重新插入,所以效率不高。

枚举

因为 JavaScript 的数组其实就是对象,所以for in语句可以用来遍历一个数组的所有属性。遗憾的是,for in无法保证属性的顺序,而且还可能从原型链中得到意外属性的问题仍然存在。

不过你可以用常规的 for 语句就可以避免这个问题

var i;
for (i = 0; i < myArray.length; i += 1) {
    console.log(myArray[i]);
    }

识别数组

在 JavaScript 中:当属性名是小而连续的整数时,使用数组。否则使用对象

JavaScript 没有一个好的机制来区别数组和对象,我们可一个自己实现一个函数

console.log(typeof numbers); // object
var is_array = function (value) {
    return Object.prototype.toString.apply(value) === '[object Array]';
    }
var myTypeof = function (value) {
    return is_array(value) ? 'array' : typeof value;
    }
console.log(myTypeof(numbers)); // array
console.log(is_array(numbers)); // true

方法

Array

array.concat(item…)

包含一个 array 的浅复制并把一个或多个参数 item 附加在其后。

var a = ['a', 'b'];
var b = ['c', 'd'];
var c = a.concat(b, [[1, 2]]); //[ 'a', 'b', 'c', 'd', [ 1, 2 ] ]

array.join(separator)

join 方法把一个 array 构造成字符串。默认分隔符 separator 是逗号’,’。使用 join 通常会比直接用+运算法快。

var a = ['a', 'b', 'c'];
var c = a.join(''); // c: abc

array.pop()

移除 array 最后一个元素并返回该元素。如果 array 是 empty,返回 undefined

array.push(item…)

将一个或多个按照参数的顺序添加到数组尾部并返回添加后数组的长度。

var a = ['a', 'b'];
var c = a.push('c'); // a: ['a', 'b', 'c'] c: 3

array.reverse()

反转数组并返回本身

array.shift()

移除数组第一个元素并返回,当数组为空时返回undefined

array.slice(start, end)

对 array 中一段做浅复制,end 默认为 array.lenght。当start大于 length 时,结果为空数组。
当start/end是负数时,则从尾部开始,如-1则是倒数第一个元素,-2是倒数第二个元素

array.sort(comparefn)

sort方法对 array 中的内容进行排序。不能正确地给一组数字排序(这么由于 JavaScript 的默认比较函数把要被排序的元素都视为字符串):

var n = [1, 2, 3, 12];
n.sort(); // n: [1, 12, 2, 3]
// 通过使用自己的比较函数来解决这个问题
n.sort(function (a, b) {
    return a - b;
    }); // n: [1, 2, 3, 12]

array.splice(start, deleteCount, item…)

splice方法从 array 中移除一个或多个元素,并用新 item 替换它们。(如果有额外的参数)结果返回被移除元素的数组

var a = ['a', 'b', 'c'];
var r = a.splice(1, 1, 'bb', 'aa');
console.log(a); // [ 'a', 'bb', 'aa', 'c'  ]
console.log(c); // [ 'a', 'b', 'c', 'd', [ 1, 2 ] ]

array.unshift(item…)

把 item 插入到 array 头部

String

string.chatAt(pos)

charAt 方法返回在 string 中 pos 位置处的字符。如果 pos 小于0或大于等于字符串的长度 string.length,返回空字符串。

var name = 'Curly';
var initial = name.charAt(0); // C

string.charCodeAt(pos)

该方法同 charAt, 返回的是Unicode 编码

var name = 'Curly';
var initial = name.charCodeAt(0); // 67

string.concat(string…)

连接字符串,它很少被使用,因为+运算法更为方便

stirng.indexOf(searchString, position)

该方法在 string 内查找另一个字符串searchString。如果它被找到,返回第一个字符的位置,否则返回-1。可选参数 position 可以指定从哪个位置开始查找。

var text = 'Mississippi';
var p = text.indexOf('ss'); // p => 2
p = text.indexOf('ss', 3); // p =>5
p = text.indexOf('ss', 6); // p => -1

string.lastIndexOf(searchString, position)

同 indexOf 类似,不过是从尾部开始查找

var text = 'Mississippi';
var p = text.indexOf('ss'); // p => 5
p = text.indexOf('ss', 3); // p => 2, 从3 - 0 查找
p = text.indexOf('ss', 6); // p => 5, 从6 - 0查找

string.search(regexp)

同indexOf类似,接受一个正则表达式,返回第一个匹配的首字符位置,自动忽略g 标志

var text = 'and in it he says "And damn fool could"';
var pos = text.search(/["']/); // pos  是 18

string.localeCompare(that)

比较两个字符串, string 比 that 小,结果为负数,相等则0,大于则正数

var m = ['AAA', 'A', 'aa', 'a', 'Aa', 'aaa'];
m.sort(function (a, b) {
    return a.localeCompare(b);
    });
console.log(m); //[ 'a', 'A', 'aa', 'Aa', 'aaa', 'AAA'  ]

string.match(regexp)

match 方法让字符串和一个正则表达式进行匹配。依据 g 标识来决定如何进行匹配。如果没有 g 标识,那么结果与调用regexp.exec(string)结果相同。如果 regexp 带有 g 标识,那么它生成一个包含所有匹配(除捕获分组之外)的数组

var text = '

'

+ 'This is bold<\ b="">!<\ p=""><\ body=""><\ html="">'; var tags = /[^<>]+|<(\ ?)([a-za-z]+)([^<="">]*)>/g; var a, i; a = text.match(tags); for (i = 0; i < a.length; i+=1) { console.log('// [' + i + ']' + a[i]); } // 结果 // [0] // [1] // [2]

// [3]This is // [4] // [5]bold // [6] // [7]! // [8]

// [9] // [10]

string.replace(searchValue, replaceValue)

replace 方法对 string 进行查找和替换操作,并返回一个新的字符串。参数 searchValue 可以是一个字符串或一个正则表达式对象。

如果它是字符串或者正则但是没有带有 g 标志,那么 searchValue 只会在第一次出现的地方被替换

var result = 'mother_in_law'.replace('_', '-'); // mother-in_law
var result = 'mother_in_law'.replace(/_/, '-'); // mother-in_law

如果是正则带 g 标志。会替换所有匹配

var result = 'mother_in_law'.replace(/_/g, '-'); // mother-in-law

replaceValue 可以是一个字符串或函数。如果 replaceValue 是一个字符串,字符$拥有特殊含义

美元符号序列 替换对象
? $
$& 整个匹配的文本
$number 分组捕获的文本
$` 匹配之前的文本
$’ 匹配之后的文本
var oldareacode = /\((\d{3})\)/g;
var p = '(555)666-1212(422)'.replace(oldareacode, '$1-'); // 555-666-1212422-
var p = '(555)666-1212(422)'.replace(oldareacode, '
'); // 666-1212(555)666-1212
var p = '(555)666-1212(422)'.replace(oldareacode, "
"); // 666-1212(422)666-1212

replaceValue 是一个函数,那么每遇到一个匹配,函数就会被调用一次,用该函数返回的字符串会被用做替换文本。传递给这个函数的第一个参数是整个被匹配的文本,2参数是分组1捕获的文本,在下一个参数则是分组2捕获的文本。

String.method('entityify', function () {
    var character = {
        ': '        '>': '>',
        '&': '&s',
        '"': '"'
    };
    return function () {
        return this.replace(/[<>&"]/g, function (c) {
            return character[c];
        });
    };
    }());
console.log("<&>".entityify());

string.slice(start, end)

参考 Array.slice

string.split(separator, limit)

split 方法把这个 string 分割成片段来创建一个字符串数组。可选参数 limit 可以限制被分割的片段数量。separator 参数可以是一个字符串或一个正则表达式

separator 是空字符串

var digits = '01234567';
var a = digits.split('', 5); // ['0', '1', '2', '3', '4']

否则,该方法会在 string 中查找所有 separator 出现的地方。分隔符两边的每个单元文本都会被复制到该数组中。此方法会忽略 g 标志

var text = 'last, first   , middle';
var d = text.split(/\s*,\s*/); // d => ['last', 'first', 'middle']
var e = text.split(/\s*(,)\s*/); // d => ['last', ',', 'first', ',', 'middle']

string.toLocaleLowerCase()

使用本地化的规则把这个 string 中的所有字母转换为小写格式

string.toLocaleUpperCase()

使用本地化的规则把这个 string 中的所有字母转换为大写格式

string.toLowerCase()

把这个 string 中的所有字母转换为小写格式

string.toUpperCase()

把这个 string 中的所有字母转换为大写格式

String.fromCharCode(char…)

从一串 Unicode 编码中返回一个字符换

var a = String.fromCharCode(67, 97, 116); // Cat

毒瘤与糟粕

全局变量

在JavaScript所有的糟糕特性中,最为糟糕的一个就是它对全局变量的依赖。全局变量就是在所有作用域中都可见的变量。在程序中使用过多的全局变量会导致程序的可靠性降低和难以调试。

有三种方式定义全局变量

  • 在任何函数之外放置一个 var 语句

    var foo = value;
  • 在Web浏览器中给全局变量添加一个属性

    window.foo = value;
  • 隐式的全局变量, 直接使用未经声明的变量将会导致该变量成为全局变量(无论语句在什么地方执行), 这会导致查找 bug 非常困难。

    foo = value;

作用域

JavaScript 的语法来源于 C。JavaScript 采用了块语法,但是没有提供块级作用域:代码块中声明的变量在包含此代码块的函数的任何位置都是可见的。

一个比较好的解决方式是在函数的开头部分声明所有变量。

自动插入分号

JavaScript 有一个自动修复机制,试图通过自动插入分号来修正有缺损的程序,但是这同时会掩盖更为严重的错误。

例如下例,JavaScript 会在 return 后面插入分号导致函数结果是 undefined

var a = function () {
    return
    {
        status: true
    };
    }
console.log(a()); // undefined

避免这样的问题的解决方案就是把左括号放置在上一行的尾部而不是下一行的头部

var a = function () {
    return {
        status: true
    };
    }
console.log(a());

保留字

JavaScript 中保留字不能用来命名变量或参数。当保留字被用做对象字面量的键值时,必须用引号括起来且不能用在点表示法中。

Unicode 待补

typeof

typeof 运算符返回一个用于识别其运算数类型的字符串。
但是仍然会一些糟糕的问题存在

  • 无法识别 Array, 即伪数组,因为数组其实也是对象。解决方案
  • 无法识别 null
  • 无法识别 NaN 和数字
    console.log(typeof []); // object
    console.log(typeof null); // null
    // 识别 null 更简单方便的方法
    myvalue = null
    console.log(myvalue === null); // true

typeof 不能辨别出 null 与对象, 也可以这样解决

if (myvalue && typeof myvalue === 'object') {
  // myvalue 是一个对象或数组
  } else {
    return 'null';
    }

parseInt

parseInt 是一个把字符串转换为整数的函数。它在遇到非数字时会停止解析,所以下面结果是一样的

console.log(parseInt("16") === parseInt("16 tons")); // true

但是该函数并不会提醒我们字符串中出现了多余的字符。
如果该字符串第一个字符是0,那么该字符换会基于八进制而不是十进制来求值。在八进制中8,9都不是数字。所以parseInt("08")等于parseInt("09")等于0。这个结果会导致程序解析日期和时间出现问题。
解决方案是加上基数参数

console.log(parseInt("08", 10)); // 8
console.log(parseInt("09", 10)); // 9

+ 运算符

  • 运算符用于加法运算或字符串连接
  • 当其中一个运算数是空字符串,会把另一个运算数转成字符串并返回
  • 当两运算数都为数字则做加法运算,否则两运算数都转换为字符换并连接起来返回。
    > '' + 4;
    '4'
    > '' + 4 + 4;
    '44'
    > '4' + 4;
    '44'

NaN

NaN 是一个特殊的数量值,虽然typeof NaN === 'number' // true, 但NaN表示的不是一个数字。

当时试图转换非数字形式的字符串时就会产生NaN值, 还有 NaN 并不等同于自己

NaN === NaN // false
NaN !== NaN // true

不过 JavaScript 提供了一个 isNaN 函数,可以辨别数组和 NaN

isNaN(NaN); // true
isNaN(0); // false
isNaN('opps'); // true
isNaN('0'); // false

假值

类型
0 Number
NaN(非数字) Number
‘’(空字符串) String
false Boolean
undefined Undefined

这些假值是不可互相转换的

==

== 只有在两个运算数类型一致时才会做出正确的判断。如果两个运算数类型不一致那么将会强制转换值的类型。== 运算符在某些特例上违背了传递性(x == y and y == z 为true, 那么 x == z为 true)

'' == '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

请不要使用==或者!=运算符,请使用 === 和 !==

with 语句

JavaScript 提供了一个 with 语句,本意是想用来它来快捷的访问对象的属性。不幸的是,它的结果有时不可预料,应该尽量避免使用它。

with语句本身严重影响了 JavaScript 处理器的速度,因为它阻断了变量名的词法作用域绑定。

eval

eval 函数传递一个字符串给 JavaScript 编译器,并且执行其结果。它是滥用得最多的 JavaScript 特性。有些人可能会这样写

eval("myValue = myObject."+myKey+";");

但是却不知道下标表示法

myValue = myObject[myKey];

  • 使用 eval 形式的代码更加难以阅读,而且性能会显著降低,因为它需要运行编译器。
  • eval函数还减弱程序的安全性,因为它给被求值的文本赋予了太多的权利。
  • 函数构造器是 eval 的另一种实行,同样也应该避免使用它。

位运算符

JavaScript 的位操作符把它们的数字运算数先转换成整数,接着执行计算,然后再转换回去。JavaScript 的执行环境一般接触不到硬件,所以非常慢,JavaScript 很少被用来执行位操作。

function语句对比function表达式

// function 语句
function foo() {}
// function  表达式
var foo = function () {}

上面两种形式实际上是相同的。function 语句在解释时会发生提升的情况(关于提升可以参考我另一篇博文你应该知道的JavaScript),这会导致一个混乱。因为提升放宽了函数必须先声明后使用的要求。

所以尽量不要使用function语句,请使用function表达式。

类型的包装对象

JavaScript 有一套类型的包装对象,例如:

new Boolean(false)

这会返回一个对象,该对象有一个 valueOf 方法会返回被包装的值。这简直是多此一举。
请避免使用 new Object 和 new Array。请使用对应的字面量{},[]来代替

new运算符

new 运算符创建一个继承于其运算数(构造器函数)的原型的新对象,然后调用该运算数,把新对象绑定给 this。

但是如果忘记使用 new,那么得到则是一个普通的函数调用,并且 this 被绑定到全局对象。这意味着当你的函数尝试去初始化新成员属性时它将会污染全局变量。
并且这没有编译警告,也没有运行时警告。

按照惯例,与 new 结合使用的函数应该以首字母大写的形式命名,并且首字母大写的形式应该只用来命名那些构造函数。

一个更好的对策就是根本不使用 new。