Js 对象模型

JavaScript 中的对象模型是描述 JavaScript 中对象如何组织和相互作用的概念。JavaScript 是一种基于原型的语言,其对象模型基于原型继承而不是经典的基于类的继承。这意味着对象直接从其他对象继承属性,而不是从类(或构造函数)继承。以下是 JavaScript 中对象模型的主要概念:

  1. 原型链(Prototype Chain):JavaScript 中的每个对象都有一个原型(prototype)。原型是一个对象,其他对象可以从中继承属性。当你试图访问对象的属性时,如果该对象本身没有这个属性,JavaScript 将沿着原型链向上查找直到找到相应的属性或到达原型链的末尾(即 null)。

  2. 原型(Prototype):每个对象都有一个原型。原型是一个对象,它包含了对象的共享属性和方法。当你创建一个新对象时,你可以指定该对象的原型。如果没有明确指定,JavaScript 将会默认为其指定一个原型。

  3. 构造函数(Constructor Function):构造函数是一种特殊的函数,用于创建对象。在 JavaScript 中,你可以使用构造函数来创建对象,并且可以通过 new 关键字调用构造函数。构造函数内部使用 this 关键字来指向新创建的对象,并且可以在构造函数内部定义对象的属性和方法。

  4. 实例(Instance):通过构造函数创建的对象被称为该构造函数的实例。每个实例都是一个独立的对象,它们共享构造函数的原型,但具有自己的属性值。

  5. 类(Class):尽管 JavaScript 是基于原型的语言,但 ES6 引入了类的概念,使得面向对象编程更加直观和易于理解。类可以看作是构造函数的语法糖,它提供了更类似于传统类的语法来定义对象和继承关系。

  6. 对象属性和方法:JavaScript 中的对象可以具有属性和方法。属性是对象的特征,而方法是对象的行为。你可以使用点运算符(.)或方括号运算符([])来访问对象的属性和方法。

总的来说,JavaScript 中的对象模型是基于原型继承的,每个对象都有一个原型,并且可以通过原型链继承属性和方法。构造函数用于创建对象,而类提供了更加清晰的语法来定义对象和继承关系。

字面式声明方式

示例1

let a = 1;
let b = 'abc';
let c = [1,2,3];
let d = x => x * 2;

let obj = {
   'a':100, // a: 100
   b:b, // b: 'abc'
   [b]:[b], // abc: [ 'abc' ]
   [a]:200 // '1': 200,将1转换为字符串
}

console.log(obj) // { '1': 200, a: 100, b: 'abc', abc: [ 'abc' ] }

示例2

let a = 1;
let b = 'abc';
let c = [1,2,3];
let d = x => x * 2;

let obj = {a,b,c,d};

console.log(obj) // { a: 1, b: 'abc', c: [ 1, 2, 3 ], d: [Function: d] }

示例3

let a = 1;
let b = 'abc';
let c = [1,2,3];
let d = x => x * 2;

let obj = {
   'a':100, // a: 100
   b:b, // b: 'abc'
   [b]:[b], // abc: [ 'abc' ]
   [a]:200 // '1': 200
}

for (let k in obj) {
    console.log(typeof k, k, obj[k])
}
// 键都是字符串
/*
string 1 200
string a 100
string b abc
string abc [ 'abc' ]
*/

示例4

let d = x => x * 2;

let obj = {
   d
}

console.log(obj) // { d: [Function: d] }
console.log(obj.d(600)) // 1200

通过函数创建类

在JavaScript中,您可以使用函数来创建类,这种模式通常被称为“构造函数”模式。您可以定义一个函数,然后使用该函数来创建新的对象实例。这种方式非常常见,特别是在ES5之前,当时JavaScript还没有原生的类语法。

以下是一个简单的示例,演示了如何使用函数创建一个简单的类:

// 构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 通过原型添加方法
Person.prototype.sayHello = function() {
    console.log("Hello, my name is " + this.name + " and I am " + this.age + " years old.");
};

// 创建对象实例
var person1 = new Person("Alice", 30);
var person2 = new Person("Bob", 25);

// 调用方法
person1.sayHello(); // 输出: Hello, my name is Alice and I am 30 years old.
person2.sayHello(); // 输出: Hello, my name is Bob and I am 25 years old.

在这个示例中,Person函数充当了类的角色,它接受nameage作为参数,并将它们分配给新创建的对象的属性。然后,我们通过Person.prototype给该类添加了一个sayHello方法,该方法用于打印出对象实例的信息。最后,我们使用new关键字来创建新的Person对象实例,并调用了sayHello方法。

示例

function Point(x, y) {
    this.x = x; // 对于类的实例来说,this永远指向实例自身
    this.y = y;
    console.log('point ~~~')
}

console.log(Point); // [Function: Point]

let p1 = new Point(2, 3); // point ~~~,构造出基于Point类型的实例

console.log(p1); // Point { x: 2, y: 3 },对象的表达方式

示例:子类继承

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

// 子类继承
function Point3D(x, y, z) {
    Point.call(this, x, y);
    this.z = z
    console.log('Point3D ~~~')
}

let p2 = new Point3D(1, 2, 3); // point ~~~  Point3D ~~~

console.log(p2); // Point3D { x: 1, y: 2, z: 3 }

通过class创建类

在JavaScript中,您也可以使用class关键字来创建类。class语法是从ES6开始引入的,它提供了一种更简洁、更易读的方式来定义类和构造函数,并且在语法上更接近传统的面向对象编程语言。

以下是使用class关键字创建类的示例:

// 定义一个类
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    // 添加方法
    sayHello() {
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
}

// 创建对象实例
const person1 = new Person("Alice", 30);
const person2 = new Person("Bob", 25);

