你根本不懂Javascript(3):类和模块

本文最初发布于http://szhshp.org/tech/2017/02/18/JavaSprite.html
转载请注明

类和模块

工厂函数

function range(from, to) {
    var r = inherit(range.methods);         //继承对应的方法             
    r.from = from;
    r.to = to;

    return r;
};


range.methods = {

    includes: function (x) {
        return this.from <= x && x <= this.to;
    },

    foreach: function (f) {
        for (var x = Math.ceil(this.from); x <= this.to; x++) f(x);
    },
    toString: function () {
        return "(" + this.from + "..." + this.to + ")";
    }
}

// Here are example uses of a range object.
var r = range(1, 3);                        // Create a range object
r.includes(2);                              // => true: 2 在对应的范围之内
r.foreach(console.log);                     // Prints 1 2 3
console.log(r);                             // Prints (1...3)
  • 这里给range()函数定义了一个属性range.method用于快捷地存放定义类的原型对象。
  • 类里面的属性fromto都是非共享属性,是不可以继承的

使用构造函数代替工厂函数

function Range(from, to) {      //注意这儿函数名首字母变为大写了
    this.from = from;
    this.to = to;
}

Range.prototype = {         //通过prototype来给类定义方法
    includes: function (x) {
        return this.from <= x && x <= this.to;
    },

    foreach: function (f) {
        for (var x = Math.ceil(this.from); x <= this.to; x++) f(x);
    },
    toString: function () {
        return "(" + this.from + "..." + this.to + ")";
    } };

var r = new Range(1, 3);                        // 注意新建obj的时候需要用到关键字new
r.includes(2);                                  // => true: 2 is in the range
r.foreach(console.log);                         // Prints 1 2 3
console.log(r);                                 // Prints (1...3)
  • 普通函数方法一般都首字母小写,但是构造方法需要首字母大写
  • 其次调用的时候需要添加关键字new, 同时这种情况下就不需要调用inherit()方法了

Constructor属性

var F = function() {};             // This is a function object.
var p = F.prototype;            // This is the prototype object associated with it.
var c = p.constructor;         // This is the function associated with the prototype.
c === F;                      // => true: F.prototype.constructor==F for any function
//对于任意函数,F.prototype.constructor == F

var o = new F();              // Create an object o of class F
o.constructor === F;          // => true: the constructor property specifies the class

类的扩充

简单易懂,如果原型中不存在对应方法就初始化对应方法

//给ES3中的函数类添加bind方法
if (!Function.prototype.bind) {
    Function.prototype.bind = function (o /*, args */) {
        // Code for the bind method goes here... };
    }
}

简单例子:

var n = 3;
n.times(function (n) {
    console.log(n + " hello");
});
//下面分别给Number,string/function等添加方法或属性
Number.prototype.times = function (f, context) {
    var n = Number(this);
    for (var i = 0; i < n; i++) f.call(context, i);
};

String.prototype.trim = String.prototype.trim || function () {
    if (!this) return this;  // Don't alter the empty string
    return this.replace(/^\s+|\s+$/g, "");  // Regular expression magic
};

