Nodejs笔记--对象模型

本文探讨JavaScript的面向对象特性,基于原型而非类。讲解了JS对象模型,包括字面量声明、ES6之前的构造器和ES6中的class,特别是class的重写方法、静态方法以及this的使用,包括显示传入、apply、call和bind方法,以及ES6引入的箭头函数对this的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JS对象模型

JavaScript 是一种基于原型(Prototype)的面向对象语言,而不是基于类的面向对象语言。
C++、Java有类Class和实例Instance的概念,类是一类事物的抽象,而实例则是类的实体。
JS是基于原型的语言,它只有原型对象的概念。原型对象就是一个模板,新的对象从这个模板构建从而获取最初的属性。任何对象在运行时可以动态的增加属性。而且,任何一个对象都可以作为另一个对象的原型,这样后者就可以共享前者的属性

定义类

字面式声明方式

var obj = {
    x: 1,
    1: 'abc',
    "y": '123'
}

for (let x in obj)
    console.log(x, typeof x);

/*运行结果:
1 string
x string
y string
*/

这种方法称作字面值创建对象

let a = 1, b = 2
let obj = {a, b}
let obj1 = {a: a, 'b': b} // 引号可以省去
console.log(obj)
console.log(obj1)

let c = 'str1'
let d = {
    c: 100, // 这个c是字符串'c', 不是上面的变量c
    [c]: 200 // 如果使用c变量, 就要用中括号括起来
}
console.log(d)

/*运行结果:
{ a: 1, b: 2 }
{ a: 1, b: 2 }
{ c: 100, str1: 200 }
*/

对象的键key只能是字符串类型, 如果使用变量, 用中括号[]括起来

ES6之前的构造器

1、定义一个函数(构造器)对象,函数名首字母大写
2、使用this定义属性
3、使用new和构造器创建一个新对象

function Point(x, y) {
    this.x = x;
    this.y = y;
    this.show = () => console.log(this.x, this.y);
    console.log('Point ----')
}

p1 = new Point(3, 4)
console.log(p1)
console.log('--'.repeat(20))

function PointChild(x, y, z) {
    Point.call(this, x, y) // 继承
    this.z = z
    console.log('PointChild ---')
}

p2 = new PointChild(4, 5, 6)
console.log(p2)
p2.show()

/*运行结果:
Point ----
Point { x: 3, y: 4, show: [Function] }
----------------------------------------
Point ----
PointChild ---
PointChild { x: 4, y: 5, show: [Function], z: 6 }
4 5
*/

构建一个新的对象必须用new来构建, 类中调用基类使用call方法, 可以传入子类的this
注意:如果不使用new关键字,就是一次普通的函数调用,this不代表实例

ES6中的class

从ES6开始,新提供了class关键字,使得创建对象更加简单、清晰。
1、类定义使用class关键字。创建的本质上还是函数,是一个特殊的函数
2、一个类只能拥有一个名为constructor的构造器方法。如果没有显式的定义一个构造方法,则会添加一个默认的constuctor方法。
3、继承使用extends关键字
4、一个构造器可以使用super关键字来调用一个父类的构造函数
5、类没有私有属性

class Point {
    constructor(x, y){ // 构造器
        this.x = x;
        this.y = y;
    }
    show() { //方法
        console.log(this, this.x, this.y);
    }
}
p1 = new Point(2,3);
console.log(p1);
p1.show(); // this就是p1代表实例本身, 类似python的self

//继承
class PointChild extends Point {
    constructor(x, y, z) {
        super(x, y); //调用基类super
        this.z = z;
    }
}
p2 = new PointChild(4, 6, 8);
console.log(p2);
p2.show();

/*运行结果:
Point { x: 2, y: 3 }
Point { x: 2, y: 3 } 2 3
PointChild { x: 4, y: 6, z: 8 }
PointChild { x: 4, y: 6, z: 8 } 4 6
*/

使用new来构造对象, 继承类时, 在属性未定义前一定要用super方法调用基类

重写方法

子类PointChild的show方法需要重写

