js-类与对象
@
1. 类与对象
类#
在 ES6 中新增加了类的概念,可以使用 class
关键字声明一个类,之后以这个类来实例化对象。类抽象了对象的公共部分,它泛指某一大类(class)对象特指某一个,通过类实例化一个具体的对象
创建类#
- 语法:
//步骤1 使用class关键字
class name {
// class body
}
//步骤2使用定义的类创建实例 注意new关键字
var xx = new name();
- 示例
// 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] }
构造函数-实例-原型对象的三角关系#
原型链#
每一个实例对象又有一个__proto__
属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有__proto__
属性,这样一层一层往上找就形成了原型链。
原型链和成员的查找机制#
① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
② 如果没有就查找它的原型(也就是 __proto__
指向的 prototype 原型对象) 。
③ 如果还没找到有就查找原型对象的原型( Object的原型对象) 。
④ 依此类推一直找到 Object 为止( null)。
⑤ __proto__
对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
原型对象this指向#
- 构造函数中的this 指向当前new的实例对象
- 原型对象里面放的是方法, 这个方法里面的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
注意:
-
继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的;
如果没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
-
子类在构造函数中使用super, 必须放到 this 前面 (必须先调用父类的构造方法,在使用子类构造方法)
-
注意this的指向问题,类里面的共有的属性和方法一定要加this使用。
- constructor中的this指向的是new出来的实例对象
- 自定义的方法,一般也指向的new出来的实例对象
- 绑定事件之后this指向的就是触发事件的事件源
-
在 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);
有关继承的一些疑问
- 如下代码
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 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix