JS原型与继承

原型与继承

原型对象

每个对象都有一个原型prototype对象,通过函数创建的对象也将拥有这个原型对象。原型是一个指向对象的指针。

  • 原型类似其他面向对象语言中的父类(基类)
  • 所有对象的原型默认是Object的实例
  • 原型包含 constructor 属性,指向构造函数,所以可以通过一个实例的对象找到其构造函数创建另一同类型对象
  • 对象包含 __proto__ 指向他的原型对象,__proto__ 不是对象属性,理解为prototype 的 getter/setter 实现,他是一个非标准定义
  • 多个对象共同继承一个原型可以共享原型中的成员,实现代码复用,可以解决构建函数创建对象时复制多个函数造成的内存占用问题
  • 继承是为了复用代码,继承的本质是将原型指向到另一个对象
    默认情况下创建的对象都有原型,下面的代码展示数组对象的层次,其中包含了变量arr的原型Array的成员:
let arr = ["a"];
//dir()可以像文件目录一样打印对象
console.dir(arr);
123

输出:
原型
以下x、y的原型都为元对象Object

let a = {};
let b = {};
console.log(a,b);
123

输出:
原型

函数的原型

函数比较特殊,prototype 用于实例对象时使用,__proto__用于构造函数时使用。因为函数比较特殊,既可以被当做构造函数产生一个对象。也可以本身是function创建的一个实例对象。

function User(){};
let lisi = new User();
//通过User构造函数实例的lisi实例对象的__proto__直接指向Object
console.log(lisi.__proto__);
//构造函数的prototype指向Object
console.log(User.prototype);
console.log(lisi.__proto__ == User.prototype);
//构造函数的__proto__指向其父亲 function
console.log(User.__proto__);
123456789

输出:
原型
关系图如下:
js原型

构造函数与原型对象

下面是使用构造函数创建对象的原型体现:

  • 构造函数拥有原型对象,利用prototype属性访问
  • 构造函数在创建对象时把原型赋予对象
  • 对象原型在配置后可以通过constructor(指向构造函数)访问构造函数
  • 实例对象可以直接通过__proto__访问对象原型
  • 可以通过实例对象的constructor访问构造函数,但是constructor本质上是对象原型的属性
    JS原型
    下面的示例展示利用对象实例arr找到其原型对象再利用其构造函数创建一个新的对象
let arr = new Array([1,2,3,4,5]);
//利用对象实例arr找到其原型对象再利用其构造函数创建一个新的对象
let newArr = new arr.__proto__.constructor([6,7,8,9,10]);//new可以省略
console.table(newArr);
1234

输出:
js原型

原型链

在前面我们可以了解到,JS中大部分的数据类型其实都是对象类型的。如果仔细看上面代码中Array构造的原型其实就是Object类型的一个实例对象,可以使用__proto__属性访问到Object

执行console.log(Array.prototype);可以在控制台看到Array的原型对象中的__proto__指向Obeject的构造的原型对象
js原型
同时也就意味可以直接通过Array.prototype.__proto__访问Object的原型对象

有如下的关系图:
在这里插入图片描述
在上面的橙色标记的引用链中,通过引用类型的原型,继承另一个引用类型的属性与方法,这也是实现继承的步骤。

创建原型链

  • 使用Object.setPrototypeOf 可设置对象的原型。下面的例子将创建一条原型链,使obj3继承obj2,obj2继承obj1obj3将同时拥有三者的属性。
let obj1 = {
    prop1 : "obj1" 
}
let obj2 = {
    prop2 : "obj2"
}
let obj3 = {
    prop3 : "obj3"
}
//继承关系如下 obj3 -> obj 2 -> obj1
Object.setPrototypeOf(obj3,obj2);
Object.setPrototypeOf(obj2,obj1);

console.log(obj3.prop1);//输出:obj1
console.log(obj3.prop2);//输出:obj2
console.log(obj3.prop3);//输出:obj3
12345678910111213141516
  • 采用构造函数直接赋值的方式也可以创建原型链:使C的原型继承B的原型,B的原型继承A的原型
function A(){};
function B(){};
function C(){};

let a = new A();
B.prototype = a;
let b = new B();
C.prototype = b;
let c = new C();
123456789
  • 使用Object.create创建一个新对象时使用现有对象做为新对象的原型对象
let a ={};
//使b继承于a
let b = Object.create(a);
123