class Point {
    constructor(x, y){ // 构造器
        this.x = x;
        this.y = y;
    }
    show() { //方法
        console.log(this, this.x, this.y);
    }
}
p1 = new Point(2,3)
console.log(p1)
p1.show() // this就是p1代表实例本身, 类似python的self

//继承
class PointChild extends Point {
    constructor(x, y, z) {
        super(x, y); //调用基类super
        this.z = z;
    }
    show() { // 重写方法
        console.log(this, this.x, this.y, this.z);
    }
}
p2 = new PointChild(4, 6, 8)
console.log(p2)
p2.show()

/*运行结果:
Point { x: 2, y: 3 }
Point { x: 2, y: 3 } 2 3
PointChild { x: 4, y: 6, z: 8 }
PointChild { x: 4, y: 6, z: 8 } 4 6 8
*/

子类中直接重写父类的方法即可。
如果需要使用父类的方法,使用super.method()的 方式调用

还可以使用箭头函数定义方法:

class Point {
    constructor(x, y){ // 构造器
        this.x = x;
        this.y = y;
        this.show = () => console.log('Point ---'); // 箭头函数定义方法
    }
}
p1 = new Point(2,3)
p1.show() // this就是p1代表实例本身, 类似python的self

//继承
class PointChild extends Point {
    constructor(x, y, z) {
        super(x, y); //调用基类super
        this.z = z;
        this.show = () => console.log('PointChild ---');
    }
}
p2 = new PointChild(4, 6, 8)
p2.show()

/*运行结果:
Point ---
PointChild ---
*/

从运行结果来看, 箭头函数也支持子类覆盖基类方法

class Point {
    constructor(x, y){ // 构造器
        this.x = x;
        this.y = y;
        this.show = () => console.log('Point ---');
    }
}
p1 = new Point(2,3)
p1.show() // this就是p1代表实例本身, 类似python的self

//继承
class PointChild extends Point {
    constructor(x, y, z) {
        super(x, y); //调用基类super
        this.z = z;
    }
    show() { // 重写方法
        console.log(this, this.x, this.y, this.z);
    }
}
p2 = new PointChild(4, 6, 8)
p2.show()

/*运行结果:
Point ---
Point ---
*/

上例父类用箭头函数定义方法, 子类用普通函数定义方法, 从运行结果来看, 优先使用了父类的show方法

class Point {
    constructor(x, y){ // 构造器
        this.x = x;
        this.y = y;
    }
    show() {
        console.log(this, this.x, this.y);
    }
}
p1 = new Point(2,3)
p1.show() // this就是p1代表实例本身, 类似python的self

//继承
class PointChild extends Point {
    constructor(x, y, z) {
        super(x, y); //调用基类super
        this.z = z;
        this.show = () => console.log('PointChild ---');
    }
}
p2 = new PointChild(4, 6, 8)
p2.show()

/*运行结果:
Point ---
PointChild ---
*/

上例父类用普通函数定义方法, 子类用箭头函数定义方法, 从运行结果来看, 优先使用了子类的show方法

总结:
父类、子类使用同一种方式类定义属性或者方法,子类覆盖父类。
访问同名属性或方法时,优先使用属性
箭头函数定义在实例的属性里, 普通函数定义在类的方法中, 属性优先于方法

静态方法

在方法名前加上static,就是静态方法了

class Add {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    static print() {
        console.log('static method', this.x)
    }
}

add = new Add(3, 5)
console.log(Add)
// add.print() // 实例不能访问直接访问静态方法,和C++、Java一致
Add.print()
add.constructor.print() // 实例可以通过constructor访问静态方法

/*运行结果:
[Function: Add]
static method undefined
static method undefined
*/

静态方法中的this是Add类,而不是Add的实例

注意:
静态的概念和Python的静态不同,相当于Python中的类变量

this的坑

虽然Js和 C++ 、Java一样有this,但是Js的表现是不同的。
原因在于, C++ 、Java是静态编译型语言,this是编译期绑定,而Js是动态语言,运行期绑定

