我们要先从简单工厂模式说起
1.简单工厂模式
工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建对象的过程。当然,我们本节课只做简单了解
function createPerson(name, age, job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function () { alert(this.name); } return o; } var person1 = createPerson("iwen", 20, "web前端"); var person2 = createPerson("ime", 30, "大牛级别"); person1.sayName(); person2.sayName();
函数createPerson能够根据接受的参数来构建一个包含所有必要信息的person对象。 可以无数次的调用这个函数,而每次都会返回一个包含三个属性一个方法的对象。 问题:工厂模式虽然解决了创建多个相似对象的问题。但是却没有决绝对象识别的问题。 (既怎么知道一个对象的类型)。
alert(person1 instanceof createPerson);//false
2.构造函数模式
在JavaScript中,构造函数可以用来创建特定类型的对象。 如:Object,Array这样的原生构造函数。 此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function () { alert(this.name); } } var person1 = new Person("iwen", 20, "web前端"); var person2 = new Person("ime", 30, "大牛级"); person1.sayName(); person2.sayName();
在这个例子中,Person取代了createPerson函数,我们注意到了,Person中的代码 除了与createPerson中相同的部分外,还存在几个不同之处 (1)没有显示的创建对象 (2)直接将属性和方法赋值给了this对象 (3)没有return对象 在构造函数模式中,创建Person的新实例,必须使用关键字new。 他会经历一下四个步骤 (1)创建一个新对象 (2)将构造函数的作用域赋给新对象(因此,this就指向了这个新的对象) (3)执行构造函数中的代码(为这个新对象添加属性) (4)返回新的对象 preson1和person2分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数) 属性,该属性指向Person。
alert(person1.constructor === Person); //true alert(person2.constructor === Person); //true
检测对象属性方法:instanceof
alert(person1 instanceof Person); //true alert(person2 instanceof Person); //true alert(person1 instanceof Object); //true alert(person2 instanceof Object); //true
创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型 (可以作为判断条件存在);
将构造函数当做函数 1.任何函数只要通过关键字new来调用,都可以作为构造函数, 而任何函数如果通过关键字new来调用,他就是普通函数。
Person("iwen", 20, "web"); window.sayName(); //iwen //通过call关键字使用 var o = new Object(); Person.call(o, "ime", 30, "大牛级"); o.sayName(); //ime
问题:构造函数每个方法都要在每个实例上重新创建一遍,例如上面的preson1和preson2,都有一个 叫sayName的方法。同样一个方法创建了2次。显然不合理,我们来看一下下面的解决方案:
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName() { alert(this.name); } var person1 = new Person("iwen", 20, "web前端"); var person2 = new Person("ime", 30, "大牛级"); person1.sayName(); person2.sayName();
上面的代码的问题: 我们把sayName转移到了外部,而构造函数内部,我们将sayName属性设置为等于全局的sayName属性 这样一来,由于sayName包含的是一个指向函数的指针,因此person1和person2共享了这个函数。 这样虽然解决了一个方法创建两次的问题,但是如果我们需要很多方法,那么这些方法都需要定义在 全局身上。这显然是不合理,也违背了封装性!
3.原型模式(原型模式很好的解决了上面的问题)
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; } Person.prototype.sayName = function () { alert(this.name); }; var person1 = new Person("iwen", 20, "web"); var person2 = new Person("ime", 30, "大牛"); person1.sayName(); person2.sayName();
4.下面我们来理解一下原型和原型链 实时上,上面的代码,sayName变成了公有方法 原型: 1.所有的函数数据类型,都天生自带一个属性:prototype,并且这个属性的值是一个对象数据类型, 浏览器默认为这个数据类型开辟一个新的堆内存 例如:xxxffff-pro 2.xxxfff-pro是浏览器默认开辟的堆内存,在这个堆内存中,天生自带一个属性,constructor (只有浏览器默认给prototype开辟的那个堆内存才有这个属性),他存储的值是当前类的本身 console.log(Person.prototype.constructor === Person);//返回true 3.我们在当前类的prototype上定义的属性和方法都是当前类本身的公有属性和方法 4.所有的对象数据类型(普通对象,类的实例,prototype)都天生自带一个属性 __proto__,这个 属性的指向是当前(实例)对象所属类的prototype console.log(p1.say === p1.__proto__.say);//true 则证明确实是同一个say Person.prototype.sayOld = function(){ console.log("my name"+this.name+",my age"+this.age); }; console.log(p1.sayOld === p1.__proto__.sayOld);//false 5.Object是基类,所有的对象数据类型都是object的实例 6.p1.say 他按照如下规律查找 1.现在当前实例上找私有的,私有的存在,则获取私有,不管公有的有没有。 2.如果私有的不存在,则在其原型连(__proto__)上寻找公有的
function Person(name, age) { this.name = name; this.age = age; this.sayOld = function () { console.log("my name is" + this.name + ",my age is" + this.age); } } Person.prototype.say = function () { alert(this.name); }; var p1 = new Person("iwen", 20); var p2 = new Person("ime", 30); console.log(p1.sayOld === p2.sayOld); //false console.log(p1.say === p2.say);//true say是共有属性 //如果公有上也没有,则通过prototype上的__proto__继续向下找,一直找到Object的prototype为止 console.log(p1.hasOwnProperty("say"));//false hasOwnProperty:检测是否包含私有属性 //p1.hasOwnProperty查找方式->看图 console.log(p1.hasOwnProperty === p1.__proto__.__proto__.hasOwnProperty);//true
5.原型扩展
function Fn(num) { this.x = num; } Fn.prototype.test = function () { console.log("test"); } var pro = Fn.prototype; Fn.prototype = { getX: function () { console.log("getX"); }, setX: function () { console.log("setX"); }, removeX: function () { console.log("removeX"); }, //恢复的方法 recover: function () { for (var key in pro) { //此时this--》Fn.prototype this[key] = pro[key]; } }, constructor: Fn }; var f = new Fn(100); f.getX(); console.log(f.constructor);//没有问题 Fn.prototype.recover(); f.test();//现在找不到了
6.继承 面向对象三大基本特征: 封装:方法的封装,增加可复用性 多态:传入不同的参数实现不同的功能 继承:子类继承父类中所有的属性和方法
function Parent() { this.x = 100; } Parent.prototype.getX = function () { console.log(this.x); } //此时parent中已经有了还需要在写么? function Son() { this.x = 10; }
/* 此时可以让Son继承Parent的属性和方法 继承的好处:不需要再次重写只需要去继承过来就可以了 */ //原型继承原理:就是让子类的原型=父类的一个实例,因为父类的实例拥有所有父类的属性和方法 //注意:此时父类中的所有公有和私有的属性和方法都变成了子类中的公有方法和属性
Son.prototype = new Parent; var s = new Son(); s.getX();//10 // // /* // 父类的原型上增加了setX的方法,子类也拥有了这个方法,父类修改原型上的方法,子类也跟着改变 // 这种情况叫:子类父类协同性 // */ Parent.prototype.setX = function () { console.log(this.x); }; s.setX(); //子类重写了父类的setX方法 Son.prototype.setX = function () { console.log("son setX"); } s.setX(); //另一种继承方法 Son.prototype = Object.create(Parent.prototype); var s = new Son(); s.getX();
多重继承 JavaScript不提供多重继承功能,即不允许一个对象同时继承多个对象。 但是,可以通过变通方法,实现这个功能
function M1(prop) { this.hello = prop; } function M2(prop) { this.world = prop; } function S(p1, p2) { this.base1 = M1; this.base1(p1); this.base2 = M2; this.base2(p2); } S.prototype = new M1(); var s = new S(111, 222); s.hello // 111 s.world // 222
this的情况: 1.自执行函数this指向window window.m = 300; (function test(){ console.log(this.m); })(); 2.DOM中this指向调用元素 3.函数中,谁调用了当前函数,this就指向谁 4.在构造函数模式类中,this.xxx = xxx;this指向当前类的实例对象 5.我们使用call和apply可以任意修改类的指向问题