自己写ES6的Class

281 阅读4分钟

实现一个 ES6 的class方法

我们都知道 ES6 中有 class 这个关键字,我们在ES6之前实现类都是通过构造函数来实现的。

我们传统上创建类分两种方式,工厂方式和构造函数方式。

  1. 工厂模式
function cratePerson(name,age) {
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.sayHello = function() {
        console.log("say hello")
    }
    return obj;
}
  1. 构造函数
function Person(name,age) {
    this.name = name;
    this.age = age;
    this.sayhello = function() {
        console.log("say hello")
    }
}

这两个的区别是啥,我初步总结以下几点,欢迎大家补充。

  1. 工厂模式需要创建一个对象,并且将对象返回,存在返回值。

  2. 我们如果在后台打印创造出来的对象会发现,工厂模式对应的是Object,但是构造函数模式对应的是构造函数的名称,比如上边的例子,我们看到的是 Person;

  3. 构造函数存在的一个问题就是如果我们在函数里边拿不到具体的值,可能会从全局去找,如果全局定义了相同的局部变量,就会造成全局污染。

  4. 构造函数的方式更加灵活,可以在后边重新定义或者通过prototype的方式来增加属性和方法。

我们研究一下构造函数定义的类

我们举个例子:本人家里养了一只🐔,一只🦆,当主人说“叫”的命令时候,🦆会嘎嘎,🐔会咯咯。

第一种方式实现:

var makeSound = function(animal){
    if(animal instanceof Duck) {
        console.log("嘎嘎嘎");
    } else if ( animal instanceof Chicken ) {
        console.log("咯咯咯");
    }
}
// 创建对应的构造函数
var Duck = function() {}; 
var Chicken = function() {};
makeSound(new Chicken());
makeSound(new Duck());

多肽方式实现:

var makeSound = function(animal) {
    animal.sound(); // 这里我们调用发出声音的方法
}
var Duck = function() {};
var Chicken = function() {};
Duck.prototype.sound = function() {
    console.log("嘎嘎嘎");
}
Chicken.prototype.sound = function() {
    console.log("咯咯咯");
}
makeSound(new Chicken());
makeSound(new Duck());

现在我们说到这里我们再讲讲原型链,以及基于原型链的继承。 其实对于Javascript只有一种结构,那就是对象,没个对象都有一个私有属性(proto)。指向他的构造函数的原型对象(constructor.prototype)。

我在浏览器写了一些测试的,可以看下

function Person() {}
var p = new Person();
p.__proto__ === p.constructor.prototype; // true

然后我们就一层一层往上找

image.png

我们最后发现最终我们可以得到一个 null。根据我们之前学习的东西,null没有原型,并作为这个原型链的最后一个环节。

通过这个方式,我们在以后的开发中,如果我们不知道当前对象有哪些方法,我们可以通过prototype的方式来查看。但是需要注意一个问题,就是方法重新定义覆盖原型链上的方法。 我们都知道如果我们想判断一个变量是不是数组我们通过:

Object.prototype.toString.call(参数) === "[object Array]"

我们可以直接通过这种方式来调用原型链上的方法。

其实我们的浏览器在我们使用了某个属性或者方法的时候浏览器要查看我们是不是存在这个属性或者方法,就是通过__proto__这个对象来找的,而不是通过constructor.prototype的这种方式找的。其实这个可以看下vue源码,里边也有类似的思想。通过这种方式寻找更加快捷。

在ESMAScript5中有个新的方法, Object.create()。 可以通过这个方法创建对象,新的对象的原型就是传入的第一个参数。

var a = { a: 1 }
var b = Object.create(a)
console.log(b.a) // 1
var c = Object.create(b);
var d = Object.create(null);
console.log(d.hasOwnProperty); // undefined 因为 d 没有继承Object.prototype

后来我们在 ES6 中使用了class关键字之后,我们定义类的时候方便多了。

class Polygon {
    constructor(height,width) {
        this.height = height;
        this.width = width;
    }
    getHei() {
        return this.height;
    }
}
class Square extends Polygon {
    constructor(sideLength) {
        super(sideLength,sideLength);
    }
    get area() {
        return this.height * this.width;
    }
    set sideLength(newLength) {
        this.height = newLength;
        this.width = newLength;
    }
}

我们先观察上边的使用方法,然后我们开始自己写class的方法

var Class = (function() {
    function create(classDefintion,parentPrototype) {
        var _NewClass = function() {
            if( this.initialize && typeof this.initialize === "function" ) {
                this.initialize.apply(this,arguments);
            }
        },
        _name;
        // 如果有父级的原型,我们将当前的class的原型对象指向父级原型对象的构造函数
        if(parentPrototype) {
            _NewClass.prototype = new parentPrototype.constructor();
            // 我们开始便利对象的属性
            for(_name in parentPrototype) {
                if(parentPrototype.hasOwnProperty(_name)) {
                    _NewClass.prototype[_name] = parentPrototype[_name];
                }
            }
        }
        function polymorph(thisFunction,parentFunction) {
            return function() {
                var output;
                this.__parent = parentFunction;
                output = thisFunction.apply(this,arguments);
                delete this.__parent;
                return output;
            }
        }
        
        for(_name in classDefinition) {
            if(classDefinition.hasOwnProperty(_name)) {
                if(parentProperty && parentProperty[_name] && type parentProperty[_name] === "function") {
                    _NewClass.property[_name] = polymorph(classDefintion[_name],parentPrototype[_name]) } else {
                        _NewClass.prototype[_name] = classDefinition[_name];
                    }
                }
            }
        }
    }
    
    function extend(classDefinition) {
        return create(classDefinition,this.prototype);
    }
    
    return {
        create
    }
})()


var Accommodation = Class.create({
        isLocked: true,
        isAlarmed: true,
        lock: function () {
            this.isLocked = true;
        },
        unlock: function () {
            this.isLocked = false;
        },
        initialize: function () {
            this.unlock();
        },
    });

    var House = Accommodation.extend({
        floors: 2,
        lock: function () {
            // this._parent();
            console.log("Number of floors locked:" + this.floors);
        },
    });

    var myAccommodation = new Accommodation();
    console.log(myAccommodation instanceof Accommodation); // true
    console.log(myAccommodation instanceof House); // false

    var myHouse = new House();
    console.log(myHouse.isLocked);
    myHouse.lock();
    console.log(myHouse.isLocked);
    console.log(myHouse instanceof House); // true
    console.log(myHouse instanceof Accommodation); // true