Function.prototype.getName = function () {
    return this.name || this.toString().match(/function\s*([^(]*)\(/)[1];
};

给原型添加的方法可以使得所有的对象都可以调用这个方法

类和类型

有三种方法用于检测对象类:

instanceof/isprototypeof

缺点:

  • 无法通过对象获得类名,只能检测对象是否属于特定类
  • 多窗口和多框架的Web应用中兼容存在问题

Constructor

        function typeAndValue(x) {
            if (x == null) return "";
            switch (x.constructor) {
                case Number:return "Number:" + x;
                case String:return "String: '" + x + "'";
                case Date:return "Date: " + x;
                case RegExp:return "Regexp: " + x;
                case Complex:return "Complex: " + x;
            }
        }

缺点:

  • 多窗口和多框架的Web应用中兼容存在问题

注意case后面的表达式都是函数。如果使用typeof的话获取到的结果会是字符串,例如下文

function type(o) {
    var t, c, n;  // type, class, name
// Special case for the null value:
    if (o === null) return "null";
// Another special case: NaN is the only value not equal to itself:
    if (o !== o) return "nan";
// Use typeof for any value other than "object".
// This identifies any primitive value and also functions.
    if ((t = typeof o) !== "object") return t;
// Return the class of the object unless it is "Object".
// This will identify most native objects.
    if ((c = classof(o)) !== "Object") return c;
// Return the object's constructor name, if it has one
    if (o.constructor && typeof o.constructor === "function" && (n = o.constructor.getName())) return n;
// We can't determine a more specific type, so return "Object"
    return "Object";
}


// Return the class of an object.
function classof(o) {
    return Object.prototype.toString.call(o).slice(8, -1);
};
// Return the name of a function (may be "") or null for nonfunctions
Function.prototype.getName = function () {
    if ("name" in this) return this.name;
    return this.name = this.toString().match(/function\s*([^(]*)\(/)[1];
};

此外并非所有对象都有Constructor属性,匿名函数就是个典型:

// This constructor has no name
var Complex = function (x, y) {
    this.r = x;
    this.i = y;
}
// This constructor does have a name
var Range = function Range(f, t) {
    this.from = f;
    this.to = t;
}

JS的面向对象技术

一个全面并且典型的纯OOP例子:

function Set() {          // This is the constructor
    this.values = {};     // The properties of this object hold the set
    this.n = 0;           // How many values are in the set
    this.add.apply(this, arguments);  // All arguments are values to add
}

// Add each of the arguments to the set.
Set.prototype.add = function () {
    for (var i = 0; i < arguments.length; i++) {  // For each argument
        var val = arguments[i];                  // The value to add to the set
        var str = Set._v2s(val);                 // Transform it to a string
        if (!this.values.hasOwnProperty(str)) {  // If not already in the set
            this.values[str] = val;              // Map string to value
            this.n++;                            // Increase set size
        }
    }
    return this;                                 // Support chained method calls
};

// Remove each of the arguments from the set.
Set.prototype.remove = function () {
    for (var i = 0; i < arguments.length; i++) {  // For each argument
        var str = Set._v2s(arguments[i]);        // Map to a string
        if (this.values.hasOwnProperty(str)) {   // If it is in the set
            delete this.values[str];             // Delete it
            this.n--;                            // Decrease set size
        }
    }
    return this;                                 // For method chaining
};

// Return true if the set contains value; false otherwise.
Set.prototype.contains = function (value) {
    return this.values.hasOwnProperty(Set._v2s(value));
};

// Return the size of the set.
Set.prototype.size = function () {
    return this.n;
};

// Call function f on the specified context for each element of the set.
Set.prototype.foreach = function (f, context) {
    for (var s in this.values)                 // For each string in the set
        if (this.values.hasOwnProperty(s))    // Ignore inherited properties
            f.call(context, this.values[s]);  // Call f on the value
};

Set._v2s = function (val) {         //这是一个内部函数,当然实例对象无法调用这个方法
    switch (val) {
        case undefined:
            return 'u';          // Special primitive
        case null:
            return 'n';          // values get single-letter
        case true:
            return 't';          // codes.
        case false:
            return 'f';
        default:
            switch (typeof val) {
                case 'number':
                    return '#' + val;    // Numbers get # prefix.
                case 'string':
                    return '"' + val;    // Strings get " prefix.
                default:
                    return '@' + objectId(val); // Objs and funcs get @
            }
    }

    // For any object, return a string. This function will return a different
    // string for different objects, and will always return the same string
    // if called multiple times for the same object. To do this it creates a
    // property on o. In ES5 the property would be nonenumerable and read-only.
    function objectId(o) {
        var prop = "|**objectid**|";   // Private property name for storing ids
        if (!o.hasOwnProperty(prop))   // If the object has no id
            o[prop] = Set._v2s.next++; // Assign it the next available
        return o[prop];                // Return the id
    }
};
Set._v2s.next = 100;    // Start assigning object ids at this value.

另一种通过返回值设定类的方法

function Test() {
    var map = 1;
    function a(){
        map = 2;
    }
    function b(){
        console.log(map);
    }
    return{
        a:a,
        b:b
    }
}
var t = new Test()

对于后者:

  • 注意如果最后的return里面包含了map那么无论如何执行b()这个map的值都不会变, 因为返回的是一个Obj是额外空间
  • 当然这里也可以不放返回值
  • 返回值的方法是为了闭合部分接口
  • 更大的区别是:很难重写第二种模式里面的方法

子类

原书中的子类内容比较累赘,可以归纳为以下几步:

  1. 继承prototype中定义的属性和方法;
  2. 继承构造函数中定义的属性和方法;
  3. 修改子类的prototype对象的constructor指针
function Animal(name) {  
    this.name = name;  
}  
Animal.prototype.set = "female";
Animal.prototype.info = function () {
        console.log("animal");  
}

function People(name) {  
    this.name = name;  
}  
People.prototype = new Animal("animal");  // 继承父类中定义的属性和方法;
People.prototype.info = function() {  //重写父类中定义的属性和方法;
    console.log("peopel")  
};  

//Demo
var cat = new Animal('cat');

console.log(cat instanceof Animal);    //t
console.log(cat instanceof Object);    //t
console.log( typeof Animal.prototype);      //object  
console.log( typeof Animal.constructor);        //function  
console.log(Animal.prototype.constructor == Animal);    //true  


var mike = new People("mike");  
console.log(mike.sex);//female  
mike.info();//People  

console.log(mike instanceof People);    //t
console.log(mike instanceof Animal);    //t
console.log(mike instanceof Object);    //t
console.log( typeof Animal.prototype);      //object  
console.log( typeof Animal.constructor);        //function  
console.log(People.prototype.constructor == People);    //true  

类的封装

简单封装方法:

  1. 使用var关键字设置私有属性

  2. 阻止类的扩展:

    使用Object.seal()可以阻止给对象添加属性并将已有的属性设置为不可配置的,即不可删除

    但是这种情况下依然可以修改属性

    Object.seal(mike);
    mike.sex = 'male'; //仍然可以修改
    delete mike.sex; //Cannot delete property 'sex' 
    
  3. 阻止类的修改:

    Object.seal()类似不过Object.freeze方法将实例方法设置为不可写的

    这种情况下修改对应方法将变得无效

    Object.seal(mike);
    mike.sex = 'male'; //不会报错但是修改无效
    

模块化模式

首先我们来看看Module模式的基本特征:

  1. 模块化,可重用
  2. 封装了变量和function,和全局的namaspace不接触,松耦合
  3. 只暴露可用public的方法,其它私有方法全部隐藏

基本用法

var Calculator = function (eq) {
    //这里可以声明私有成员

    var eqCtl = document.getElementById(eq);

    return {
        // 暴露公开的成员
        add: function (x, y) {
            var val = x + y;
            eqCtl.innerHTML = val;
        }
    };
};


var calculator = new Calculator('eq');
calculator.add(2, 2);

匿名闭包

(function () {
    // ... 所有的变量和function都在这里声明,并且作用域也只能在这个匿名闭包里
    // ...但是这里的代码依然可以访问外部全局的对象
}());

注意,匿名函数后面的括号,这是JavaScript语言所要求的,因为如果你不声明的话,JavaScript解释器默认是声明一个function函数,有括号,就是创建一个函数表达式,也就是自执行,用的时候不用和上面那样在new了,当然你也可以这样来声明:

(function () {/* 内部代码 */})();

引用全局变量

获取全局变量到匿名函数域

(function ($, YAHOO) {
    // 这儿$相当于全局的jQuery
} (jQuery, YAHOO));//这两个是全局变量, 我们把它们放到这儿说明使用这两个参数调用上面那个匿名函数

从匿名函数域设定全局变量

var blogModule = (function () {
    var my = [1,2,3]

    return my;//其实只要把这些变量返回回去就行了, 之后blogModule就相当于my这个变量
} ());

当然return也可以返回一个object

var blogModule = (function () {
    var my = [1,2,3]

    return {
        my: my,
        you: null
    }
} ());

高级用法

对变量自身进行扩展

var blogModule = (function (my) { // 2. 这里接收到了传进来的blogModule并把blogModule命名为my
    var AddPhoto = function () {    // 3. 这里给my添加了个函数, 因此blogModule也多了个函数
        console.log(123);
    };
    return {AddPhoto: AddPhoto};
} (blogModule)); //1. 这里将blogModule传了进去
blogModule.AddPhoto()// 4. 扩展完毕后就可以调用了

松耦合扩展

上面的扩展必须要先定义这个blogModule, 能否在未定义的时候初始化而在已定义的时候直接扩展来达到松耦合的目的呢:

var blogModule = (function (my) {

    // 添加一些功能   
    
    return my;
} (blogModule || {}));  

这样可以英一顺序加载module模式

紧耦合扩展

虽然松耦合扩展很牛叉了,但是可能也会存在一些限制,比如你没办法重写你的一些属性或者函数,也不能在初始化的时候就是用Module的属性。紧耦合扩展限制了加载顺序,但是提供了我们重载的机会,看如下例子:

var blogModule = (function (my) {
    var oldAddPhotoMethod = my.AddPhoto;

    my.AddPhoto = function () {
        // 重载方法,依然可通过oldAddPhotoMethod调用旧的方法
    };

    return my;
} (blogModule));

子模块

blogModule.CommentSubModule = (function () {
    var my = {};
    // ...

    return my;
} ());
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,567评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 前言 人生苦多,快来 Kotlin ,快速学习Kotlin! 什么是Kotlin? Kotlin 是种静态类型编程...
    任半生嚣狂阅读 26,139评论 9 118
  • 春天来了 (2013-03-06 16:15:01)[编辑][删除] 时间过得真快,还没从元旦的新旧年的交替中完全...
    冷珩湄阅读 278评论 0 0
  • 决策树理论在决策树理论中,有这样一句话,“用较少的东西,照样可以做很好的事情。越是小的决策树,越优于大的决策树”。...
    制杖灶灶阅读 5,832评论 0 25