JavaScript类

1 JavaScript类

  • 每个JavaScript对象都是一个属性集合,相互之间没有任何联系
  • 在JavaScript中也可以定义对象的类
    • 让每个对象都共享某些属性,这种“共享”的特性是非常有用的
      • 类的成员或实例都包含一些属性,用以存放或定义它们的状态
      • 其中有些属性定义了它们的行为(通常称为方法)。这些行为通常是由类定义的,而且为所有实例所共享
    • 在JavaScript中,类的实现是基于其原型继承机制的。如果两个实例都从同一个原型对象上继承了属性,我们说它们是同一个类的实例
    • 如果两个对象继承自同一个原型,往往意味着(但不是绝对)它们是由同一个构造函数创建并初始化的

2 类和原型

  • 在JavaScript中,类的所有实例对象都从同一个原型对象上继承属性。因此,原型对象是 类的核心
  • 如果定义一个原型对象,然后创建一个继承自它 的对象,这就定义了一个JavaScript类
  • 类的实例还需要进一步的初始化,通常是通过定义一个函数来创建井初始化这个新对象
//使用工厂的方法创建对象:返回使用new创建的对象
function inherit(p){
    if(p == null) throw TypeError();
    if(Object.create) return Object.create(p);
    if(typeof p!== 'object'&&typeof p!=='function') throw ypeError();
    function f(){}
    f.prototype = p;
    return new f();
}

//使用工厂方法创建一个对象
function range(from,to){
    var r = inherit(range.methods);
    //这两个属性是不可以被继承的,因为它们不是创建在对象的原型上
    r.from = from;
    r.to = to;

    return r;
}

//在range.methods中定义的那些可共享、 可继承的方法都用到了from和to属性, 而且使用了this关键字,

//为了指代它们, 二者使用this关键字来指代调用这个方法的对象。 任何类的方法都可以通过this的这种基本用法来读取对象的属性。

range.methods = {
    //如果x在范围内,则返回true,否则返回false
    //这个方法可以比较数字范围,也可以比较字符串和日期范围
    includes: function (x) { 
        return this.from<= x && x <= this.to;
    }, 
    //对于范围内的每个整数都调用一次f(x),这个方法只可用做数字范围
    foreach: function (f) {
        for (var x = Math.ceil(this.from); x <= this.to; x++) f(x);
    },
}

var r = range(1,3);
r.includes(2); 

3 类和构造函数

  • 在JavaScript中,使用工厂方法定义类不常用
  • 在JavaScript中, 推荐使用构造函数来定义类,构造函数是用来初始化新创建的对象的
    • 使用关键字new来调用构造函数会自动创建一个新对象, 因此构造函数本身只需初始化这个新对象的状态即可
    •  使用关键字new来调用构造函数的一个重要特征是, 构造函数的prototye属性被用做新对象的原型。 这意味着通过同一个构造函数创建的所有对象都继承自一个相同的对象, 因此它们都是同一个类的成员
//构造函数
function Range(from,to){
    //这两个属性是不可以被继承的,因为它们不是创建在对象的原型上
    this.from = from;
    this.to = to;
}
//在range.methods中定义的那些可共享、 可继承的方法都用到了from和to属性, 而且使用了this关键字,
//为了指代它们, 二者使用this关键字来指代调用这个方法的对象。 任何类的方法都可以通过this的这种基本用陆来读取对象的属性。
Range.prototype = {
    //如果x在范围内,贝Jj返回true,否则返回false
    //这个方法可以比较数字范围,也可以比较字符串和日期范围
    includes: function (x) { 
        return this.from<= x && x <= this.to;
    }, 
    //对于范围内的每个整数都调用一次f(x),这个方法只可用做数字范围
    foreach: function (f) {
        for (var x = Math.ceil(this.from); x <= this.to; x++) f(x);
    },
}

var r = new Range(1,3);
r.includes(2); 

3.1 构造函数和类的标识

  • 原型对象是类的唯一标识:当且仅当两个对象继承自同一个原型对象时, 它们才是属于同一个类的实例
  • 初始化对象的状态的构造函数则不能作为类的标识,只有两个构造函数的prototype属性指向同一个原型对象,那么这两个构造函数创建的实例是属于同一个类的
  • 尽管构造函数不像原型那样基础, 但构造函数是类的外在表现 。很明显的,构造函数的名字通常用做类名。更根本地讲,当使用instanceof运算符来检测对象是否属于某个类时会用到构造函数。实际上instanceof运算符并不会检查实例是否是由构造函数初始化而来,而会检查实例是否继承自构造函数的prototype, 不过,instanceof的语法则强化了“构造函数是类的公有标识”的概念

3.2 constructor属性

  • 任何JavaScript函数都可以用做构造函数, 并且调用构造函数是需要用到一个prototye属性的。因此,每个JavaScript函数(ECMAScript 5中的Function.bind()方法返回的函数除外)都自动拥有一个prototype属性
  • prototype属性的值是一个对象,这个对象包含唯一一个不可枚举属性constructor,它的值是一个函数对象
  • 通过重写prototype属性,会丢失原prototype属性的constructor属性,可以显式添加回来进行弥补
  • 通过扩展prototype属性,则不存在丢失原prototype属性的constructor属性
