面向对象编程的相关知识点

265 阅读7分钟

创建类和生成实例

class People {
    constructor(uname, age) {
        this.uname = uname
        this.age = age
    }
}

let zs = new People('张三', 18);
let ls = new People('李四', 20);

1、通过class 关键字创建类,类名首字母大写

2、类里有constructor函数,接受传过来的参数,同时返回实例对象

3、只要new生成实例时,就会自动调用constructor函数。若不写,类也会自动生成该函数

4、new不能省略,注意规范,创建类后面不加小括号,生成实例后面要加小括号,构造函数不需要添加function

类中添加共有方法

class People {
    constructor(uname, age) {
        this.uname = uname
        this.age = age
    }
    sing (song) {
        console.log(this.uname + '会唱' + song);
    }
}

let ldh = new People('刘德华');
ldh.sing('男人哭吧哭吧不是罪')

继承extends

创建一个类,该类是另一个类的子类

class Father {
    money() {
        console.log('这是父亲的遗产');
    }
}

class Son extends Father {
    
}

let son = new Son();
son.money();

super关键字

用于访问和调用一个对象的父对象上的函数。相当于是指向当前对象的父类的引用。

在构造函数中使用时,super关键字将单独出现,并且必须在使用this关键字之前使用。

class Father {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    sum() {
        console.log(this.x + this.y);
    }
}

class Son extends Father {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
}

let son = new Son(1, 2);
// 报错 Must call super constructor in derived class before accessing 'this' or returning from derived constructor
son.sum(); 

分析: 父类(Father)的方法(sum)中,this指向的是父类constructor里的数据,构造函数Son的数据1,2指向的是子类constructor里的数据

class Father {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    sum() {
        console.log(this.x + this.y);
    }
}

class Son extends Father {
    constructor(x, y) {
        super(x, y); // 调用了父类中的构造函数
    }
}

let son = new Son(1, 2);
son.sum(); 

super关键字也可以用来调用父对象上的函数。

class Father {
    say() {
        return '我是父亲';
    }
}

class Son extends Father {
    say() {
        console.log(super.say() + '的儿子');
    }
}

let son = new Son();
son.say(); 

class Father {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    sum() {
        console.log(this.x + this.y);
    }
}

class Son extends Father {
    constructor(x, y) {        
        // this.x = x; // ReferenceError,super 需要先被调用!        
        
        super(x, y); // 调用了父类中的构造函数        
        
        // 在派生的类中, 在你可以使用'this'之前, 必须先调用super()        

        this.x = x;        
        this.y = y;    
    }    
    sub() {
        console.log(this.x - this.y);
    }
}

let son = new Son(10, 2);
son.sum(); 
son.sub(); 

创建对象

// 利用new Object 创建对象
let obj = new Object();

// 利用对象字面量 创建对象
let obj1 = {}

// 利用构造函数 创建对象
function People (uname, age) {
    this.uname = uname;
    this.age = age
}

let obj2 = new People('对象', 18)

实例成员和静态成员

// 实例成员是构造函数内部通过this添加的成员,uname age sing 就是实例成员
function People (uname, age) {
    this.uname = uname;
    this.age = age;
    this.sing = function () {
        console.log('我会唱歌');
    }
}

let ldh = new People('刘德华', 18);
// 实例成员只能通过实例化的对象来访问
console.log(ldh.uname);
ldh.sing();
// console.log(People.uname);  // undefined,不可以通过构造函数来访问实例成员

// 静态成员 在构造函数本身上添加的成员, sex 就是静态成员
People.sex = '男'
console.log(ldh.sex);  // undefined,不可以通过对象来访问静态成员
console.log(People.sex);

构造函数和原型

构造函数的问题,存在浪费内存的问题。每new一个函数, 就会在内存中,生成一个独立的内存区域,用来存储当前的对象,以及对象上面的方法和属性。

function Star(uname, age) {
    this.uname = uname;
    this.age = age;
    this.sing = function () {
        console.log('我会唱歌');
    }
}
let ldh = new Star('刘德华', 18); // new 一个对象  ===》 function () {}
let zxy = new Star('张学友', 20); // new 一个对象  ===》 function () {}......

console.log(ldh.sing === zxy.sing) // false

所有引用类型(函数,数组,对象)都拥有__proto__属性(隐式原型)

所有函数拥有prototype属性(显示原型)(仅限函数)

let a = {};
console.log(a.prototype);      //undefined
console.log(a.__proto__); 	//Object{}

let b = function(){};
console.log(b.prototype); 	//Object{}
console.log(b.__proto__);	//function(){}
// 由上述代码可知,prototype是函数才有的属性

原型是一种对象(属性的集合),除了constructor外,还可以自定义许多属性。

constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()

对象原型__proto__

function Star(uname, age) {
    this.uname = uname;
    this.age = age;
}
Star.prototype.sing = function() {
    console.log('我会唱歌');
}
let ldh = new Star('刘德华', 18); // 对象上系统自动添加一个__proto__ 指向我们构造函数的原型对象
ldh.sing();
console.log(ldh.__proto__ === Star.prototype ) // true
// 对象调用方法查找规则
// 先查看ldh对象身上是否有sing方法,如果有就执行这个对象上的sing
// 如果没有,因为ldh对象上有__proto__的存在,就会去构造函数原型对象prototype身上去查找sing方法

1、Star构造函数有个原型对象 Star.prototype,把sing方法放到原型对象里面

// Star.prototype

2、利用构造函数,new一个 ldh 实例对象,ldh这个对象实例,并不存在sing方法,

但是实例对象上因有__proto__原型的存在,指向了构造函数的原型对象prototype