// 调用方法
person1.sayHello(); // 输出: Hello, my name is Alice and I am 30 years old.
person2.sayHello(); // 输出: Hello, my name is Bob and I am 25 years old.

在这个示例中,我们使用class关键字定义了一个名为Person的类。类中的constructor方法用于初始化对象实例的属性。类中的其他方法则直接在类的主体中定义,不需要使用function关键字,也不需要在方法之间添加逗号。

与使用函数的方式相比,使用class语法更加清晰、易读,并且更符合传统的面向对象编程的习惯。

示例

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    showme() {
        console.log(this, this.x, this.y);
    }
}

let p1 = new Point(10, 20);

console.log(Point) // [class Point]
console.log(p1) // Point { x: 10, y: 20 }
p1.showme() // Point { x: 10, y: 20 } 10 20

示例:子类继承

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    showme() {
        console.log(this, this.x, this.y);
    }
}

class Point3D extends Point {
    constructor(x, y, z)  {
        super(x, y);
        this.z = z
    }
}

let p2 = new Point3D(666, 888, 999);

console.log(p2) // Point3D { x: 666, y: 888, z: 999 }

p2.showme() // Point3D { x: 666, y: 888, z: 999 } 666 888

示例:属性优先

在JavaScript中,如果类中定义了同名的属性和方法,当您在对象实例上访问该名称时,属性会优先于方法。这意味着如果存在同名的属性和方法,访问该名称时会返回属性的值,而不是方法本身。这是因为JavaScript在查找属性或方法时会首先查找属性,然后才是方法。

以下是一个示例,演示了当类中定义了同名属性和方法时,属性优先于方法的情况:

class Example {
    constructor() {
        // 定义属性
        this.name = "John";
    }

    // 定义方法
    name() {
        return "Method name";
    }
}

const example = new Example();

// 访问同名属性和方法
console.log(example.name); // 输出: "John"
console.log(example.name()); // 输出: "Method name"

在这个示例中,类Example中定义了一个名为name的属性和一个同名的方法。当您在example对象实例上访问name时,会返回属性的值"John"。然后,当您调用example.name()时,会执行方法,并返回方法的结果"Method name"

因此,在JavaScript中,属性会优先于同名的方法。

静态属性 & 静态方法

在JavaScript中,您可以使用static关键字来定义静态属性和静态方法。静态属性和静态方法属于类本身,而不是类的实例。这意味着您可以直接通过类来访问它们,而不需要创建类的实例。

以下是一个示例,演示了如何在类中定义静态属性和静态方法:

class Example {
    // 静态属性
    static staticProperty = "I am a static property";

    // 静态方法
    static staticMethod() {
        return "I am a static method";
    }

    // 构造函数
    constructor(name) {
        this.name = name;
    }

    // 实例方法
    sayHello() {
        return `Hello, my name is ${this.name}`;
    }
}

// 访问静态属性
console.log(Example.staticProperty); // 输出: "I am a static property"

// 调用静态方法
console.log(Example.staticMethod()); // 输出: "I am a static method"

// 创建对象实例
const example = new Example("John");

// 调用实例方法
console.log(example.sayHello()); // 输出: "Hello, my name is John"

在这个示例中,staticProperty是一个静态属性,您可以直接通过类来访问它。staticMethod是一个静态方法,同样可以直接通过类来调用。

静态属性和静态方法通常用于表示与整个类相关的行为或状态,而不是特定于单个实例的行为或状态。

示例:静态方法

静态方法是属于类本身而不是类的实例的方法。它们在类的定义中使用 static 关键字声明。静态方法通常用于执行与整个类相关的操作,而不是与特定实例相关的操作。您可以直接通过类来调用静态方法,而不需要先创建类的实例。

以下是一个示例,演示了如何在类中定义和使用静态方法:

class MathHelper {
    // 静态方法,计算两个数字的和
    static add(x, y) {
        return x + y;
    }

    // 静态方法,计算两个数字的差
    static subtract(x, y) {
        return x - y;
    }
}

// 调用静态方法
console.log(MathHelper.add(5, 3)); // 输出: 8
console.log(MathHelper.subtract(10, 4)); // 输出: 6

在这个示例中,addsubtract 方法都是静态方法。您可以直接通过 MathHelper 类来调用这些方法,而不需要先创建类的实例。这使得它们非常适合执行通用的功能,例如数学运算或其他与类本身相关的操作。

this问题

this 与 globalThis

this 关键字在 JavaScript 中是一个非常重要的概念,它通常指向当前执行代码的对象。但是,this 的指向取决于代码执行的上下文,可能会在不同的情况下指向不同的对象。

另一方面,globalThis 是一个全局对象,它提供了在任何 JavaScript 环境中访问全局对象的标准方式。它的作用是提供一个标准化的方式来访问全局对象,而不受宿主环境的限制。在浏览器中,globalThis 指向 window 对象;在 Node.js 中,globalThis 指向全局对象 global

下面是一个简单的示例来说明它们之间的区别:

console.log(this === globalThis); // 在浏览器中,可能为 false;在 Node.js 中,为 true

function testFunction() {
    console.log(this === globalThis); // 在浏览器和 Node.js 中,为 false
}

testFunction();

在上面的示例中,this === globalThis 的结果取决于代码的执行环境。在浏览器环境中,全局的 this 可能指向 window 对象,而 globalThis 也指向 window 对象,所以它们相等。但在函数内部,this 的值会根据函数的调用方式而变化,通常不会指向全局对象,因此 this === globalThis 的结果为 false

在 Node.js 环境中,globalThisthis 都指向全局对象 global,所以 this === globalThis 的结果为 true

总的来说,globalThis 提供了一种标准的方式来访问全局对象,而 this 关键字则是在不同的执行上下文中动态确定的。