js语言精粹

121 阅读8分钟
数据类型:
数字, 字符串, 布尔值, null, undefined, 对象[数组, 函数, 正则, 对象]

一. 对象篇

1.检索

使用[] 或 . 的访问属性

stooge['first-name'] 或者 stooge.first-name

1)如果访问一个不存在的属性,那么返回undefined,可以用||填充默认值

2)访问undefined的属性会抛出TypeError异常,可以使用&&屏蔽错误(或者if判断)

3)操作undefined的方法()会抛出TypeError异常,可以使用&&屏蔽错误(或者if判断)

如:stooge.secondname  //undefined
var price = flight.price || "100"
var arrName = res.arrName || []
alert(alert(flight.price));     // undefined
alert(flight.price.cheap);      // 抛出异常

// 解决方法
alert(flight.price && flight.price.cheap);
2.原型

每个对象都连接一个原型,可以继承原型中的属性

所有的通过对象字面量创建的对象都连接到Object.prototype这个标准的JS原型

当你创建对象时,可以选择某个对象作为它自己的原型

if (! typeof Object.beget != "function") {
    Object.beget = function (o) {
        var F = function () {};
        F.prototype = o;
        return new F();
    }
}
var another_stooge = Object.beget(stooge);
alert(another_stooge['first-name']);    // zhang
alert(another_stooge['last-name']);     // fei

委托的过程:查找对象的属性 –> 查找原型对象的属性 –> 查找原型的属性 …–> Object.prototype中去

例如查找another_stooge的’first-name’的属性值,首先从该对象开始,然后去原型对象stooge中,找到了

如果没有找到,继续到原型中找,一直找到Object.prototype中,如果没有,则返回undefined

  • 原型连接只有在检索值得时候才会被用到.
  • 原型连接在更新时是不起作用,改变某个对象时,不会触及该对象的原型.
3.反射

检测对象有什么类型的属性很容易,用typeof

typeof stooge['first-name'];     // string
typeof flight.number;            // number

原型链中的任意一个属性都会返回一个值

typeof flight.constructor;       // function 
typeof flight.toString;          // function 
typeof Object.beget;             // function 

function Person () {
    this.name = "zhang";
}
Person.prototype.getName = function () {
    return this.name;
}
var p = new Person();
alert(typeof p.name);       // string
alert(typeof p.getName);    // function

例: 处理并丢弃值为函数的属性

方法一:处理类型为函数的属性

if (typeof obj[item] != function){
    ...
}

方法二:使用hasOwnProperty(), 如果是对象拥有的独有属性,它将返回true.否则返回false.它不会检查原型链.

<script>
var flight = {
    "number":10,
    "age":300
}
console.log(flight.hasOwnProperty('number'));           //true
console.log(flight.hasOwnProperty('constructor'));      //false
</script>
var p = new Person();
p.name = "li";
alert(p.hasOwnProperty("name"));    // true
4.枚举

for in语句遍历对象的所有属性,包含函数或者原型中的你可能不关心的属性

for(var name in flight) {
    if(typeof flight[name] !== "function") {
        document.writeln(name + " = " + flight[name]);
    }
}

例子:把一个对象转化为数组

var newarr = [];
for(name in classStooge){
    newarr.push(classStooge[name]);
}
5.删除

delete删除对象中的属性,可能会让来自原型链的属性显示出来

delete another_stooge.first-name;
6.减少全局污染

将你的应用程序全部打包到一个唯一的全局变量中

var MYAPP = {};
MYAPP.stooge = {
    "last-name" : "fei"
}
MYAPP.flight = {
    airline : "BeiJing",
    arrival : {
        city : "ShangHai"
    }
};


二. 函数篇

1.函数对象

函数: 用于代码复用,信息隐藏和组合调用.

  1. 原型
  • 字面量对象,连接到Object.prototype上
  • 函数对象,连接到Function.prototype上, => Object.prototype上

2)每个函数对象在创建时,隐藏两个属性,

  • 即函数的上下文
  • 实现函数行为的代码

3)函数就是对象,可以像值一样使用

  • 可以保存在变量,对象,数组中
  • 可以做参数传递,可以返回函数
  • 可以拥有方法
  • 不同之处.在于函数可以被调用.

每个函数在创建时都会携带一个prototype属性,它的值拥有一个constructor属性且值为该对象的函数

2.调用

4种调用模式,调用的函数除了接受实参外,还将接受this和arguments两个参数

1)方法调用模式

this --> 所属的对象

// 当函数被保存为一个对象的属性,称为方法调用

