js-类与对象

@


1. 类与对象

#

在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实例化对象。类抽象了对象的公共部分,它泛指某一大类(class)对象特指某一个,通过类实例化一个具体的对象

创建类#

  1. 语法:
//步骤1 使用class关键字
class name {
  // class body
}     
//步骤2使用定义的类创建实例  注意new关键字
var xx = new name();     
  1. 示例
 // 1. 创建类 class  创建一个 明星类
 class Star {
   // 类的共有属性放到 constructor 里面
   constructor(name, age) {
   this.name = name;
   this.age = age;
   }
 }
// 2. 利用类创建对象 new
var ldh = new Star('马云', 18);
console.log(ldh);
//输出结果:Star{name: '马云', age: 18}

类创建添加属性和方法#

 // 1. 创建类 class  创建一个类
class Star {
    // 类的共有属性放到 constructor 里面 , constructor是 构造器或者构造函数
    constructor(uname, age) {
      this.uname = uname;
      this.age = age;
    }//注意,方法与方法之间不需要添加逗号
    sing(song) {
      console.log(this.uname + '唱' + song);
    }
}
// 2. 利用类创建对象 new
var ldh = new Star('刘德华', 18);
console.log(ldh); // Star {uname: "刘德华", age: 18}
ldh.sing('冰雨'); // 刘德华唱冰雨

注意:

语法规范

  • 通过class关键字创建类, 类名我们还是习惯性定义首字母大写

  • 创建类 类名后面不要加小括号,生成实例 类名后面加小括号

  • 函数不需要加 function,多个函数方法之间不需要添加逗号分隔

构造函数

  • 类里面有个constructor 函数,可以接受传递过来的参数,同时返回实例对象
  • constructor 函数 只要 new 生成实例时,就会自动调用这个函数, 如果不写这个函数,类也会自动生成这个函数


对象#

对象特指某一个,通过类实例化一个具体的对象 ,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等,对象是由属性和方法组成的。

创建对象#

//1. 字面量创建对象
var ldh = {
    name: '马云',
    age: 18
}
console.log(ldh);
//输出结果: {name: '马云', age: 18}

//2. 构造函数创建对象
  function Star(name, age) {
    this.name = name;
    this.age = age;
    this.sing = function() {
        console.log("我会唱歌");
    }
 }
var my = new Star('马云', 18);//实例化对象
var mht = new Star('马化腾', 18);//通过构造函数可创建多个对象
console.log(my);
//输出结果: Star{name:'马云', age:18}
my.sing();//输出:我会唱歌


2. constructor 构造函数

constructor() 方法是类的构造函数(默认方法), 用于传递参数,返回实例对象, 通过 new 命令生成对象实例时,自动调用该方法。

如果没有显示定义, 类内部会自动给我们创建一个constructor(){};

语法:

class Person {
	constructor(name,age) { // constructor 构造方法或者构造函数
		this.name = name;
		this.age = age;
	}
}

//创建实例
var ldh = new Person('john', 18);
console.log(ldh.name);//john


3. 静态成员和实例成员

实例成员#

实例成员就是构造函数内部通过this添加的成员, 实例成员只能通过实例化的对象来访问,实例化对象成员。

在普通构造函数中:

 function Star(uname, age) {
     //uname, age, sing 都为实例成员
     this.uname = uname;
     this.age = age;
     this.sing = function() {
     console.log('我会唱歌');
    }
}
var ldh = new Star('刘德华', 18);
console.log(ldh.uname);//实例成员只能通过实例化的对象来访问

在类里面实例成员是如何定义的,它里面哪些属于实例成员?


静态成员#

静态成员 在构造函数本身上添加的成员 ,静态成员只能通过构造函数来访问

