〇、写在前面的碎碎念
最近在重读高程,有些知识点每读一次收获都不同。今天和师父聊起来,师父说,如果每次能收获的都越来越少,到最后没有什么可以收获的了,那么,基础就真的吃透了。
嗯,言归正传,原型继承、作用域和闭包是基础的重中之重。
哈,今天先来原型继承的第一部分,有关创建对象的方式。
创建对象的方式有七种:
- 工厂模式
- 构造函数模式
- 原型模式
- 组合使用构造函数模式和原型模式
- 动态原型模式
- 寄生构造函数模式
- 稳妥构造函数模式
其中,前三种是基础。好啦开始正文~
一、工厂模式
function createPerson (name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
// console.log(this); // window
o.sayName = function () {
// console.log(this); // 具体的obj
console.log(this.name);
}
return o;
}
var p1 = createPerson('Aico', 23, 'FE');
var p2 = createPerson('Jen', 26, 'RD');
p1.sayName();
p2.sayName();
函数createPerson()
能够根据接受的参数来构建一个包含所有必要信息的Person
对象。可以无数次调用这个函数,而每次它都会返回一个包含三个属性一个方法的对象。
this
指向函数的直接调用者。可以看到,在函数createPerson()
里,this
指向的是window
,因为此时调用该函数的正是全局。而在方法sayName()
中,this
指向的是具体的实例。因为调用该方法的是实例。
工厂模式虽然解决了创建多个相似对象的问题,但是却没有解决对象识别的问题,因为这样创建出来的实例类型都是Object,辨识度太差。
二、构造函数模式
function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
console.log(this.name);
};
}
var p1 = new Person('Nico', 21, 'FE');
var p2 = new Person('Jen', 22, 'RD');
和工厂模式相比,构造函数的不同之处在于:
- 没有显式地创建对象;
- 直接将属性和方法赋给了
this
对象; - 没有
return
语句;
同时还有new
操作符,new
实际上干了四件事情:
- 创建一个新对象;
var obj = new Object();
- 将构造函数的作用域赋给新对象;
obj.__proto__= Person.prototype;
Person.call(obj);
- 执行构造函数中的代码(为新对象添加属性);
- 返回新对象;
构造函数带有Prototype
原型对象,原型对象的一个默认属性是constructor
,该属性又指回函数本身,即
console.log(Person.prototype.constructor === Person); // true
然而,
console.log(p1.constructor === Person); // true
实例本身并没有这个属性:
console.log(p1.hasOwnProperty(constructor)); // false
这充分的说明了,实例的constructor属性是沿着原型链找到的。
嗯,接下来再看两行代码。
console.log(p1 instanceof Object); // true
console.log(p1 instanceof Person); // true
创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型,而这正是构造函数模式胜过工厂模式的地方。
当然,构造函数本身也是函数,它与其他函数的唯一区别,在于调用它们的方式不同。任何函数,只要拿new
操作符来调用,那它就可以当作构造函数来用哒~比如,前面的Person()
函数可以通过以下任何一种方式来调用。
// 当作构造函数来使用
var p = new Person('Nico', 23, 'RD');
p.sayName(); // 'Nico'
// 作为普通函数调用
Person('Ann', 25, 'FE');
window.sayName(); // 'Ann'
// 在另一个对象的作用域中调用
var o = new Object();
Person.call(o, 'Bob', 26, 'QA');
o.sayName(); // 'Bob'
上述代码还间接说明了一个问题,就是,this
指向直接调用它的函数~~
当然,构造函数也是有问题的,其主要问题,就是每个方法都要在每个实例中重新创建一遍。
在之前的例子中,p1
和p2
都有一个名为sayName()
的方法,但是这两个方法不是同一个Function
的实例。如下。
console.log(p1.sayName == p2.sayName); // false
Why~~?
在ECMAScript中,函数是对象,因此每定义一个函数,也就是实例化了一个对象。也就是说,此时的构造函数也可以这样定义。
function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = new Function('console.log(this.name)');
}
从这个角度上看构造函数,更容易看明白每个Person
实例都包含一个不同的Function
实例(以显示name
属性)的本质。也就是说,以这种方式创建函数,会导致不同的作用域链和标识符解析,但创建Function
新实例的机制是相同的。
所以,创建两个完成同样任务的Function
实例完全没有必要况且有`this`对象在,根本不用在执行代码前就把函数绑定到特定对象上面,因而,可以把函数定义转移到外部呀~
function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName () {
console.log(this.name);
}
var p1 = new Person('Nico', 21, 'FE');
var p2 = new Person('Ann', 21, 'RD');
如此,sayName
包含的是一个指向函数的指针,因此p1
和p2
对象就共享了在全局作用域中定义的同一个sayName()
函数。
但素~~!!
- 在全局作用域中定义的函数实际上只能被某个函数调用,这让全局作用域有点名不副实;
- 还有~如果对象需要定义好多个方法,那么就要定义很多个全局函数,那么这个自定义的引用类型还有什么封装性可言?
于是,原型模式出现~~~
宝宝太困了回宿舍睡觉去,明天接着写!