var myObject = {
    value : 0,
    increment : function (inc) {
        alert(this);    // [object Object],即myObject对象
        this.value += typeof inc === "number" ? inc : 1;
    }
}
myObject.increment();

2)函数调用模式

this --> 指向全局对象

// 函数并非一个对象的属性时,此时是函数调用

var value = 1;
myObject.one = function () {
    var helper = function () {
        alert(this.value);    // 1,这个匿名函数中的this统统指向window
    }
    helper();
}
myObject.one();

解决方法: var that = this; 即把执行环境保存下来.
此时内部函数可以通过that变量,访问到this.
that --> 指向所属的对象

3)构造器调用模式

this --> 指向新对象

// 如果一个函数在其前面加new调用,那么将创建一个连接到本函数的prototype成员的新对象

var Que = function (string) {
    this.name = string;
}
Que.prototype.getName = function () {
    return this.name;
}
var q = new Que("no");

4)Apply调用模式

它接受两个参数:第一个是将被绑定this的值,第二个是一个数组

// apply函数构建一个数组并去其去调用函数

var array = [3, 4];
alert(add.apply(null, array));  // 7, this没有被强制绑定值,即为window
var nameObject = {
    name : "zhangfei"
}
// 将this绑定到nameObject中,并且调用Que.prototype.getName方法
alert(Que.prototype.getName.apply(nameObject)); // zhangfei
3.参数

默认arguments参数,它是传入实参的集合

var addNoParam = function () {
    var res = 0
    for (var i = 0, len = arguments.length; i < len; i++) {
        res += arguments[i];
    }
    return res;
}
alert(addNoParam(1, 3, 4, 5))
4.返回 return

函数总是会有一个返回值

  • 没有指定,则返回undefined
  • 当函数是构造器函数模式调用时,返回this,并非一个对象
5.异常

异常时干扰程序的正常流程的非正常事故,当事故被查出时,会抛出一个异常

var addHasError = function (a, b) {
    if (typeof a !== "number" || typeof b !== "number") {
        throw {
            name : "TypeError",
            message : "add needs number"
        }
    }
    return a + b;
}
var try_it = function () {
    try {
        addHasError("two", "one");
    } catch(ex) {
        TypeError : add needs number 
        document.writeln(ex.name + " : " + ex.message);
    }
}
try_it();
6.扩充类型的功能

JS允许给基本类型添加方法,这样所有的函数都可以调用

例1:给对象加方法

Function.prototype.method = function (name, fun) {
    // 如果添加的方法不存在,才可以添加
    if(!this.prototype[name]) {
        this.prototype[name] = fun;
        return this;
    }
}

例2: 添加一个取整的方法

Number.method("integer", function () {
    alert(this);    // this代表调用给方法的数字,这里是- 10 / 3
    return Math[this < 0 ? 'ceil' : 'floor'](this); 
});
alert((- 10 / 3).integer());    // -3

例3:添加一个去除字符串末端空白的方法

String.method("trim", function () {
    return this.replace(/^\s+|\s+$/g, '');
});
alert("  zhang shan  ".trim());
8.递归

递归函数会直接或间接的调用自身的一种函数

一个递归函数调用自身去解决它的子问题

例1:汉诺塔

<div>
    <ul name="good">
        <p></p>
        <span name="good"></span>
    </ul>
    <ul>

    </ul>
</div>
<h1>
    <h3 name="good"></h3>
</h1>


var hanoi = function (disc, src, aux, dst) {
    if (disc > 0) {
        arguments.callee(disc - 1, src, dst, aux);
        document.writeln("Move disc " + disc + " form " + src + " to " + dst + "<br/>");
        arguments.callee(disc - 1, aux, src, dst);
    }
}
hanoi(3, "盘子A", "盘子B", "盘子C");

例2:调用walk_the_DOM函数,它从给定的节点开始,依照html的顺序访问该树的每个节点

var walk_the_DOM = function walk (node, func) {
    func(node);
    node = node.firstChild;
    while(node) {
        walk(node, func);
        node = node.nextSibling;
    }
}

9.作用域

作用域控制着参数和变量的可见性和生命周期

jS不支持块级作用域,即if、for、while等花括号中的变量,可以在外部访问

10.闭包

闭包的好处在于内部函数可以访问定义它的外部函数的参数和变量

且闭包的生命周期比它的外部函数要长

我们是将调用函数的结果,即返回的对象赋值给myObject对象

