JavaScript面向对象

面向对象

1.作用

封装信息。比如Student类里可以封装学生的姓名、年龄、成绩等。

对象具有特征(属性)和行为(方法)。

面向对象:可以创建自定义的类型,很好的支持继承和多态。

面向对象的特征:封装、继承、多态。

万物皆对象

2.对象与类的创建

面向对象有两个基本概念:类和对象

先创建类,在根据类创建这个类的对象

类:是对一类具有相同特征和行为的事务的抽象或者描述

对象:是符合某一个类的所有特征的具体的事物,也叫做类的实例

2.1 创建对象的三种方式

2.1.1字面量
var p1 = {
	name: 'yhb',
    age: 18
} 
console.log(p1);
  • 从 json 角度看 name 与 age 称作键
  • 从对象角度看, name 与 age 称作属性
2.1.2构造函数
 function Person() {
    this.name = 'yhb';
    this.age = 18;
 }
var p2 = new Person()//实例化对象
console.log(p2);    
  • 上面使用 new 运算符调用函数 Person 创建对象

  • name 与 age 称作对象的属性

    我们发现每次new出来的都是一个对象 值都相同

    所以我们需要利用构造函数 传入参数

    function Person(name,age){
        this.name=name
        this.age=age
    }
    var p1=new Person('yhb',18)
    var p2=new Person('onlifes',20)
    console.log(p1);
    console.log(p2);
    
2.1.3 类
//步骤1 使用class关键字
class Name {
  // class body
}     
//步骤2使用定义的类创建实例  注意new关键字
var xx = new Name();

例子:

class Person{
    constructor(name,age){
        this.name=name
        this.age=age
    }
}
var p1=new Person('yhb',18)
var p2=new Person('onlifes',20)
console.log(p1);
console.log(p2);

2.2类的继承

语法:

// 父类
class Father{   
} 

// 子类继承父类
class  Son  extends Father {  
}     

例子:

// 父类:基类
class Father{
    constructor(name){
        this.name='张飞'
    }
    speak(){
        console.log('常山赵子龙');
    }
}
// 子类:派生类
class Son extends Father{

}
var s=new Son()
console.log(s.name);
s.speak()
2.2.1子类的成员
class Dog{
    speak(){
        console.log('汪汪汪');
    }
}
class Husky extends Dog{
    // 子类自己的方法
    destroy(){
        console.log('我是辛勤的小蜜蜂,拆家是我的乐趣');
    }
}
var h=new Husky()
h.speak() // 继承自父类的方法
h.destroy() // 子类自己的方法
2.2.2构造函数的继承
  • 子类会自动继承父类的构造函数
  • 如果父类中没有声明构造函数,也会有一个默认的无参构造函数,这个函数子类也会继承
  • 如果父类中有声明了构造函数,子类中也声明了构造函数,则在子类的构造函数中编写代码之前,一定要使用super关键字调用父类的构造方法
class Dog {
    constructor(brand) {
        this.brand = brand;
    }
}
class Husky extends Dog {
    // level:拆家战力等级(1-10)
    constructor(brand, level) {
        super(brand); // 调用父类构造函数,并传递参数
        this.level = level;
    }
}
var h = new Husky('哈士奇',7);
console.log(h.brand, h.level);
2.2.3父类和子类有同名方法

  • 如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
  • 如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
2.2.4继承小结
  • 类里面的共有的属性和方法一定要加this使用.
  • constructor中的this指向的是new出来的实例对象
  • 自定义的方法,一般也指向的new出来的实例对象

3.原型和继承

3.1 对象的创建方式

3.1.1 字面量方式
var obj = {};

主要用来创建 json 数据,一般不会用来创建对象

3.1.2 普通模式

基于 Object 创建

var Person=new Object()
Person.name='yhb'
Person.age=20
Person.speak=function(){
  console.log('hello');
}
console.log(Person);
  • 因为是基于 Object 基类创建,所以无法获知对象的具体类型
  • 初始化成员非常麻烦

3.1.3 构造函数方式

function Person(name,age){
  this.name=name
  this.age=age
  this.speak=function(){
    console.log('hello world');
  }
}
var p1=new Person('李白',10)
console.log(p1); // Person

缺点:每个函数都会开辟一块新的内存,造成内存浪费

3.1.4原型改造构造函数

构造函数通过原型分配的函数是所有对象共享的

每一个构造函数都有一个prototype属性指向另一个对象。注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。

可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。

 function Person(name, age) {
     this.name = name;
     this.age = age;
 }
// 将方法定义到原型上
Person.prototype.speak = function () {
    console.log("hello world");
};
var p1 = new Person("李白", 10);
var p2 = new Person("杜甫", 8);
console.log(p1.speak === p2.speak); // true

3.2 静态成员和实例成员

3.2.1 实例成员

实例成员就是构造函数内部的成员 实例成员只能通过实例化的对象来访问,也就是说实例成员只跟每个对象实例有关,不同对象实例成员是没有任何关系的

 function Star(uname, age) {
     this.uname = uname;
     this.age = age;
     this.sing = function() {
     console.log('我会唱歌');
    }
}
var lyf = new Star('李易峰', 18);
console.log(lyf.uname);//实例成员只能通过实例化的对象来访问
3.2.2 静态成员