原型检测

instanceof 关键字可以用来检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,即会向上对比是否为该类型。

function A(){};
function B(){};
function C(){};

let a = new A();
B.prototype = a;
let b = new B();
C.prototype = b;
let c = new C();

console.log(c instanceof B);//true
console.log(c instanceof A);//true
console.log(b instanceof A);//true
12345678910111213

使用isPrototypeOf可以检测一个对象是否在另一个对象的原型链中,接上面的代码:

console.log(a.isPrototypeOf(b));//ture
console.log(a.isPrototypeOf(c));//ture
console.log(b.isPrototypeOf(c));//ture
123

属性遍历

in关键字会对原型链上所有属性描述enumerableture的属性进行遍历(向上攀升),使用 hasOwnProperty可以判断对象中属性是否为自有属性,即非继承来的属性。

let a ={
    name1:"a"
}
let b = {
    name2:"b"
}
let c = {
    name3:"c"
}
//继承关系 c -> b -> a (->:继承)
Object.setPrototypeOf(c,b);
Object.setPrototypeOf(b,a);

for (const key in c) {
    console.log(key);
}
/*输出:name3 name2 name 1 */

for (const key in c) {
    if (c.hasOwnProperty(key)) {
        console.log(key)
    }
}/*输出: name 3*/
1234567891011121314151617181920212223

借用原型

callapply可以改变函数体内的this指针,从而可以借用其他对象的方法来完成功能。根据传入的对象不同,两者都可以改变函数体中this的值,两者的差别在于call传入零散的参数,而apply传入一个参数数组。

let exam = {
    score: new Map([["C/C++",90],["Java",87],["Js",99]]),
    average : function(){
        let s = Array.from(this.score.values());
        let sum = s.reduce((total,value) => total+=value);
        return sum/s.length;
    }
}
console.log(exam.average()); // 92

let game = {
    score: [100,99,200,123,213]
}
//game对象没有average方法,但是可以借用exam的完成平均数的计算
console.log(exam.average.call(game)); // 147
123456789101112131415

原型工厂

原型工厂是将继承的过程封装,使用继承业务简单化

//使sub构造继承sup构造
function extend(sub,sup){
    //继承原型
    sub.prototype = Object.create(sup);
    //定义构造函数,防止构造函数地址丢失
    sub.prototype.constructor = sub;
}
1234567

对象工厂

在原型对象的基础上可以拓展到对象工厂,即子类的构造函数的创建。

function A(name,age){
    this.name = name;
    this.age = age;
}
A.prototype.show = function(){
    console.log(this.name + " " + this.age);
}
function B(name,age){
    let instance = Object.create(A.prototype);
    //复用A的构造函数,执行B的实例化
    A.call(instance,name,age);
    //额外添加属性
    instance.newProp = " B ";

    return instance;
}

let b = new B("lisi",20);
b.show();//lisi 20
12345678910111213141516171819

混合模式

JS不能实现多继承,如果要使用多个原型的方法时可以使用mixin混合模式来完成。在JS中,使用Object.assign来让需要继承的多个对象进行合并,以实现需要使用到多个类的方法的情况。JavaScript对象

JavaScript 对象

对象是包括属性与方法的数据类型,在JS中大部分数据类型其实都是对象,例如:String/Number/Math/RegExp/Date。对象和函数、数组一样,都是引用类型。

对象的声明

JS中的对象声明方式有如下几种
(1) 使用字面量形式声明对象是最简单的方式,在JS中使用一对花括号{}代表对象类型。

 let zhangSan = 
 {
    fristName: "张",
    lastName: "三",
    age: 19,
    getName : function()
    {
        return `${this.fristName}${this.lastName}`;
    }
}
12345678910

(2)使用构造函数方式创建对象

 function User(firstName,lastName,age)
{
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
}
let lisi = new User("李","四",22);
1234567

(3)其实字面量形式内部也是通过使用构造函数 new Object创建的

let zhangSan = new Object();
1

属性和方法

js中的属性是这个对象的属性即对象中定义的变量,方法是这个对象所拥有的功能即对象中的函数,拿上面的对象zhangSan为例,fristName、lastName、age就是对象的属性,而getName 就是方法。JS中定义属性和方法采用 : 来给对象的属性和方法初始化,:左边是属性(方法)名,右边是值。属性和方法统称为对象的成员。