function F(){}
//重写
F.prototype = {
    constructor:F,//显式增加constructor,纠正重写prototype属性丢失constructors属性
    method1 = function(){},
    method1 = function(){},
}

//扩展
F.prototype.Fmethod = function(){};

//constructor属性值是一个函数对象
var f = new F();//函数对象
var p = f.protptype;
var c = p.constructor;
c === f;//true,对于任意函数f.prototype.constructor == f

4 JavaScript中类似Java的类

  • Java或其他类似强类型面向对象语言的类成员
    • 实例字段:它们是基于实例的属性或变量,用以保存独立对象的状态
    • 实例方法:它们是类的所有实例所共享的方法,由每个独立的实例调用
    • 类字段:这些属性或变量是属于类的,而不是属于类的某个实例的
    • 类方法:这些方法是属于类的,而不是属于类的某个实例的
  • JavaScript模拟Java中的上面的四种类成员,涉及三种不同的对象
    • 构造函数对象:构造函数(对象)为JavaScript的类定义了名字。任何添加到这个构造函数对象中的属性都是类字段和类方法(如果属性值是函数的话就是类方法)
    • 原型对象:原型对象的属性被类的所有实例所继承,如果原型对象的属性值是函数的话,这个函数就作为类的实例的方法来调用
    • 实例对象:类的每个实例都是一个独立的对象,直接给这个实例定义的属性是不会为所有实例对象所共亭的。定义在实例上的非函数属性,实际上是实例的字段
  • 在JavaScript中定义类的步骤
    • 第一步,先定义一个构造函数,并设置初始化新对象的实例属性
    • 第二步,给构造函数的prototype对象定义实例的方法
    • 第三步, 给构造函数定义类字段和类属性
function Complex(real, imaginary){
    if(isNaN(real) || isNaN(imaginary)) throw new TypeError(); 
    this.r = real; 
    this.i = imaginary; 
}

//当前复数乘以另外一个复数,并返回一个新的计算乘积之后的复数对象
Complex.prototype.mul = function (that) { 
    return new Complex(this.r * that.r - this.i * that.i, this.r * that.i + this.i * that.r);
}

Complex.ZERO = new Complex(0,0);
Complex.ONE= new Complex(1,0);
Complex.toStr = function(){console.log("Class method")};
  • JavaScript类定义三个步骤封装进一个简单的defineClass()函数
//定义class简单的封装函数
//constructor构造函数,如function F(x){this.x = x};
//实例方法:var methods = {m1:function(){},m2:function(){}}
//类方法:var statics = {s1:function(){},s2:function(){}}

function defineClass(constructor,methods,statics){
    function extend(o,p){
        for(prop in p){
            o[prop] = p[prop];
        }
        return o;
    }

    if(methods) extend(constructor.prototype,methods);
    if(statics) extend(constructor,statics);
    return constructor;
}

 5 类的扩充

  • JavaScript中基于原型的继承机制是动态的
    • 对象从其原型继承属性,如果创建对象之后原型的属性发生改变,也会影响到继承这个原型的所有实例对象
    • 这意味着我们可以通过给原型对象添加新方法来扩充JavaScript类

6 类和类型

  • 我们往往更希望将类作为类型来对待,这样就可以根据对象所属的类来区分它们
    •  JavaScript语言核心中的内置对象(通常是指客户端JavaScript的宿主对象)可以根据它们的class属性来区分彼此(如使用classof()函数)
    • 但通过构造函数和原型技术定义类,实例对象的class属性都是 “Object” ,classof()函数也无法使用
  • 三种用以检测任意对象的类的技术(三种技术都不甚完美)
    • instanceof运算符
    • constructor属性
    • 构造函数的名字
  • 鸭式辩型,这种编程哲学更加关注对象可以完成什么工作(它包含什么方住)而不是对象属于哪个类

6.1 instanceof 运算符

  • instanceof 运算符。左操作数是待检测其类的对象,右操作数是定义类的构造函数
    • 如果o继承自c.prototype,则表达式o instanceof c 值为true。这里的继承可以不是直接继承,如果o所继承的对象继承自另一个对象,后一个对象继承自 c.prototype,这个表达式的运算结果也是true
    • 构造函数是类的公共标识,但原型是唯一的标识。尽管 instanceof 运算符的右操作数是构造函数,但计算过程实际上是检测对象的继承关系,而不是检测创建对象的构造函数
    • 对象的原型链上是否存在某个特定的原型对象,可以不使用构造函数作为中介的方法,使用isPrototypeOf(o)方法
  • instanceof运算符和isPrototypeOf()方法的缺点是,通过对象无法获得类名,只能检测对象是否属于指定的类名
  •  在客户端JavaScript中还有一个比较严重的不足,就是在多窗口和多框架子页面的Web应用中兼容性不佳。 每个窗口和框架子页面都具有单独的执行上下文,每个上下文都包含独有的全局变量和一组构造函数。在两个不同框架页面中创建的两个数组继承自两个相同但相互独立的原型对象,其中一个框架页面中的数组不是另一个框架页面的Array()构造函数的实例,instanceof运算结果是false

6.2 constructor属性

 

posted @ 2023-07-03 15:33  好记心不如烂笔头  阅读(91)  评论(0)    收藏  举报