var school = {
    name: 'school',
    getNameFunc:function () {
        console.log(this.name);
        console.log(this);
        return function () {
            console.log(this === global); // this是否是global对象
            return this.name;
        };
    }
};
console.log(school.getNameFunc()());

/*运行结果:
school
{ name: 'school', getNameFunc: [Function: getNameFunc] }
true
undefined
*/

函数执行时,会开启新的执行上下文环境ExecutionContext。
创建this属性,但是this是什么就要看函数是怎么调用的了。
1、myFunction(1,2,3),普通函数调用方式,this指向全局对象。全局对象是nodejs的global或者浏览器中的window。
2、myObject.myFunction(1,2,3),对象方法的调用方式,this指向包含该方法的对象。
3、call、apply、bind方法调用时,要看第一个参数是谁。

分析上例:
school{ name: 'school', getNameFunc: [Function: getNameFunc] }很好理解。
第三行打印的true,是 console.log(this === global)执行的结果,说明当前是global,因为调用这个返回的函数
是直接调用的,这就是个普通函数调用,所以this是全局对象。
第四行undefined,就是因为this是global,没有name属性

显示传入
var school = {
    name: 'school',
    getNameFunc:function () {
        console.log(this.name);
        console.log(this);
        return function (that) {
            console.log(this === global); // this是否是global对象
            return that.name;
        };
    }
};
console.log(school.getNameFunc()(school));

/*运行结果:
school
{ name: 'school', getNameFunc: [Function: getNameFunc] }
true
school
*/

通过主动传入对象,这样就避开了this的问题

apply、call方法
var school = {
    name: 'school',
    getNameFunc:function () {
        console.log(this.name);
        console.log(this);
        return function () {
            console.log(this === global); // this是否是global对象
            return this.name;
        };
    }
};
console.log(school.getNameFunc().call(school)); // call方法显示传入this对应的对象

/*运行结果:
school
{ name: 'school', getNameFunc: [Function: getNameFunc] }
false
school
*/

apply、call方法都是函数对象的方法,第一参数都是传入对象引入的。
apply传其他参数需要使用数组
call传其他参数需要使用可变参数收集

function Point(x, y){
    this.x = x;
    this.y = y;
    console.log(this === global);
    console.log('Point ----');
}

p1 = Point(3, 4);
console.log(p1);
console.log('--'.repeat(20))

p2 = new Object();
console.log(p2);

p3 = Point.call(p2, 10, 11);
// p3 = Point.apply(p2, [10, 11]);
console.log(p3);
console.log(p2);

/*运行结果:
true
Point ----
undefined
----------------------------------------
{}
false
Point ----
undefined
{ x: 10, y: 11 }
*/

p1 = Point(3, 4);p3 = Point.call(p2, 10, 11);没有用new方法构造对象, 只能算是普通函数调用, Point没有返回值, 所以p1和p3都是undefined
p3 = Point.call(p2, 10, 11);通过call方法显示传入p2为函数的this对象, 函数内为该对象注入x,y属性.

bind方法
var school = {
    name: 'school',
    getNameFunc:function () {
        console.log(this.name);
        console.log(this);
        return function () {
            console.log(this === global); // this是否是global对象
            return this.name;
        };
    }
};
var boundfunc = school.getNameFunc().bind(school); // bind方法绑定后返回新的函数
console.log(boundfunc);
console.log(boundfunc());

/*运行结果:
school
{ name: 'school', getNameFunc: [Function: getNameFunc] }
[Function: bound ]
false
school
*/

bind方法与apply、call方法不同, 是为函数先绑定this, 返回一个新函数, 调用时直接用.
apply、call方法都是调用时传入this.

ES6引入支持this的箭头函数

ES6 新技术,就不需要兼容this问题

var school = {
    name: 'school',
    getNameFunc:function () {
        console.log(this.name);
        console.log(this);
        return () => {
            console.log(this === global); // this是否是global对象
            return this.name;
        };
    }
};
console.log(school.getNameFunc()());

/*运行结果:
school
{ name: 'school', getNameFunc: [Function: getNameFunc] }
false
school
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值