成员的访问

(1)JS中可以用.来访问对象中的成员

let zhangSan = 
{
    name : "张三",
    age : 22,
    getName: function()
    {
        return this.name;
    }
}
//访问属性
console.log(zhangSan.name);//张三
//访问方法
console.log(zhangSan.getName());//张三
12345678910111213

(2)JS中还可以使用[]访问成员

//访问属性
console.log(zhangSan["name"]);//张三
//访问方法
console.log(zhangSan["getName"]());//张三
1234

使用.操作成员更简洁,而当成员名不是合法变量的情况下就只能采用[]语法

let zhangSan = 
{
    name : "张三",
    age : 22,
    getName: function()
    {
        return this.name;
    }
}
zhangSan["a b"] = "这是不合法的变量名";
//变量名中含有空格,只能通过[]访问
console.log(zhangSan["a b"]);//这是不合法的变量名
123456789101112

添加和删除

对象和方法的属性可以动态的添加或删除。

  • 对于已经存在的属性,如果没有进行权限和作用域的设定,值将被覆盖。
let zhangSan = 
{
    name : "张三",
    age : 22,
    getName: function()
    {
        return this.name;
    }
}
//对一个已经存在的属性赋值将覆盖掉该属性的值
zhangSan.name = "李四";
console.log(zhangSan.name);//李四
123456789101112
  • 对于不存在的属性进行赋值,该属性将被创建,可以利用这个特性后天给对象添加属性
let zhangSan = 
{
    name : "张三",
    age : 22,
    getName: function()
    {
        return this.name;
    }
}
//对一个不存在的属性赋值将添加该属性
zhangSan.birthday = "1998/04/15"
console.log(JSON.stringify(zhangSan,null,2));
123456789101112

输出:

{
  "name": "张三",
  "age": 22,
  "birthday": "1998/04/15"
}
12345
  • 也可以使用Object.assign进行赋值操作
 "use strict";
let zhangSan = {name:"张三",age : 22};
zhangSan = Object.assign(zhangSan,{age : 23},{sex : "男"});
console.log(JSON.stringify(zhangSan,null,2));
1234

输出:

{
  "name": "张三",
  "age": 23,
  "sex": "男"
}
12345

JSON.stringify(zhangSan,null,2)可以将对象zhangSan转换为json格式数据,并设置缩进值为2,可以直观输出js对象

  • 删除成员可以使用关键字delete
//接上面的代码
delete zhangSan.birthday;
console.log(JSON.stringify(zhangSan,null,2));
123

输出:

{
    "name": "张三",
    "age": 22
}
1234

引用类型特性

对象和函数、数组(本质上它们也是对象)一样,都是引用类型,引用类型进行复制时只会复制引用。所谓复制只会引用地址,即当对象被赋值时,只是把对象所在内存的引用复制了一份,而两个引用共有一块内存。不难理解,当其中一个引用操作其成员,会导致其他引用的一起改变。
值类型进行赋值时会在内存中对原数据进行赋值,看似两个的值是相同的,实则保存在同一块内存

let zhangSan = 
{
    name : "张三",
    age : 22,
    getName: function()
    {
        return this.name;
    }
}
let zhangSan_1 = zhangSan;
zhangSan_1.age = 18;
console.log(JSON.stringify(zhangSan,null,2));
123456789101112

输出:

{
  "name": "张三",
  "age": 18
}
1234

当我们修改zhangSan_1的值,zhangSan的值也跟着一起改变了

this

this 指当前对象的引用,不同环境下的this指向不同的内容,this只指向当前对象。this是为了解决下列的问题:

  • 当对象成员函数的形参名与成员属性名相同时,浏览器无法理解该名称是代表谁
  • 看下面的代码,当删除了birthday 变量,但函数体内还在使用造成错误,而使用了this就不会出现错误。
let zhangSan = 
{
    name : "张三",
    birthday : new Date("1998/12/25"),
    getAge : function()
    {
        let age = new Date().getFullYear() - this.birthday.getFullYear();
        return age;
    }
   
}
console.log(zhangSan.getAge()); //22
123456789101112
  • 在严格模式下方法中的全局的this值为undefined,这是为了防止无意的修改window对象

展开语法

展开语法不仅可以用于类数组序列,还可以用于对象。使用...可以展示对象的结构

