1.面向对象编程
面向对象编程:Object Oriented Programming,即OOP,面向对象就是将真实世界的各种复杂关系抽象为一个个对象,对象之间分工合作。
面向对象的特性:封装,继承,多态(JavaScript中将多态特性称为抽象)。
在 JavaScript 中,所有数据类型都可以视为对象,当然也可以自定义对象,对象具有属性和方法。
自定义的对象数据类型就是面向对象中的类( Class )的概念,在JavaScript中创建对象的模板是构造函数。
2.对象的创建方式
1.var 对象名 = new Object();
创建属性和方法 对象名.属性 对象名.方法 = function() {}
var student = new Object();
student.name = '张三'; //创建对象的属性
student.age = 12;
student.sex = '男';
//创建对象的方法
student.printName = function() {
onsole.log('Hello! My name is ' + this.name);
}
student.printName();
2.对象字面量 var 对象名 = { 属性名:值,方法 = function() {}}
//属性与属性/方法之间使用的是逗号不是分号
var person = {
name: '李四',
age: 15,
sayHello = function() {
console.log(this.name);
}
}
使用new Object和对象字面量的方式创建对象,代码过于冗余,而且重复性代码多,所以不适合用于创建大量对象。
3.工厂函数 function 方法名(对象的属性){
var 对象名 = new Object();
//让对象的属性和函数内的属性相等
return 对象
}
var 对象名 = 工厂函数名(相应属性的值);
function createPerson(name,age,sex) {
var person = new Object();
person.name = name,
person.age = age,
person.sex = sex,
person.sayName = function() {
console.log(this.name);
}
return person;
}
//使用工厂函数,要注意需要把对象返回。工厂函数使用与创建大量对象的情况,
//但是缺点在于无确定对象的具体类型。
4.构造函数创建对象
function 构造函数名(属性名) {
this.属性 = 属性名,this,方法名 = function(){函数体}
}
var 对象名 = new 构造函数();
//注意:构造函数名首字母一般大写 区分普通函数
function Person(name,age,sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.sayBye = function() {
console.log('Bye Bye');
}
}
var p1 = new Person('张三',18,'男');
p1.sayBye();
/*
使用构造函数创建的每一个对象,都有一个constructor属性 用于指向创建实例的构造函数
判断对象的类型:typeof constructor instanceof
推荐使用instanceof,可以判断一个对象的具体类型,返回的是布尔值
*/
3.静态成员和实例成员
静态成员:直接使用对象来调用,一般工具的对象使用的是静态成员。
实例成员:构造函数中的成员就是实例成员,需要new一个构造函数对象,然后再调用,当有多个对象的时候,使用构造函数的形式创建对象。
实例也可以作为对象的另一种说法。
4.原型/原型对象
引入原型/原型对象的背景:使用构造函数创建对象虽然方便,但是存在严重的内存浪费问题,构造函数内创建的方法,不同的对象调用相同的方法,就会开辟多个内存,如果实例对象很多,就会造成内存浪费。
解决构造函数内存浪费的方法:
1.可以在构造函数外部创建方法,然后在构造函数内用this.方法名 = 调用定义的方法,不过当有多个需要共享的函数时,容易造成全局变量命名冲突的问题。
2.使用原型:每一个对象都有prototype属性,每一个构造函数创建之后,都会有一个原型对象,这个对象的所有属性和方法,都会被构造函数所拥有。
//创建一个构造函数
function Student(name,number,age) {
this.name = name;
this.number = number;
this.age = age;
}
//原型对象创建共享方法
Student.prototype.sayName = function() {
console.log(this.name);
}
//多个对象 调用同一个方法 不会开辟新的内存
var stu1 = new Student('小明','001',18);
stu1.sayName();
var stu2 = new Student('小张','002',20);
stu2.sayName();
对象调用属性和方法的先后顺序:在调用属性/方法的时候,会先去调用对象本身的属性/方法,如果没有,则会去调用原型的属性/方法,如果原型也没有,则会报错。
5.prototype,proto,constructor的关系
对象有一个__proto__属性,用来指向原型对象,
构造函数调用prototype属性,创建原型对象
原型对象中有constructor属性,用来记录创建该对象的构造函数
原型对象调用prototype.constructor可以指向该对象的构造函数
prototype,__proto__,constructor三者之间的关系
构造函数(Student) Student.prototype 创建Student的原型对象
原型对象(Student) Student.prototype.constructor 指向构造函数
构造函数(Student) new 一个对象 s1
s1对象 s1.__proto__ 指向Student的原型对象
s1对象 s1.constructor 指向Student构造函数
举例:
//创建构造函数Student
function Student(name,number,age) {
this.name = name;
this.number = number;
this.age = age;
}
// 共享方法 创建原型对象:Student.prototype
Student.prototype.sayName = function() {
//当改变构造函数的prototype的时候,需要重新设置constructor属性
constructor:Student,
console.log(this.name);
}
//创建stu1对象
var stu1 = new Student('张三',11220,'男');
stu1.sayName();
//对象的__proto__属性和构造函数的prototype属性都是指向原型对象 所以是相等的
console.log(stu1.__proto__ == Student.prototype);
//对象stu1对象的constructor属性 指向构造函数Student
console.log(stu1.constructor == Student);
6.使用原型对象扩展内置对象
使用原型对象可以扩展内置对象的方法和属性,但是不能够修改内置对象默认的方法和属性,内置对象.prototype.方法名 = function() {}。
var arr = [15,85,96,33];
Array.prototype.getSum = function() {
var sum = 0;
for(var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
}
console.log(arr.getSum());
7.继承
一个对象继承另一个对象,那么该子对象就有父对象有的属性和方法。
JavaScript没有继承的关键字,继承的实现原理是对象的拷贝,就是将一个对象的所有成员复制给另一个对象。
//创建两个对象 parent和child
var parent = {
name:'zs',
age:15,
sex:'男'
}
var child = {
name:'ls'
}
function extend(parent,child) {
for(var key in parent) {
if(child[key]) {
//如果子对象存在父对象的属性或者方法 那么跳出本次循环
continue;
}
child[key] = parent[key];
}
}
//调用方法
extend(parent,child);
console.log(child);
除了对象直接拷贝之外,还可以使用原型对象来实现继承,但是使用原型对象继承的方式,无法设置参数。
function Person() {
this.name = 'zs';
this.age = 18;
this.sex = '男';
}
function Student() {
this.score = 90;
}
//将Person()的对象,赋值给Student的原型对象
Student.prototype = new Person();
//Student()的原型对象指向Student构造函数
Student.prototype.constructor = Student;
var s1 = new Student();
console.log(s1);
bind()方法改变函数的this,返回一个新的函数,但是不会调用。
function fn(x,y) { //参数默认 this fn(this,x,y)
console.log(this);
console.log(x + y);
}
var nameObject = {
name:'zs'
}
//this不是指向默认的window,而是改变之后的nameObject
var f = fn.bind(nameObject,2,5);
f(); //需要调用
call()方法:改变函数的this,同时会直接调用函数。
借用构造函数实现继承:构造函数中使用call()方法,父对象.call(this,父对象的参数);
//父对象
function Animal(name,age,sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
//子对象
function Dog(name,age,sex,wow) {
//改变了Animal构造函数为当前的this(Dog),修改了参数并且直接调用函数
Animal.call(this,name,age,sex);
this.wow = wow;
}
var dog = new Dog('dog',5,'母的','ww');
console.log(dog);
//借用构造函数的缺点在于不能继承父对象的函数
组合继承
组合继承:借用构造函数 + 原型继承
function Person(name,age,sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
//原型对象,可以实现继承父对象的方法
Person.prototype.sayHi = function() {
console.log('Hello Everyone');
}
//借用构造函数加上call()方法,可以实现继承父类的属性
function Student(name,age,sex,score) {
Person.call(this,name,age,sex);
this.score = score;
}
//子对象自身特殊方法
Student.prototype.exam = function() {
console.log('考试了');
}
//注意:不能直接将父对象的原型赋值给子对象,
//因为一个对象只有一个原型,直接赋值,会把子对象的特殊方法也复制给父对象
Student.prototype = new Person();
Student.prototype.constructor = Student;
var stu1 = new Student('zs',18,'男',100);
console.log(stu1);
stu1.sayHi();