var myObject = function () {
    var value = 0;
    return {
        increment : function (inc) {
            value = typeof inc === "number" ? inc : 1;
        },
        getValue : function () {
            return value;
        }
    };
}();
myObject.increment(10);
alert(myObject.getValue());     // 10
// 返回的对象访问私有属性
// 并不需要new实例化

var quo = function (status) {
    return {
        getStatus : function () {
            return status;
        }
    }
}
// var q = quo("zhang");
// alert(q.getStatus());        // zhang
var fade = function (node) {
    var level = 1;
    var step = function () {
        var hex = level.toString(16);
        node.style.background = "#FFFF" + hex + hex;
        if (level < 16) {
            level++;
            setTimeout(step, 100);
        }
    }
    setTimeout(step, 100);
}
fade(document.body);

内部函数能够访问外部函数的变量并非复制

糟糕的例子

<div>
    <ul name="good">
        <p>111</p>
        <span name="good">222</span>
    </ul>
    <ul>
    3333
    </ul>
</div>
<h1>

var ul = document.querySelectorAll("ul");
var add = function (nodes) {
    for(var i = 0; i < nodes.length; i++) {
        nodes[i].onclick = function() {
            alert(i);
        }
    }
};
add(ul);    // 你会发现,无论你点击哪一个ul都只会返回2
// 原因是闭包访问外部的变量是真实的并非复制的
// 像本例子中的i,它是不断发生变化的

// 改造后
var addClosed = function (nodes) {
    for(var i = 0; i < nodes.length; i++) {
        nodes[i].onclick = function(pos) {
            return function () {
                alert(pos);
            }
        }(i);
    }
};
// addClosed(ul);   // 这样就ok了

11.回调

函数让不连续的事件处理变得容易起来

var request = prepare_request();    // 请求预处理
send_request_asyc(request, function (response) {
    display(response);
});
// 我们传递函数作为参数发送给send_request_asyc,当服务器响应后进行处理

同步发送的,如果服务器响应过慢,那么会出现客户端假死的现象

因此我们需要使用异步的方式,完成客户端与服务器端的交互

12.模块

模块是一个提供了接口但是隐藏了状态与实现函数的函数或者对象

我们可以用函数和闭包来构成模块

模块可以使我们完全摒弃全局变量的作用

Function.prototype.method = function (name, fun) {
    // 如果添加的方法不存在,才可以添加
    if(!this.prototype[name]) {
        this.prototype[name] = fun;
        return this;
    }
}
String.method("deentityify", function () {
    // 私有属性对象
    var entity = {
        quot : '"',
        lt : "<", 
        gt : ">"
    }
    // 创建可以返回且可以访问entity私有变量的特权方法
    return function () {
        // 匹配以&开始,以;结束,且中间的字符不能为&或者;开头的字符
        // this代表调用此函数的字符串
        return this.replace(/&([^&;]+);/g, function (a,  b) {
            var r = entity[b];
            // alert(a);    // &lt; &quot; &gt; &ddd;
            // alert(alert(b));// lt undefined quot undefined gt undefined ddd
            return typeof r === "string" ? r : a;
        });
    }
}());
document.writeln("&lt;&quot;&gt;&ddd;".deentityify());  // <">&ddd; 

模块的一般形式: 一个定义了私有变量或者函数的函数, 利用闭包创建可以访问到私有变量或者函数的特权方法, 最后返回这个特权方法,或者将其存放到一个可以访问的地方。 接下来我们要构建一个可以生成序列化的模块

var serial_maker = function () {
    // 定义两个私有的变量,前缀和序列号
    var prex = '';
    var seq = 0;
    // 返回一个可以访问私有变量的对象
    return {
        setPrex : function (p) {
            prex = String(p);
        },
        setSeq : function (s) {
            seq = Number(s);
        },
        gensyn : function () {
            var result = prex + seq;
            seq++;
            return result;
        }
    };
};
var maker = serial_maker();
maker.setPrex("U");
maker.setSeq(200);
alert(maker.gensyn());  // U200
maker.setPrex("Q");
alert(maker.gensyn());  // Q200
13.级联

一些方法,只是修改和设置的操作,没有返回值,默认为undefined,

我们可以让他们返回this,这样这个方法就开启了级联,即可以依次调用同一个对象的许多方法

getElement("div").move(200, 200).setBackground("#ccc").....;    //这个div可以一次性完成许多工作
14.套用--柯里化

套用允许我们将函数与传递给它的参数结合产生新函数

