面向对象初级
目录
1.构造函数,原型和继承
1.1 构造函数的定义
构造函数就是用来创造新对象的(函数,数组,正则表达式,日期等都是对象), 它必须用过关键字new来创造,如果将构造函数用作普通函数的话,往往不会正常工作的. 按照一惯的约定, 我们开发者把构造函数的首字母大写用作辨别. 一个构造函数创造的对象被称为该构造函数的实例.
1.2 常见的构造函数
Object()
Array()
RegExp()
Function()
Date()
1.3 辨别变量是否由构造函数创造
变量名 instanceof 构造函数
1.4__ proto__和prototype
每一个对象都拥有一个proto属性(即为prototype原型属性),该属性内部有一个不可枚举的属性constructor,constructor的属性的值是一个函数对象(即为该对象的构造函数)
1.5 原型和构造函数的关系
每一个构造函数都有一个prototype属性,该属性的值是一个对象,对象内的各种方法,就是该构造函数创造示例时,子元素继承过去的方法.
原型和构造函数的关系
这是一个无限循环的过程.
2.工厂模式
2.1 普通创建对象的方式
var lilei = {
name:"李雷",
age:18,
address:'安宁西路',
sayName:function() {
console.log(`我的名字是${this.name}`);
}
}
var hanmeimei = {
name:"韩梅梅",
age:17,
address:'安宁西路',
sayName:function() {
console.log(`我的名字是${this.name}`);
}
}
var liming = {
name:"李明",
age:17,
address:'安宁西路',
sayName:function() {
console.log(`我的名字是${this.name}`);
}
}
使用普通方式创建对象,工作量大且重复性非常高.
2.2 工厂模式的概念模型
//工厂模式的概念模型
function createPerson(name,age,address) {
let obj = {};
obj.name = name;
obj.age = age;
obj.address = address;
obj.sayName = function () {
console.log(`我的名字是${this.name}`);
}
return obj;
}
函数createPerson()能够根据接受的参数来构建一个包含所有必要信息的Person 对象。可以无数次地调用这个函数,而每次它都会返回一个包含三个属性一个方法的对象.
var lilei = createPerson("李雷",18,"安宁西路六号");
var hanmeimei = createPerson("李雷",17,"安宁西路六号");
var liming = createPerson("李明",17,"安宁西路六号");
但是使用这种方式创建的对象回存在一个弊端,因为createPerson内创造对象的方式还是对象字面量, 所以就相当于是new Object创造一样,这就导致createPerson函数创造的所有的对象的构造函数还是Object, 这样咱们就无法得知某些对象是不是同一个函数创造出来的了。
所以出现了构造函数来创建一个新的对象。
3.构造函数模式
3.1 构造函数简介
function Person (name,age,address) {
this.name = name;
this.age = age;
this.address = address;
this.sayName = function () {
console.log(`我的名字是${this.name}`);
}
}
var lilei = createPerson("李雷",18,"安宁西路六号");
var hanmeimei = createPerson("李雷",17,"安宁西路六号");
var liming = createPerson("李明",17,"安宁西路六号");
在构造函数模式中Person()函数取代了createPerson()函数,他们两者之间存在以下差别:
-
没有显示的创建对象;
-
直接将属性和sayName方法赋值给了this对象;
-
不存在return语句;
此外,还有我们值得注意的是构造函数Person中的P是大写字母,createPerson函数中的c是小写,这是我们约定俗称的,构造函数名称以大写字母开头,而普通函数名称由小写字母开头.这么做主要是为了区分ECMAScript中的其他函数.
注:构造函数也是函数,只不过它被用来创建对象而已!!!
3.2 构造函数实例化对象过程
function Person (name,age,address) {
let obj = new Object(); //模拟对象创造的过程,该对象是由Person创造的
let this = obj;//this本身并不能作为变量名,这里只是为了模拟this指向每一次新生成的对象本身
this.name = name;
this.age = age;
this.address = address;
this.sayName = function () {
console.log(`我的名字是${this.name}`);
}
return obj;
}
var lilei = new Person("李雷",18,"安宁西路六号");
var hanmeimei =new Person("李雷",17,"安宁西路六号");
var liming = new Person("李明",17,"安宁西路六号");
要创建Person的新实例,必须使用new操作符,用这种构造函数的方式创建对选哪个会经历下面的四个步骤:
-
创建一个新对象;
-
将构造函数的作用于赋值给新创建的对象(因此this的指向变为了这个新创建的对象);
-
执行构造函数中的代码(为这个属性添加对象和方法);
-
返回新对象;
我们在这个例子中创建的所有对象既是Object 的实例,同时也是Person的实例,这一点通过instanceof 操作符可以得到验证。 之所以lilei,hanmeimei都是Object的实例是因为所有的对象都继承自Object
3.3 构造函数和普通函数的区别
构造函数与其他函数的唯一区别,就在于调用它们的方式不同。不过,构造函数毕竟也是函数,不存在定义构造函数的特殊语法。
-
任何函数,只要通过new操作符来调用,那它就可以作为构造函数;
-
任何函数,如果不通过new操作符来调用,那它跟普通函数也不会有什么两样
直接运行构造函数时,构造函数中的this默认指向window.
普通函数也可以当做构造函数来使用;
function fn () {
console.log("我是小仙女");
}
let obj = new fn();
console.log(obj);
在这种情况下:
函数内的代码首先会全部执行一次,然后会返回一个新的对象,这个对象的construct属性就是这个普通的函数.
3.4 构造函数的缺点
使用构造函数实例化对象的时候每个方法都要在实例上重新创建一遍.
在上面的例子实例化的过程中,lilei和hanmeimei都有sayName方法,但是这两个方法并不是同一个function的实例,在ECMAScript中,函数也是对象,每定义一个函数也就是重新实例化了一个函数.
在下面的例子中,我们把sayName()函数的定义转移到了构造函数外部。而在构造函数内部,我们将sayName 属性设置成等于全局的sayName 函数。这样一来,由于sayName 包含的是一个指向函数的指针,因此李雷和韩梅梅对象就共享了在全局作用域中定义的同一个sayName()函数。
function fn() {
console.log(`我的名字是${this.name}`);
}
function Person (name,age,address) {
this.name = name;
this.age = age;
this.address = address;
this.sayName = fn;
console.log(this);
}
var lilei = new Person("李雷",18,"安宁西路六号");
var hanmeimei = new Person("李雷",17,"安宁西路六号");
这样做固然有它的好处,但是这个函数本身是应该属于Person构造函数的,将它定义在全局则失去了局部作用域的意义,将局部函数定义在全局作用域,于是我们这个自定义的引用类型就丝毫没有封装性可言.