let zhangSan = 
{
    name : "张三",
    birthday : new Date("1998/12/25"),
    getAge : function()
    {
        let age = new Date().getFullYear() - this.birthday.getFullYear();
        return age;
    }
   
}
let new_zhangSan = {...zhangSan}
console.log(JSON.stringify(new_zhangSan,null,2));
12345678910111213

输出:

{
  "name": "张三",
  "birthday": "1998-12-24T16:00:00.000Z"
}
1234

解构赋值

基本用法

解构是一种更简洁的赋值特性,可以将对象对应成员赋值给左值。

let zhangSan = 
{
    name : "张三",
    birthday : new Date("1998/12/25"),
    getAge : function()
    {
        let age = new Date().getFullYear() - this.birthday.getFullYear();
        return age;
    }
   
}
//使用 : 运算符 左边为右值对象成员,右边为接收的变量名
let {name:MyName,birthday:MyBirthday} = zhangSan;
//如果接收的变量名与右值的对应成员名相同,可以省略:
let {name,birthday} = zhangSan;
console.log(name,birthday);//张三,"1998-12-24T16:00:00.000Z"
12345678910111213141516

函数解构

函数返回值直接解构到变量

function getZhangSan(birthDate) 
{ 
   
   let age = new Date().getFullYear() - birthDate.getFullYear();
   return {
        name : "张三",
        age
   }
}

let {name,age} = getZhangSan(new Date("1998/03/15"));
console.log(name,age);//张三 22
123456789101112

解构语法可以直接使用在函数传参上

function getZhangSan({name,birthDate}) 
{ 
   
   let age = new Date().getFullYear() - birthDate.getFullYear();
   return {
        name,
        age
   }
}

let ZhangSan = {name : "张三",birthDate : new Date("1998/03/22")}
let newZhangSan = getZhangSan(ZhangSan);
console.log(newZhangSan.age) // 22
12345678910111213

遍历对象

keys/values/entries

使用系统提供的Object静态方法可以方便获取对象属性与值。

let ZhangSan = {name : "张三",birthDate : new Date("1998/03/22")}
console.log(Object.keys(ZhangSan));
console.log(Object.values(ZhangSan));
console.log(Object.entries(ZhangSan));
1234

输出:
keys,values

for/in和for/of

对象也可以使用for/infor/of进行遍历,for/of用于遍历迭代对象,不能直接操作对象。但Object对象的keys/values/entries方法返回的是迭代对象。

"use strict";
let zhangSan = {name:"张三",age : 22};
for (const key in zhangSan)
{
    console.log(key);//name age
}
123456
"use strict";
let zhangSan = {name:"张三",age : 22};
for (const key of Object.keys(zhangSan)) {
    console.log(key); // name age
}
for(const value of Object.values(zhangSan)){
    console.log(value); // 张三 22
}
12345678

对象拷贝

因为对象的引用类型特性,直接将对象赋值给另一对象会导致两个对象引用同一内存。改变其中一个的属性或方法会牵连另一对象。如果要实现两个对象互不牵连可以使用浅拷贝深拷贝两种方式进行对象的拷贝。浅拷贝不会将对象的嵌套数据和引用类型进行拷贝。

浅拷贝

(1)使用展开语法 ...进行拷贝

"use strict";
let zhangSan = {name : "张三",age : 30}
let lisi = {...zhangSan}
123

(2)使用for/in语法对对象进行遍历拷贝

"use strict";
let zhangSan = { name: "张三", age: 30 }
let lisi = {}
for (const key in zhangSan) {
    lisi[key] = zhangSan[key];
}
123456

(3) Object.assign 函数可简单的实现浅拷贝,它是将两个对象的属性叠加后面对象属性会覆盖前面对象同名属性。

"use strict";
let zhangSan = { name: "张三", age: 30 }
let lisi  = Object.assign({},zhangSan)
123

深拷贝

浅拷贝不会将深层的数据拷贝,要进行深层次的拷贝就需要用到深拷贝。深拷贝利用递归算法进行对象的层层拷贝,可以进行深层次的拷贝

使用浅拷贝后,虽然第一层的内容进行了拷贝,但是第二层的对象zhangSan里的还是牵连在了一起.

"use strict";
let obj ={
    name : "张三",
    zhangSan : {
        age : 20
    }
}
//直接进行浅拷贝
let obj_1 = {...obj}
obj_1.zhangSan.age = 30;
console.log(JSON.stringify(obj,null,2));
console.log(JSON.stringify(obj_1,null,2));
123456789101112