静态成员 在构造函数本身上添加的成员,与具体的对象无关

function Car(brand) {
    this.brand = brand;
    if (!Car.total) {
        Car.total = 0;
    }
    Car.total++;
}
var c1=new Car('五菱')
var c2=new Car('长安')
// 获取静态属性total
console.log(Car.total);

也可以通过实例来访问这个对象

3.3原型

3.3.1 prototype 与 proto
  • 构造函数有一个原型对象,通过 prototype 获取
  • 基于构造函数创建对象后,每个对象都有一个私有的 proto 属性,指向兑现的构造函数的原型对象
  • 基于上面两点,构造函数 prototype 属性与对象的 proto 属性指向的是同一个对象

__proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype

3.3.2 constructor构造函数

对象原型( proto)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。

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

如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数如:

 function Car(brand) {
            this.brand = brand // 实例属性            
        }

        Car.prototype = {
            constructor:Car,
            drive: function () {
                console.log('骑着我的小毛驴');
            },
            start: function () {
                console.log('start');
            },
            stop: function () {
                console.log('stop');
            },
            run: function () {
                console.log('run');
            }
        }
        console.log(Car.prototype)    
        var c1 = new Car()
        c1.drive()
3.3.3 原型链

每一个实例对象又有一个 __proto__属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有__proto__属性,这样一层一层往上找就形成了原型链。

JavaScript 对象是动态的属性“包”(指其自己的属性)。

JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

3.3.4 构造函数实例和原型对象三角关系
  • 构造函数的prototype属性指向了构造函数原型对象
  • 实例对象是由构造函数创建的,实例对象的__proto__属性指向了构造函数的原型对象
  • 构造函数的原型对象的constructor属性指向了构造函数

在这里插入图片描述

3.3.5 原型链和成员的查找机制

任何对象都有原型对象,也就是prototype属性,任何原型对象也是一个对象,该对象就有__proto__属性,这样一层一层往上找,就形成了一条链,我们称此为原型链;

当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
如果还没有就查找原型对象的原型(Object的原型对象)。
依此类推一直找到 Object 为止(null)。
__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
3.3.6 原型对象中this指向

构造函数中的this和原型对象的this,都指向我们new出来的实例对象

 /**
         * 1)在构造函数中,this 指代的是创建的构造函数的对象实例
         * 2)在构造函数的原型对象中,this 同样指代的是创建的构造函数的对象实例
         * 
         */ 
        function Car(bran) {
            this.brand = bran
            console.log(this);
        }
        Car.prototype.run=function(){
            console.log(this);
        }
        var c1 = new Car('五菱宏光')
        var c2 = new Car('长城皮卡')
        c1.run()
        c2.run()

4 继承

class 方式创建的类,可以非常轻松的实现继承

但是,要记住,class 语法只是 js 中的一种语法糖,其本质还是使用了基于原型的构造函数

使用 call 方法,就可以实现构造函数之间的继承

4.1 call方法

  • call 可以调用函数
  • call 可以修改this的指向,使用call()的时候 参数一是修改后的this指向,参数2,参数3…使用逗号隔开连接
4.1.1 调用函数
function f1(){
    console.log('f1');
}
f1.call()
4.1.2 修改this指向
function f1() {
    console.log(this); // Person
}
function Person() {
    this.name = name;
}
var p1 = new Person();
f1.call(p1);
4.1.3 子构造函数继承父构造函数中的属性

子对象会继承父对象中的属性和方法,这主要得力于子构造函数使用 call 方法调用父构造函数中的成员

  1. 先定义一个父构造函数
  2. 再定义一个子构造函数
  3. 子构造函数继承父构造函数的属性(使用call方法)
function Parent(name,age){
    console.log(this);
    this.name=name
    this.age=age
}
function Chinese(name,age,hukou){
    Parent.call(this)
    this.hukou=hukou
}

实例化 Parent 类时,this 为 Parent

var f=new Parent('杜甫',18)

实例化 Chinese 类时,this 为 Chinese

var c=new Chinese('李白',20,'河北保定')
4.1.4 借用原型对象继承方式
  1. 先定义一个父构造函数
  2. 再定义一个子构造函数
  3. 子构造函数继承父构造函数的属性(使用call方法)
// 1. 父构造函数
function Father(uname, age) {
  // this 指向父构造函数的对象实例
  this.uname = uname;
  this.age = age;
}
Father.prototype.money = function() {
  console.log(100000);
 };
 // 2 .子构造函数 
  function Son(uname, age, score) {
      // this 指向子构造函数的对象实例
      Father.call(this, uname, age);
      this.score = score;
  }
// Son.prototype = Father.prototype;  这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
  Son.prototype = new Father();
  // 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
  Son.prototype.constructor = Son;
  // 这个是子构造函数专门的方法
  Son.prototype.exam = function() {
    console.log('孩子要考试');

  }
  var son = new Son('刘德华', 18, 100);
  console.log(son);
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端大斗师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值