面向对象编程OOP
面向对象是把事务分解成一个个*对象*,以对象功能来划分问题,而不是步骤,然后由对象之间分工合作。
具有灵活、代码可复用、容易维护,更适合多人合作的大型项目。
面向对象编程的特性:封装性、继承性、多态性
面向对象的两个概念:
类 | 对象 |
---|---|
class关键字声明类 | 一个具体的事物,由属性和方法组成 |
抽取对象公共部分封装成一个类,泛指某一大类(class) | 特指某一个,通过类实例化一个具体的对象 |
-
封装 – 构造函数和原型
-
构造函数创建对象
构造函数是Person() ;person1是实例对象;原型对象是Person.prototype;
function Person(name) { this.name = name; } var person1 = new Person('xiaoming'); var person2 = new Person('xiaoli');
-
构造函数中的属性和方法成为成员,成员可以添加
静态成员 实例成员 在构造函数本身上添加的成员 构造函数内部通过this添加的成员 只能通过构造函数来访问 只能通过实例化对象来访问 function Star(uname,age) { this.uname = uname; this.age = age; //通过this添加的实例成员 this.sing = function() { console.log('我会唱歌'); } } var xzq = new Star('薛之谦', 18); console.log(Star.uname) //undefined console.log(xzq.uname);//只能通过实例化对象来访问uname实例成员 Star.sex = '男'; //在构造函数本身上添加,sex 静态成员 console.log(xzq.sex) //undefined 不能通过对象访问 console.log(Star.sex) //男 只能通过构造函数来访问
-
构造函数存在浪费内存问题
function Star(uname,age) { this.uname = uname; this.age = age; //方法放在构造函数里面,每一个实例对象就会为这个方法单独开辟内存空间,造成内存浪费 this.sing = function() { console.log('我会唱歌'); } } var xzq = new Star('薛之谦',19); var zls = new Star('赵露思',18); console.log(xzq.sing === zls.sing)//false 如下图,同一个函数存放的地址不一样
-
原型对象prototype(共享方法)
原型对象 作用 prototype 共享方法,减少内存开辟 - 每一个构造函数都有一个prototype对象,这个对象的所有属性和方法都被构造函数所拥有
- 把那些不变的方法,直接定义在prototype对象上,这样所有的实例对象都可以共享此方法,减少占用内存
- 一般情况,公共 属性 定义到 构造函数 里面,公共 方法 定义在 原型对象 身上!!!
function Star(uname,age) { this.uname = uname; this.age = age; //this.sing = function() { //console.log('我会唱歌'); //} } //sing()定义在原型对象上,每个实例对象都共享这个方法 Star.prototype.sing = function() { console.log('我会唱歌'); } var xzq = new Star('薛之谦',19); var zls = new Star('赵露思',18); console.log(xzq.sing === zls.sing) //true 都通过原型对象查找sing,指向同一个地址
-
扩展内置对象方法
为Array函数添加自定义方法属性,通过在原型对象上.xxx添加 Array.prototype.sum = function() { var sum = 0; for(var i = 0;i<this.length; i++) { sum += this[i]; } return sum; } var arr = new Array(1,2,3) //或者=[1,2,3] console.log(arr.sum())//6
-
对象原型 __ proto __
-
为什么实例对象可以访问构造函数原型对象的属性方法呢?
因为每个对象都有一个 proto 属性指向创建它的构造函数的原型,如下:
ldh1.__proto__ == Star.prototype; ldh2.__proto__ == Star.prototype; //如果实例对象没有这个方法,则去查找构造函数的原型对象 //Star本身是一个构造"函数",那么创建它的构造函数就是一个Function,所以 Star.__proto__ == Function.prototype;
-
-
constructor 属性
- 每个对象都有一个constructor属性,指回构造函数本身,如下:
实例对象: person1.constructor == Person; person2.constructor == Person;
- Person.prototype 是Person的原型"对象",所以这个对象也有constructor属性,同样指向Person
原型对象: Person.prototype.constructor == Person;
-
-
作用:记录该对象的构造函数是哪一个,可以用person1.constructor.name 找到构造函数
它可以让原型对象重新指向原来的构造函数
function Star() { this.name = 'oo'; } Star.prototype = { //修改原型对象 sing: function () { console.log('唱歌'); } } var a = new Star(); var b = new Star(); console.log(a.constructor == Star) // false //由于Star.prototype原型对象被重新赋值,原本的constructor属性没有了,无法指回构造函数
-
Star.prototype = { constructor: Star, //手动利用constructor 这个属性指回原来的构造函数 sing: function () { console.log('唱歌'); } } var a = new Star(); console.log(a.constructor == Star) // true 指回构造函数
-
7.原型链 ( JS的成员查找机制:沿着原型链依次查找属性方法,直到Object为止null)
- Person.prototype 是一个原型"对象",那么创建它的构建函数就是一个Object,所以
```javascript
Person.prototype.__proto__ == Object.prototype;
Object.prototype.constructor == Object
```
- 原型链的终点是null :
```
Object.prototype.__proto__ == null;
```
- Math和JSON是以对象存在的, null没有原型 :
```
Math.__proto__ === Object.prototype;
JSON.__proto__ === Object.prototype;
null.__proto__ // Cannot read properties of null (reading '__proto__')
```
- Function.prototype是唯一一个typeof Function.prototype == "function"的原型,其他构造器的原型都是object
```
typeof Function.prototype == "function";
typeof Person.prototype == "object";
```
##### *js*的*成员查找机制*(就近原则)
① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
② 如果没有就查找它的原型(也就是_proto_指向的prototype原型对象)。
③ 如果还没有就查找原型对象的原型(Object的原型对象)。
④ 以此类推一直找到Object为止(null)。
⑤ _proto_对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
function Person() {
}
Person.prototype.name = "小明";
person1 = new Person();
person1.constructor.prototype.name = "小花";
console.log(Person.prototype.name, person1.constructor === Person);// 小花 true
person1 = {//这里已经属于重写构造函数了
getName: function(){
console.log("获取姓名");
}
}
console.log(Person.prototype.name);// 小花
console.log(Person) // ƒ Person() {} constructor不指向构造函数Person
console.log(person1.constructor == Person) //false
console.log(person1.constructor == Object); //true ƒ Object() { [native code] }
-
父构造函数继承属性(call)(ES6之前)
通过call()把父的this指向子的this,实现子类型继承父类型的属性。
function Father(uname,age) { //this指向父构造函数的实例对象; this.uname = uname; this.age = age; } function Son(uname,age,sex) { //this指向子构造函数的实例对象; Father.call(this,uname,age); this.sex = '男'; } var son = new Son('薛之谦',18,'男'); console.log(son);
Father.call(this,uname,age)调用之后把父亲的this换成了son的this:
-
原型对象继承(ES6之前)
function Super(){ } Super.prototype.money = function() { console.log('我有钱'); }; function Sub(){}; //将父类型的实例对象作为子类型的原型对象,继承父亲的方法 Sub.prototype = new Super(); //不能用 Sub.prototype = Super.prototype直接赋值,如修改了子原型对象,父原型对象也回跟着变化!!! Sub.prototype.constructor = Sub; //由于修改了原型对象,constructor需要重新指向给Sub var instance = new Sub; console.log(instance.money())//我有钱
3. 类的继承(ES6)
3.1 创建类
- class本质还是function
- 类的所有方法定义在类的prototype属性上,类创建实例对象也有__ proto __指向类的原型对象
- ES6其实就是语法糖,写法更加清晰、方便
//通过class关键字创建类,类名首字母大写,类名不用加小括号
class Star {
constructor(uname,age) { //可以接收传递过来的参数,同时返回实例对象
this.uname = uname;
this.age = age; //this指向创建的实例对象
}
}
//类中添加方法:
Star.prototype.sing = function(song) {
console.log(this.uname + song); //this指向sing方法调用者,即实例对象
}
//利用类创建实例对象 new不能省略
var xzq = new Star('薛之谦', 40);
var zls = new Star('赵露思', 25);
//只要new生成实例,就会自动调用constructor(),如果我们不写这个函数,类也会自动生成
console.log(xzq.uname) //通过this拿到
//类里面所有函数不需要写function,不需要逗号隔开
xzq.sing('无数');//薛之谦无数
zls.sing('有你在');
3.2 类的extends继承
class Father {
constructor(x,y) {
this.x = x;
this.y = y;
}
}
Father.prototype.sum = function() {
console.log(this.x + this.y); //this指向sum方法调用者,即实例对象
}
1.super关键字 调用父类构造函数
class Son extends Father {
constructor(x,y) { //接收到的参数是new Son的
super(x,y); //在子类构造函数constructor中
//通过super调用父类的构造函数,把参数传递给父类的constructor
}
}
var son = new Son(1,2);
son.sum();//3 通过super可把子类参数传给父类并调用方法
2.super关键字 调用父类普通函数
class Father {
say() {
return '我是爸爸';
}
}
class Son extends Father {
say() {
//super.say()就是调用父类的普通函数say()
console.log(super.say() + '的儿子');
}
}
var son = new Son();
son.say(); //我是爸爸的儿子
-- 继承中的属性或方法查找,就近原则;实例化子类输出一个方法,先看子类没有再去查找父类
3.子类继承父类方法同时扩展自己的方法
class Father {
constructor(x,y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y); //this指向的是类的constructor
}
}
//子类继承父类加法 同时 扩展减法方法
class Son extends Father {
constructor(x,y) {
super(x,y); //子继承父类后,必须先调用父类构造函数才能使用子类构造函数constructor
// 即 super必须在子类this之前调用
this.x = x;
this.y = y;
}
subtract() {
console.log(this.x - this.y);
}
}
class Son2 extends Father {
constructor(x,y) {
super(x,y); //子继承父类后,必须先调用父类构造函数才能使用子类构造函数constructor
// 即 super必须在子类this之前调用
this.x = x;
this.y = y;
}
multiply() {
console.log(this.x * this.y);
}
}
var son = new Son(5,3);
var son2 = new Son2(1,3);
son.subtract(); //2
son.sum(); //8
son2.sum(); //4
son2.multiply()//3
3.3 使用类的注意事项
<button>点击<button>
var that;//声明一个全局变量
class Star {
constructor(uname,age) {
//3.constructor里面的this指向的是 创建的实例对象
that = this; //将this保存给全局变量that
this.uname = uname;
this.age = age;
//this.sing(); //2.类里面共有的属性和方法,一定要加this使用
this.btn = document.querySelector('button');
this.btn.onclick = this.sing;
}
sing() {
//4.方法里面的this指向的是 这个方法的调用者
console.log(this.uname);//sing方法里面的this指向的是btn,则是undefined
console.log(that.uname);//薛之谦 that里面存储的是constructor里面的this
}
dance() {
//这个dance方法里面的this 指向的是实例对象 因为实例对象xzq调用了这个函数
}
}
var xzq = new Star('薛之谦');//1.ES6中类没有变量提升,必须先定义类,才能通过类实例化对象
xzq.dance();//实例对象调用dance