var add = function (a, b) {
    return a + b;
}
Function.method("curry", function () {
    var slice = Array.prototype.slice;
    // 取得外部参数,即下面实例的10
    var args = slice.apply(arguments);
    alert(args);    // 10
    var that = this;
    alert(this);    // 调用curry的函数,即a下面实例的add()方法
    return function () {
        alert(this);    // [object Window]
        return that.apply(null, args.concat(slice.apply(arguments)));
    }
});
var add1 = add.curry(10);
alert(add1(100));   // 110
15.记忆

函数可以让对象记住先前操作的结果,这样就避免了无谓的计算

fibonacci函数被调用了453次,我们调用了11次,

var fibonacci = function (n) {
    return n < 2 ? n : arguments.callee(n - 1) + arguments.callee(n - 2);
}
var start = Date.now();
for(var i = 0; i <= 30; i++) {
    if (i == 30) {
        document.writeln("// " + i + " : " + fibonacci(i) + "<br/>");
    }
}
var end = Date.now();
document.writeln("原始使用时间: " + (end - start) + "<br/>"); // 用时1360

var memoizer = function (memo, fundamental) {
    var shell = function (n) {
        var result = memo[n];
        if (typeof result !== 'number') {
            result = fundamental(shell, n);
            memo[n] = result;
        }
        return result;
    }
    return shell;
};
var fibonacci = memoizer([0, 1], function (shell, n) {
    return shell(n - 1) + shell(n - 2);
});
alert(fibonacci(10));       // 55
alert(fibonacci(100));      // 354224848179262000000
var factorial = memoizer([1, 1], function (shell, n) {
    return shell(n - 1) * n;
});
alert(factorial(5));    // 120
alert(factorial(50));   // 3.0414093201713376e+64

三. 继承篇

1.伪类 构造器继承
var Person = function (name) {
    this.name = name;
}
Person.prototype.getName = function () {
    return this.name;
}
Person.prototype.getSaying = function () {
    return this.saying || "";
}
var p = new Person("zhang");
alert(p.getName()); // zhang
var Student = function (name) {
    this.name = name;
    this.saying = "student saying";
}
Student.prototype = new Person();
Student.prototype.purr = function (n) {
    var i, s = '';
    for(i = 0; i < n; i++) {
        if(s) {
            s += "-";
        }
        s += "r";
    }
    return s;
}
var s = new Student("li");
alert(s.getSaying());   // sayistudent
alert(s.purr(10));  // r-r-r-r-r-r-r-r-r-r

伪类模式的本意是向面向对象靠拢,但是看起来总是格格不入,我们可以优化一个

Function.prototype.method = function (name, func) {
    if (! this.prototype[name]) {
        this.prototype[name] = func;
    }
    return this;
}
Function.method("inherits", function (Parent) {
    this.prototype = new Parent();
    return this;
});
var Teacher = function (name) {
    this.name = name;
    this.saying = "teacher saying";
}
.inherits(Person)
.method("purr", function (n) {
    var i, s = '';
    for(i = 0; i < n; i++) {
        if(s) {
            s += "-";
        }
        s += "r";
    }
    return s;
})
.method("getName", function (){
    return this.getSaying() + " " + this.name + this.getSaying();   
});
var t = new Teacher("wang");
alert(t.getName()); // wang
alert(t.purr(3));       // r-r-r

缺陷 
没有私有环境 
所有的属性都是公开的 
无法访问super父类的方法 
在实例化时,如果忘记new,那么this会指向全局环境 
2.对象说明符

有时候,实例化构造器时会有许多参数,我们会感觉很麻烦

var myObject = maker(f, l, s, c);
//如下,使用对象说明符
//这样我们就可以很清晰的看清楚了参数的顺序关系了 
//对象说明符和JSON结合使用有意想不到的便捷 
var myObject = maker({
    first : f,
    last : l,
    state : s,
    city : c
});
3.原型

让我们用对象字面量的形式创建一个有用的对象

var myPerson = {
    name : "hello everyone",
    getName : function() {
        return this.name;
    },
    says : function () {
        return this.saying || "";
    }
}
Object.prototype.create = function (o) {
    if(typeof Object.create !== "function") {
        Object.create = function () {
            var F = function () {};
            F.prototype = o;
            return new F();
        }
    }
}

var worker = Object.create(myPerson);
worker.name = "worker";
worker.saying = "saying";
worker.getSaying = function () {
    return this.name + " " + this.saying;
}
alert(worker.getSaying());  // worker saying
alert(myPerson.getName());      // hello everyone

4.函数化

迄今为止,我们所有的继承的方式都无法保护隐私,我们可以用模式的方式来解决