在普通构造函数中:

 function Star(uname, age) {
     this.uname = uname;
     this.age = age;
     this.sing = function() {
     console.log('我会唱歌');
    }
}
//添加静态成员: 构造函数名.成员名 = 值
Star.sex = '男';
Star.fn = function(){};
var ldh = new Star('刘德华', 18);
//静态成员只能通过构造函数来访问
console.log(Star.sex);
Star.fn();

静态成员的显示定义方式还有哪些?

在class类中静态成员又是如何定义?如何调用?



4. 原型

问题引入: 普通构造函数浪费内存的问题#

请添加图片描述

如上图所示, 每实例化一个对象, 每个对象都要分别单独存储一个function,如果实例化多个对象就会造成内存浪费问题

解决方法: 为了节省内存, 如何才能使每个对象都共用这个方法?


构造函数原型 prototype#

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

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

比如上述的Star构造函数

请添加图片描述

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

示例代码

function Star(uname, age) {
    this.uname = uname;
    this.age = age;
}
Star.prototype.sing = function() {
	console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
//对象访问构造函数原型对象的方法
ldh.sing();//输出: 我会唱歌
zxy.sing();//输出: 我会唱歌
console.log(ldh.sing === zxy.sing)//输出: true

另外有关class定义的类里也有prototype, 同样可通过类名修改或查询原型对象里的内容


对象原型__proto__#

对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象(系统自动生成的), 之所以对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在。

function Star(uname, age) {
    this.uname = uname;
    this.age = age;
}
Star.prototype.sing = function() {
    console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
ldh.sing();
console.log(ldh); 
console.log(ldh.__proto__ === Star.prototype);
// 方法的查找规则: 首先先看ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的sing
// 如果没有sing 这个方法,因为有__proto__ 的存在,就去构造函数原型对象prototype身上去查找sing这个方法
  • __proto__对象原型和原型对象 prototype 是等价的
  • __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype

constructor构造函数#

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

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

一般情况下,对象的共有方法都在构造函数的原型对象中设置。 如果对象有多个共享方法,可以给构造函数的原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。此时,可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。

function Star(name, age){
    this.name = name;
    this.age = name;
}
Star.prototype = {
     // 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
    constructor: Star,
    sing: function(){
        console.log("star can sing song");
    },
	movie: function(){
        console.log("star can play movie");
    }
}
var zxy = new Star("张学友", 19);

console.log(zxy);

以上代码运行结果,设置constructor属性如图:

请添加图片描述

如果未设置constructor属性,如图:

请添加图片描述

//如果没有指定constructor,那么就无法获取到
console.log(ldh.__proto__.constructor);
//此时输出结果为: ƒ Object() { [native code] }

构造函数-实例-原型对象的三角关系#

A.prototype

A.prototype.constructor

a.proto

构造函数 A

A的 原型对象prototype

实例对象 a


原型链#

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

A.prototype

A.prototype.constructor

a.proto

A.prototype.proto

Object.prototype.constructor

Object.prptotype

Object.prototype.proto

构造函数 A

A的 原型对象prototype

实例对象 a

Object 原型对象prototype

Object 构造函数

null 顶层


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

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


原型对象this指向#

  1. 构造函数中的this 指向当前new的实例对象
  2. 原型对象里面放的是方法, 这个方法里面的this 指向的是 这个方法的调用者, 也就是这个实例对象

扩展内置对象#

可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给数组增加自定义求偶数和的功能。

Array.prototype.sum = function() {
    var sum=0;
    for(var i=0; i<this.length; i++){
        sum += this[i];
    }
    return sum;
}
console.log(Array.prototype);

输出如下

请添加图片描述

可见添加成员高亮显示

注意:数组和字符串内置对象不能给原型对象覆盖操作 Array.prototype = {} ,只能是 Array.prototype.xxx = function(){} 的方式。


5. 类的继承

ES6之后的继承#

语法:

class Father{ // 父类
}
class Son extends Father { // 子类会继承了父类的属性和方法  
}

示例:

class Father {
      constructor(surname) {
        this.surname= surname;
      }
      say() {
        console.log('你的姓是' + this.surname);
       }
}

class Son extends Father{  // 这样子类就继承了父类的属性和方法
}
var damao= new Son('刘');
damao.say();      //结果为:你的姓是刘


super 关键字#

super 关键字用于访问和调用对象父类上的成员。 比如可以调用父类的构造函数,也可以调用父类的普通函数

 class Person { // 父类
        constructor(surname) {
            this.surname = surname;
        }
        showName() {
            console.log(this.surname)
        }

    }
    class Student extends Person { // 子类继承父类
        constructor(surname, firstname) {
            // 利用super 调用父类的构造函数 super 必须在子类this之前调用,放到this之后会报错
            super(surname); // 调用父类的constructor(surname)
            this.firstname = firstname; // 定义子类独有的属性
        }
        showName() {
            super.showName();
            console.log(this.firstname);
        }
        say() {
            console.log('hello');
        }
    }

    var stu = new Student('p', 's');
    stu.showName(); //调用子类中的showname(), 先输出 p  后输出 s
    stu.say(); //调用父类的say(), 输出hello

注意:

  1. 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的;

    如果没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)

  2. 子类在构造函数中使用super, 必须放到 this 前面 (必须先调用父类的构造方法,在使用子类构造方法)

  3. 注意this的指向问题,类里面的共有的属性和方法一定要加this使用。

    • constructor中的this指向的是new出来的实例对象
    • 自定义的方法,一般也指向的new出来的实例对象
    • 绑定事件之后this指向的就是触发事件的事件源
  4. 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象



ES6之前的继承#

ES6之前并没有给我们提供 extends 继承。我们可以通过构造函数+原型对象模拟实现继承, 被称为组合继承

call( )#

函数调用call(),该函数被执行, 并且修改该函数运行时的 this 指向,该函数可接收 call() 中的参数。

fun.call(thisArg, arg1, arg2, ...)
  • thisArg :调用 call() 的函数中的this的指向对象
  • arg1, arg2:传递的其他参数

案例代码:

function fn(x, y) {
	console.log('hello world');
	console.log(this);
	console.log(x + y);
}
var o = {
	name: 'andy'
};
//1. fn函数被调用
//2. 修改fn函数中this的指向欸o对象
//3. 添加参数到fn函数中
fn.call(o, 1, 2);

借用构造函数继承父类型属性#

核心原理: 通过 call() 把父类型的 this 指向子类型的 this ,这样就可以实现子类型继承父类型的属性。

// 父类
function Person(name, age, sex) {
	this.name = name;
	this.age = age;
	this.sex = sex;
}
// 子类
function Student(name, age, sex, score) {
	Person.call(this, name, age, sex); // 此时父类的 this 指向子类的 this,同时调用这个函数
	this.score = score;
}
var s1 = new Student('zs', 18, '男', 100);
console.dir(s1);

输出如下:

请添加图片描述


借用原型对象继承父类型方法#

一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。
核心原理:
① 将子类所共享的方法提取出来,让子类的 prototype 原型对象 = new 父类()
② 本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象
③ 将子类的 constructor 从新指向子类的构造函数

案例代码

 // 借用父构造函数继承属性
 // 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);
 console.log(Father.prototype);
 console.log(Son.prototype.constructor);

请添加图片描述

有关继承的一些疑问

  1. 如下代码
 class Father {
        constructor(surname) {
            this.surname = surname;
        }
        say() {
            console.log('你的姓是' + this.surname);
        }
    }

    class Son extends Father { // 这样子类就继承了父类的属性和方法
    }
    var damao = new Son('刘');
    damao.say(); //结果为:你的姓是刘

由上可知,class son 的默认构造函数肯定调用了 class father 里的构造函数,那么子类的构造函数时怎样的?如何调用父类的带参构造函数



作者:Hong•Guo

出处:https://www.cnblogs.com/ghnb1/p/15848435.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Hong•Guo  阅读(297)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示