输出:

{
  "name": "张三",
  "zhangSan": {
    "age": 30
  }
} 

{
  "name": "张三",
  "zhangSan": {
    "age": 30
  }
}
12345678910111213

使用递归进行深拷贝:

let oj = {
    name : "obj1",
    obj2 : {
        name : "obj2"
    }
}
function copy(object){
    //因为数组也是引用类型,兼容数组的复制
    let obj = object instanceof Array ? [] : {};
    for(const [key,value] of Object.entries(object)){
        //如果对象成员是引用类型,再次递归拷贝,否则直接结束递归
        obj[key] = typeof value == "object" ? copy(value) : value;
    }
    return obj;
}
let object = copy(oj);
console.log(JSON.stringify(object,null,2));
1234567891011121314151617

输出:

{
  "name": "obj1",
  "obj2": {
    "name": "obj2"
  }
}
123456

属性特征

三类对象两类属性

  • 内置对象(native object) 是由ECMScript规范定义的对象或者类。例如:函数,数组,日期,正则…
  • 宿主对象(host object) 是由js编译器所嵌入的宿主环境(web浏览器)所定义的。比如客户端js中表示网页结构的HTMLElement对象就是宿主环境创建的对象。宿主环境定义的对象可以直接使用的话,我们也可以把它们当做内置对象。
  • 自定义对象(user-defined object) 由运行中的js创建的对象。
  • 自有属性(own property) 直接在对象当中定义的属性,区别于继承属性。
  • 继承属性(inherited property) 在对象原型中定义的属性。

属性描述

JS中可以对属性的访问特性进行控制。

描述说明默认值
configurable能否使用delete、能否需改属性特性、或能否修改访问器属性true
enumerable对象属性是否可通过for-in循环,或Object.keys() 读取true
writable对象属性是否可修改true
value对象属性的默认值undefined

使用 Object.getOwnPropertyDescriptor查看对象属性的描述。

let zhangSan = {
    name : "张三",
    age  : 30
}
let desc = Object.getOwnPropertyDescriptor(zhangSan,"name");
console.log(JSON.stringify(desc,null,2));
123456

输出:

{
  "value": "张三",
  "writable": true,
  "enumerable": true,
  "configurable": true
}
123456

使用 Object.getOwnPropertyDescriptors查看对象所有属性的描述

let zhangSan = {
    name : "张三",
    age  : 30
}
let desc = Object.getOwnPropertyDescriptors(zhangSan,"name");
console.log(JSON.stringify(desc,null,2));
123456

设置属性描述

使用Object.defineProperty 方法修改属性描述的值

//设置zhangSan name属性的属性描述
Object.defineProperty(zhangSan,"name",{
    value : "未命名", // 设置name的默认值为未命名
    writable: false, // 设置name为只读
    enumerable : false,// 设置name属性不允许遍历
    configurable : false,// name字段不允许访问 
});
1234567

使用 Object.defineProperties 可以一次设置多个属性,具体参数和上面介绍的一样。

属性封闭

除了上面的直接控制对象属性的操作权限,js还封装了一些方法给我们封闭对象的属性

封闭对象
Object.seal()`方法封闭一个对象,阻止添加新属性并将所有现有属性标记为 `configurable: false
冻结对象
Object.freeze` 冻结对象后不允许添加、删除、修改属性`,writable、configurable`都标记为`false
禁止添加

Object.preventExtensions 禁止向对象添加属性

属性访问器

属性访问器有两种:getter/setter
getter方法用于获得属性值,setter方法用于设置属性,这是JS提供的存取器特性即使用函数来管理属性。

属性访问器的特点

  1. 当程序查询访问器属性值时,js调用getter方法。这个方法返回的就是属性存取表单式的值。
  2. 当程序设置访问器属性值是,js调用setter方法。这个方法将赋值表达式右边的计算结果,传入setter。
  3. 访问器属性不具有可写性,如果同时具有getter和setter方法那它就是一个读写属性。也可以只拥有其中一个,来作为只读或者只写属性。

下面的代码将展示如何对一个用户对象设置其年龄属性的访问器监控:

let user = {
    data : {
        name : "张三",
        age : 20
    },
    set age(value){
        if(typeof value != "number" || value < 0 || value % 1 != 0){
            //如果设置的年龄不是数字,或 小于 0 或不是整数,将报错
            throw new Error("年龄不合法");
        }
        else{
            this.data.age = value 
        }
    },
    get age(){
        return this.data.age;
    }
}
console.log(user.age);//20
user.age = 12.5;//Uncaught Error: 年龄不合法

为了和其他语言形态一致,ES6提供了class 关键词作为原型链继承的语法糖来模拟其他主流语言中的类的继承形态,简化了原型的定义的过程。

  • class只是JS提供的一个语法糖,优化并且简化了原型继承
  • class 语法定义的成员间无需使用逗号
  • 类的本质是函数
  • 类的属性可以定义在构造函数中也可以直接定义在class中
  • 定义在类中的属性无需使用this,方法无需使用thisfunction,直接使用方法签名+()的形式
  • class 中定义的方法直接定义在其原型中且不能枚举
  • class 默认使用strict 严格模式执行
  • 使用static关键字设置静态属性和静态方法
  • 类中可以使用setget 访问器对属性进行访问控制
  • class中使用 extends 关键字进行 继承
  • super 指向上一基类,定义构造函数必须优先调用super()以调用基类构造

定义语法

定义一个类User,注意类的定义中,成员之间无需逗号分隔,这点与原型不同。constructor对象指向其构造函数,this指向其实例对象本身。

class User{
    constructor(name){
        this.name = name;
        this.show = function(){
            console.log("User -> " + this.name)
        }
    }
    //无需逗号
    getName(){
        return this.name;
    }
}
123456789101112

使用new关键字创建一个实例对象,constructor 会在 new 时自动执行

let lisi = new User("lisi");
1

类的本质是一个函数对象,所以可以跟构造方法一样使用new关键字调用其constructor创建实例对象

console.log(User.__proto__) // function()
1

所以下面的两段代码的实现效果是一样的

class User {
    constructor(name){
        this.name = name;
    }
    getName() {
        return this.name;
    }
}
12345678
function User(name){
    this.name = name;
}
User.prototype.getName = function(){
   return this.name;
}
123456

定义属性

类的属性定义有如下两种方法,可以定义在构造函数中也可以直接定义在类中,定义在类中的属性无需使用thisfunction定义,直接使用方法签名+()的形式,定义在

class User{
    constructor(){
        this.age = 20;
        this.func1 = function(){
            return "func1()";
        }
    }
    name = "张三";
    func3(){
        return "func3";
    }
}
123456789101112

class 中定义的方法直接定义在其原型中且不能枚举

class User {
    constructor(name) {
        this.name = name;
    }
    func() {
        console.log("func");
    }
}
User.prototype.func(); // func
let pd = Object.getOwnPropertyDescriptor(User.prototype,"func");
console.log(pd.enumerable); // false
1234567891011

静态属性

静态属性即为类设置属性,而不是为生成的对象设置,在JS中使用static关键字设置

class User{
    constructor(){};
    static name = "User's name";
}
let john = new User();
console.log(john.name);//undefined
console.log(User.name);//User's name
1234567

静态方法

与静态属性类似,指通过类访问而不能通过实例对象访问的方法

class User{
    constructor(){
    };
    static name = "User's name";
    static show(){
        console.log(this.name);
    }
}
let john = new User();
User.show(); // User's name
john.show(); // 报错!! 方法不存在
1234567891011

访问器

使用访问器可以对对象的属性进行访问控制

class User {
    constructor(name, age) {
        this.data = { name, age }
    }
    set age(value) {
        if (typeof value != Number || value <= 0)
            console.error("年龄不合法");
        this.data.age = value;
    }
    get age() {
       return this.data.age;
    }
}
let peter = new User("peter",20);
peter.age = -20; // 报错 : 年龄不合法
123456789101112131415

继承

使用 extends 关键字进行 继承,super 指向上一基类构造,在定义constructor时必须优先调用父类构造,即super()

class Preson{
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    show(){
        console.log(`${this.name}  ${this.age}`);
    }
}

class User extends Preson{
    constructor(name,age,sex){
        super(name,age);
        this.sex = sex
    }
    UserShow(){
        console.log(`${this.name} ${this.age} ${this.sex}`);
    }
}
let user = new User("张三",20,"男");
user.show();//张三  20 
user.UserShow();//张三 20 男
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值