constructor 属性

对象原型__proto__和构造函数prototype原型对象里有个constructor属性,该属性指向的是构造函数的本身。

function Star(uname, age) {
    this.uname = uname;
    this.age = age;
}
Star.prototype.sing = function () {
        console.log('我会唱歌');
}
let ldh = new Star('刘德华', 18); 

console.log(Star.prototype) console.log(ldh.__proto__) 
//constructor指向的是构造函数Star本身

constructor主要是用于记录该对象引用于哪个构造函数,它可以让原型对象指向原来的构造函数。

function Star(uname, age) {   
     this.uname = uname;   
     this.age = age;
}
// Star.prototype.sing = function () {
//     console.log('我会唱歌');
// }
// Star.prototype.movie = function () {
//     console.log('我会演电影');
// }
Star.prototype = {    
    sing: function () {        
        console.log('我会唱歌');
    },   
    movie: function () {     
        console.log('我会演电影');
    }
}
let ldh = new Star('刘德华', 18); 
// 不再指向构造函数本身  需要手动利用constructor这个属性指回原来的构造函数
console.log(Star.prototype.constructor) 
console.log(ldh.__proto__.constructor) 

手动利用constructor这个属性指回原来的构造函数

function Star(uname, age) {    
    this.uname = uname;    
    this.age = age;
}
Star.prototype = {
    constructor: Star, // 手动利用constructor这个属性指回原来的构造函数
    sing: function () {         
       console.log('我会唱歌');    
    },    
    movie: function () {       
         console.log('我会演电影');   
    }
}

let ldh = new Star('刘德华', 18); 
console.log(Star.prototype.constructor) 
console.log(ldh.__proto__.constructor) 

如果我们修改了原来的原型对象,并给原型对象赋值的是一个对象,需要手动利用constructor属性指回原来的构造函数。

构造函数、实例、原型对象三者之间的关系

function Star(uname, age) {    
    this.uname = uname;    
    this.age = age;
}
Star.prototype = {    
    constructor: Star, // 手动利用constructor这个属性指回原来的构造函数     
    sing: function () {
        console.log('我会唱歌');
    },    
    movie: function () {         
       console.log('我会演电影');    
    }
}
let ldh = new Star('刘德华', 18); 

console.log(Star);
console.log(Star.prototype);
console.log(Star.prototype.constructor);
console.log(ldh);
console.log(ldh.__proto__);
console.log(ldh.__proto__.constructor); 

1、每个构造函数Star都有一个原型对象prototype,通过Star.prototype指向原型对象

2、原型对象里有个constructor属性,指回了构造函数

3、构造函数通过new,创建了一个实例对象ldh,指向了这个对象实例

4、实例对象ldh有一个__proto__原型,指向原型对象

5、该实例对象的__proto__原型里也有个constructor属性,通过ldh.__proto__指向了原型对象,并通过原型对象指向了构造函数。

原型链

function Star(uname, age) {        
    this.uname = uname;        
    this.age = age;
}
Star.prototype = {
    constructor: Star, // 手动利用constructor这个属性指回原来的构造函数
    sing: function () {
        console.log('我会唱歌');
    },
    movie: function () {
        console.log('我会演电影');
    }
}
let ldh = new Star('刘德华', 18);
console.log(Star.prototype)

1、ldh这个实例对象里,有个__proto__原型,它指向的是构造函数Star的原型对象prototype

2、Star原型对象也有个__proto__原型,它指向的是Object的原型对象

3、Object原型对象也有个__proto__原型,它指向的是null

这就是原型链。

在构造函数Star中,this指向的是对象实例ldh,原型对象函数里的this,也指向的是实例对象ldh.

利用原型对象扩展内置对象方法

Array.prototype.sum = function () {
    var sum = 0;
    for (var i = 0; i < this.length; i++) {
        sum += this[i];
    }
    return sum;
}
var arr = [1, 2, 3, 4, 5];

console.log(arr.sum()); // 15
console.log(Array.prototype);

继承

call方法的作用

function fn (x, y) {
    console.log(this);
    console.log('我在调用函数' + x + y);
}
// 1、调用函数fn.call(); 
// 此时的this指向的是window

// 2、改变this的指向
var o = {
    name: '函数'
}
fn.call(o, 1, 2); // 此时的this指向的是o, 后面就是参数

组合继承,利用父构造函数继承属性 

// 父构造函数
function Father(uname, age) {
    console.log(this, 0);
    this.uname = uname;
    this.age = age;
}
// 子构造函数
function Son(uname, age) {
    console.log(this, 1);
    Father.call(this, uname, age);
}
let son = new Son('刘德华', 18);
let fater = new Father('张学友', 20)
console.log(son, 2);
console.log(fater, 3);

new实例对象son,this指向的是构造函数Son,通过call(),将构造函数Father里this指向Son.

new实例对象Father,又会将this指向构造函数Father.

组合继承,利用父构造函数继承方法

// 父构造函数
function Father(uname, age) {
    this.uname = uname;
    this.age = age;
}
Father.prototype.money = function () {
    console.log('我是Father');
}
// 子构造函数
function Son(uname, age) {
    Father.call(this, uname, age);
}

// 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化,会有exam方法
// Son.prototype = Father.prototype; 

Son.prototype = new Father(); // 覆盖了Son里面的constructor
Son.prototype.constructor = Son; // ,需要手动指向构造函数

Son.prototype.exam = function() {
    console.log('我要考试');
}

let son = new Son('刘德华', 18);
console.log(son);
console.log(Father.prototype);
console.log(Son.prototype.constructor);