一、面向对象概念
1、ECMAScript有两种开发模型:函数式编程(过程化)、面对对象编程。
- 类:类是对象的模型模板,可是理解为种类,例如定义People来表示人,这时People表示人的类型;
- 实例:实例式根据类创建的对象。
- 但是ECMAScript没有类的概念,所以它的对象也与基本类的语言中的对象有所不同。
2、对象的组成:
- 属性——对象的具体特点描述(姓名、年龄);
- 方法——对象的行为,动态等。
3、对象的基本特征:
- 封装:简单来说就是把一些方法封装到类中,便于使用。这个函数封装差不多,主要是不想外界作用域直接可以访问到。
- 继承:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展添加。
- 多态:允许将子类类型的指针赋值给父类类型的指针。
二、面对对象的创建
- 字面量方式创建。就是以json格式创建,适用于创建单个对象。
// 1、字面量创建对象
var obj = {
name : 'yy',
age : 18
};
- 实例化创建对象。使用new Object()创建一个实例对象,然后给这个对象增加属性和方法。
// 2、实例化对象创建
var obj = new Object();
obj.name = 'yy';
obj.age = 18;
obj.sya = function () {
console.log('加油!');
}
- 工厂模式创建对象。就是函数封装,将创建对象,及对象的属性和方法都封装在函数中,最后返回这个创建的对象。
// 3、工厂模式创建对象
function createObj() {
var obj = new Object();
obj.name = 'yy';
obj.age = 18;
obj.sya = function () {
console.log('加油!');
}
return obj; // 返回创建的对象
}
-
构造函数创建对象。这个是使用最多的创建对象方法,也是将所有操作都封装在函数中,但是与工厂模式有些不同,可以明确区分对象的种类。最后创建实例的时候要用new关键字;
构造函数的特点:- 构造函数的函数名首字母要大写(约定成俗的约定,为了区分普通函数);
- 构造函数方法没有显式的创建对象;
- 直接将对象的属性和方法赋值给this对象;
- 没有return语句,不需要返回对象;
- 通过构造函数创建对象,必须使用new运算符;
new关键字的用处:
- 创建一个新对象;
- 将构造函数的作用域赋值给新对象(让this指向这个对象);
- 执行构造函数中的代码(为新对象添加属性);
- 返回新对象;
// 4、构造函数创建对象
function CreateObj(name, age) {
this.name = name;
this.age = age;
this.sya = function () {
console.log('加油!');
}
}
var obj = new CreateObj('yy', 18);
console.log(obj); // CreateObj对象
- 原型创建对象。 每个对象都会有自己的原型对象(prototype),是这类对象共有的方法和属性的集合。而__proto__是实例对象的一个属性,指向原型对象。原型对象需要类型名词调用,如Object.prototype。原型下面还有一个constructor的属性,它又指向构造函数。通常用来判断对象类型。
// 5.原型对象创建对象
function CreateObj () {};
CreateObj.prototype.name= 'yy';
CreateObj.prototype.age = 18;
CreateObj.prototype.say = function () {
console.log('加油!');
}
var obj = new CreateObj();
console.log(obj.name); // yy
console.log(obj.age); // 18
obj.say() // 加油
需要注意的是:在原型对象定义的属性和方法都是固定的值。创建的对象不能传参。
- 混合模式模式创建对象(构造+原型) 这个模式就可以传参和继承原型里面的方法
// 6.混合模式模式创建对象(构造+原型)
function CreateObj (name) {
this.name = name;
};
CreateObj.prototype.say = function () {
console.log('加油!');
}
var obj = new CreateObj('yy');
console.log(obj.name); // yy
obj.say() // 加油
- 动态混合模式创建对象。 一般创建对象用混合模式就够了,单数严格来说这种方式破坏了封装性,所以可以使用动态混合模式创建对象,将原型对象也写在构造函数当中。
// 7.动态混合模式创建对象
function CreateObj(name) {
this.name = name;
// 判断是否有这样一个方法没有,如果没有就在原型上添加
if (typeof (this.sya) !== 'function') {
CreateObj.prototype.sya = function () {
console.log('加油');
}
}
};
var obj = new CreateObj('yy');
console.log(obj.name); // yy
obj.say() // 加油
三、原型的继承方式
- 原型链继承:是指子类继承父类的属性和方法,原型链继承就是讲父类的实例对象赋值给子类的原型对象。
// 1.原型链继承: 通过父类的实例对象传给子类的原型对象
function ParseArr() { // 父类构造函数
this.arr = [5, 4, 3, 2, 1];
}
// 创建一个子类构造函数
function SonArr() {};
// 将子类的原型继承父类
SonArr.prototype = new ParseArr();
// 创建一个对象obj1接受子类构造函数的属性和方法
var obj1 = new SonArr();
console.log(obj1.arr); // [5,4,3,2,1]
// 改变SonArr里面的数组顺序
obj1.arr.sort();
console.log(obj1.arr); // [1,2,3,4,5]
// 再用新对象obj2接受子类构造函数的属性和方法
var obj2 = new SonArr();
// 这次输出发现里面的内容是对象obj1修改过的 这就说明继承了引用类型,会一改全改,一个实例对象改变了引用类型的数据
console.log(obj2.arr); // [1,2,3,4,5]
- 对象冒充继承:继承就是想用父类的属性和方法,使用call获取apply改变this的指向,让他指向父类,就可以使用,称之为对象冒充。
// 2.对象冒充继承
function ParseObj (name, age) {
this.name = name;
this.age = age;
};
function SonObj (name, age) {
// 调用父类的构造函数通过call来给改变this的指向对象冒充
ParseObj.call(this,name, age);
};
var obj1 = new SonObj('yy',18);
console.log(obj1.name); // yy
- 组合继承(对象冒充+原型链):对象冒充继承构造函数中的内容,原型链继承原型上的属性和方法。
// 3.组合继承
function ParentObj (name, age) {
this.name = name;
this.age = age;
}
// 给父类原型上定义方法
ParentObj.prototype.say = function () {
console.log('加油');
}
function SonObj (name, age) {
// 通过对象冒充继承父类
ParentObj.call(this, name, age);
}
// 在子类的原型链上继承
SonObj.prototype = new ParentObj();
var obj1 = new SonObj('yy',18);
console.log(obj1.age); // 18
obj1.say(); // 加油
组合继承的问题是无论什么情况下,都会调用两次父类构造函数,一次在子类创建子类原型的时候,另一次在子类构造函数内部。
- 寄生组合式继承:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。不必为了制定子类型的原型来调用父类的构造函数,我们所需要的无非是父类型原型的一个副本,本质上就是使用寄生式继承来继承父类型的原型,然后再将结果指定给子类型的原型。
// 4.寄生组合式继承
function inherits(Child, Parent) {
var F = function () { };
// 让F函数的原型指向父类的原型
F.prototype = Parent.prototype;
// 让Child的原型等于F的实例化对象
Child.prototype = new F();
// 在修改Child原型的constructor
Child.prototype.constructor = Child;
}
// 创建父类
function ParentObj(name, age) {
this.name = name;
this.age = age;
}
// 给父类原型上定义方法
ParentObj.prototype.say = function () {
console.log('加油');
}
// 创建子类
function SonObj (name, age) {
// 通过对象冒充继承父类
ParentObj.call(this, name, age);
}
// 调用函数
inherits(SonObj, ParentObj);
var obj = new SonObj('yy', 18);
console.log(obj.name); // yy
console.log(obj.age); // 18
四、闭包
闭包就是能够读取其他函数内部变量的函数(函数里面套函数,内部函数访问外部函数变量),在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
闭包可以把变量长久的存储在内存中,每次闭包都是新的。
function outer () {
var a = 10;
function inner () {
a++;
console.log(a);
}
return inner;
}
var inn = outer();
console.log(inn);
inn(); // 11
inn(); // 12
var inn1 = outer();
inn1(); // 11
inn1(); // 12