var mammal = function (spec) {
    var that = {};
    that.getName = function () {
        return spec.name;
    };
    that.getSaying = function () {
        return spec.saying || "";
    }
    return that;
}
var m = mammal({name : "zhang"});
alert(m.name);  // undefined
alert(m.getName()); // zhang

让cat继承mammal

Function.prototype.method = function (name, func) {
    if(this.prototype[name] !== "function") {
        this.prototype[name] = func;
    }
    return this;
}
var cat = function(spec) {
    spec.saying = spec.saying || "";
    var that = mammal(spec);
    override getName method
    that.getName = function () {
        return spec.name + " : " + spec.saying;
    };
    that.purr = function (n) {
        var i, s = '';
        for(i = 0; i < n; i++) {
            if(s) {
                s += "-";
            }
            s += "r";
        }
        return s;
}
    return that;
}
var c = cat({name : "wang", saying : "hello dear"});
alert(c.getName()); // wang : hello dear
alert(c.purr(3));       // r-r-r
// 调用父类的方法
Object.method("superior", function (name) {
    var that = this,
        method = that[name];
    return function () {
        return method.apply(that, arguments);
    }
});
var littleCat = function (spec) {
    var that = cat(spec);
    get_super_name = that.superior("getName");
    that.getName = function () {
        return "like " + get_super_name() + " body";
    }
    return that;
}
var lc = littleCat({name : "zhangFei", saying : "Come On"});
alert(lc.getName());    // like zhangFei : Come On body
5.部件
var eventuality = function (that) {
    var registry = {};
    that.fire = function (event) {
        // alert(event.type);       // show
        var array, func, handler, i,
            type = typeof event === "string" ? event : event.type;
        if(registry.hasOwnProperty(type)) {
            array = registry[type];
            // alert(array);    // [object Object]
            for(i = 0; i < array.length; i++) {
                handler = array[i];
                // alert(handler);  // [object Object]
                func = handler.method;
                // alert(func());   // show
                if (typeof func === 'string') {
                    func = this[func];
                }
func.apply(this, handler.parameters || [event]);
            }
        }
        return this;
    }
    that.on = function (type, method, parameters) {
        var handler = {
            method : method, 
            parameters : parameters
        };
        // handler = {
            // method : "show",
            // parameters : [1, 2, 3]
        // }
        if(registry.hasOwnProperty(type)) {
            registry[type].push(handler);
        } else {
            registry[type] = [handler];
        }
        // alert(registry[type][0].method());       // show
        // alert(registry[type][0].parameters); // 1, 2, 3
return this;
    }
    return that;
}
var e = eventuality({ name : "zhang"});
var show = function () {
    return "show";
}
e.on("show", show, [1, 2, 3]);
e.fire({type : "show"});

四. 数组篇

1.删除

delete删除,数组的索引却没有变化

delete number[number.length - 1];
console.log(number);    // Array [ "one", "tow", "three", "five", <1个空的>]

改进: 因此我们用另一种形式,splice方法

number.splice(number.length - 2, 2);
console.log(number);    // Array [ "one", "tow", "three" ]
2.容易混淆的地方

例:判断一个变量是数组或对象

var is_array = function (value) {
    return value &&
        typeof value === "object" &&
        typeof value.length === "number" &&
        typeof value.splice === "function" &&
        !(value.propertyIsEnumerable('length'));
}
console.log(is_array([1,2]));       // true
3.方法

我们为Array.prototype可以扩展方法

Function.prototype.method = function (name, func) {
    if (!this.prototype[name]) {
        this.prototype[name] = func;
    }
    return this;
}
Array.method("reduce", function (func, value){
    var i, len;
    // this表示方法调用模式
    // 代表调用该方法的数组对象
    for (i = 0, len = this.length; i < len; i++) {
        value = func(this[i], value);
    }
    return value;
});
var data = [1, 2, 3, 4, 5];
var add = function (a, b) {
    return a + b;
}
var mult = function (a, b) {
    return a * b;
}
console.log(data.reduce(add, 0));       // 15
console.log(data.reduce(mult, 1));      // 120

同时,我们也可以为数组添加方法,以证明数组就是对象

data.total = function () {
    return this.reduce(add, 0);
}
console.log(data.total());          // 15
4.指定初始值

定义一个dim方法,让数组初始值为0

Array.method("dim", function (len, init) {
    var i, len;
    this.length = len;
    for(i = 0; i < this.length; i++) {
        this[i] = init;
    };
    return this;
});
console.log([].dim(10, 0));     // Array [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]