工厂模式、构造函数模式、原型模式、构造函数模式+原型模式
// 工厂模式 function Person(name,age) { let o = new Object(); o.name = name; o.age = age; o.say = function() { console.log(this.name); } return o; //记得返回对象 } let person1 = Person('kevin',18); let person2 = Person('kevin2',8); // console.log(person1 instanceof Person) //false 不能识别对象类型 // 可以无数次调用这个工厂函数,每次都会返回一个包含两个属性和一个方法的对象 // 工厂模式虽然解决了创建多个相似对象的问题,但是没有解决对象识别问题,即不能知道一个对象的类型 // 构造函数模式 function Person1(name,age) { this.name = name; this.age = age; this.say = () => { console.log(this.name) } } let person3 = new Person1('kevin',18); let person4 = new Person1('kevin2',8); // 没有显示的创建对象,使用new来调用这个构造函数,使用new后会自动执行如下操作 // 创建一个新对象 // 这个新对象会被执行[[prototype]]链接 // 这个新对象会绑定到函数调用的this // 返回这个对象 // 使用这个方式创建对象可以检测对象类型 // 但是使用构造函数创建对象,每个方法都要在每个实例上重新创建一次 // console.log(person3 instanceof Person1) // true 能够识别对象类型 // 原型模式 function Person2() {}; Person2.prototype.name = 'kevin'; Person2.prototype.age = 18; Person2.prototype.girlFirends = ['mimi','kiki'] Person2.prototype.say = () => { console.log(this.name); } let person5 = new Person2(); let person6 = new Person2(); person5.girlFirends.push('gigi'); // 将信息直接添加到原型对象上。使用原型的好处是可以让所有的实例对象共享它所包含的属性和方法,不必在构造函数中定义对象实例信息。 // 使用原型,所有的属性都将被共享,这是个很大的优点,同样会带来一些缺点 // 原型中所有属性实例是被很多实例共享的,这种共享对于函数非常合适。对于那些包含基本值的属性也勉强可以,毕竟实例属性可以屏蔽原型属性。但是引用类型值,就会出现问题了 // console.log(person5 instanceof Person2,person5,person6, person5.girlFirends === person6.girlFirends) //true, Person2 {}, Person2 {}, true // 构造函数模式 + 原型模式 // 属性写在构造函数模型中,方法写在原型模型中 function Person3(name) { this.name = name; this.girlFirends = ['mimi','kiki']; } // 这里之前写错了,不能用箭头函数,会拿不到this Person3.prototype.say = function() { console.log(this.name) } let person7 = new Person3('kevin'); let person8 = new Person3('kevin2'); person7.girlFirends.push('gigi'); // console.log(person7,person8,person7 instanceof Person3) //Person3 {name: "kevin", girlFirends: Array(3)} Person3 {name: "kevin2", girlFirends: Array(2)} true // 这是使用最为广泛、认同度最高的一种创建自定义类型的方法。它可以解决上面那些模式的缺点 // 使用此模式可以让每个实例都会有自己的一份实例属性副本,但同时又共享着对方法的引用 // 这样的话,即使实例属性修改引用类型的值,也不会影响其他实例的属性值了
对比在传统构造函数和 ES6 中分别如何实现类:
//传统构造函数 function MathHandle(x,y){ this.x=x; this.y=y; } MathHandle.prototype.add =function(){ return this.x+this.y; }; var m=new MathHandle(1,2); console.log(m.add()) //class语法 class MathHandle { constructor(x,y){ this.x=x; this.y=y; } add(){ return this.x+this.y; } } const m=new MathHandle(1,2); console.log(m.add())
这两者有什么联系?其实这两者本质是一样的,只不过是语法糖写法上有区别。所谓语法糖是指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。比如这里class语法糖让程序更加简洁,有更高的可读性。
对比在传统构造函数和 ES6 中分别如何实现继承:
//传统构造函数继承 function Animal() { this.eat = function () { alert('Animal eat') } } function Dog() { this.bark = function () { alert('Dog bark') } } Dog.prototype = new Animal()// 绑定原型,实现继承 var hashiqi = new Dog() hashiqi.bark()//Dog bark hashiqi.eat()//Animal eat //ES6继承 class Animal { constructor(name) { this.name = name } eat() { alert(this.name + ' eat') } } class Dog extends Animal { constructor(name) { super(name) // 有extend就必须要有super,它代表父类的构造函数,即Animal中的constructor this.name = name } say() { alert(this.name + ' say') } } const dog = new Dog('哈士奇') dog.say()//哈士奇 say dog.eat()//哈士奇 eat
Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。
Class 和传统构造函数有何区别
- Class 在语法上更加贴合面向对象的写法
- Class 实现继承更加易读、易理解,对初学者更加友好
- 本质还是语法糖,使用prototype
类和构造函数的区别这一部分内容来自浪里行舟博客