Skip to content

this、class、new

this

是什么

在 JavaScript 中,this 是一个关键字,它在函数被调用的时候设定,指向函数运行时的上下文(context)。this 的值并不取决于函数本身是如何定义的,而是取决于函数是如何被调用的。

绑定方式

  1. 默认绑定:在非严格模式下,当一个函数独立调用(即它不是作为对象的属性或方法调用),this 通常会绑定到全局对象(在浏览器中是 window 对象,在 Node.js 中是 global 对象)。在严格模式下,this 会绑定到 undefined

  2. 隐式绑定:当一个函数作为对象的方法调用时,this 会绑定到那个对象,就近原则。

  3. 显式绑定:我们可以使用 callapplybind 方法显式地设置 this 的值。callapply 会立即调用函数,并允许你传入一个对象作为 this 的值,而 bind 会创建一个新的函数,当这个新函数被调用时,this 会被绑定到你传入的对象。

  4. 硬绑定:硬绑定是显式绑定的一种,使用 .bind() 方法可以创建一个新的函数,这个函数的 this 被永久绑定到 bind 的第一个参数。

  5. new 绑定:当一个函数或类被 new 关键字作为构造函数调用时,JavaScript 会创建一个新的空对象,然后 this 会被绑定到这个新对象,也就是实例对象。

  6. 箭头函数:箭头函数不会创建自己的 this 上下文,而是会捕获其所在(即定义的位置)上下文的 this 值。这意味着箭头函数内的 this 值与它被定义时的外围上下文中的 this 值相同。

  7. 事件处理器:事件处理函数内部的 this 总是指向被绑定 DOM 元素,这种行为使得你可以在事件处理器中方便地访问和操作触发事件的元素。需要注意的是,如果你使用箭头函数作为事件处理器,那么 this 将不会指向触发事件的元素,而是指向箭头函数在定义时的上下文。

这些规则的优先级从高到低依次是:new 绑定 > 显式绑定/硬绑定 > 隐式绑定 > 默认绑定。也就是说,如果一个函数同时满足多个规则,那么优先级高的规则将决定 this 的值。

理解 this 的工作原理对于编写和理解 JavaScript 代码非常重要,因为 this 在函数调用、对象方法、构造函数、事件处理器等多个场景中都有广泛的应用。

使用场景

this 在 JavaScript 中有很多使用场景,下面是一些典型的例子:

  1. 对象方法中使用 this

    在对象的方法中,this 通常指向调用该方法的对象。

    javascript
    let obj = {
      name: "Alice",
      greet: function () {
        console.log("Hello, " + this.name);
      },
    };
    
    obj.greet(); // 输出:'Hello, Alice'

    在这个例子中,greet 方法内的 this 指向了 obj 对象。

  2. 构造函数中使用 this

    在使用 new 关键字调用的函数(即构造函数)中,this 指向新创建的对象。

    javascript
    function Person(name) {
      this.name = name;
    }
    
    let alice = new Person("Alice");
    console.log(alice.name); // 输出:'Alice'

    在这个例子中,thisPerson 函数内部指向新创建的对象。

  3. 显式绑定 this

    我们可以使用 callapplybind 方法显式地设置 this 的值。

    javascript
    function greet() {
      console.log("Hello, " + this.name);
    }
    
    let obj = { name: "Alice" };
    
    greet.call(obj); // 输出:'Hello, Alice'

    在这个例子中,我们使用 call 方法将 this 绑定到 obj 对象,然后调用 greet 函数。

  4. 箭头函数中的 this

    箭头函数不会创建自己的 this 上下文,而是继承外层代码块的 this

    javascript
    let obj = {
      name: "Alice",
      greet: function () {
        setTimeout(() => {
          console.log("Hello, " + this.name);
        }, 1000);
      },
    };
    
    obj.greet(); // 一秒后输出:'Hello, Alice'

    在这个例子中,箭头函数内的 this 是从外层的 greet 方法继承的,所以它指向 obj 对象。

  5. DOM 事件处理器中的 this

    在 DOM 事件处理器中,this 通常指向触发事件的元素。

    javascript
    let button = document.querySelector("button");
    button.addEventListener("click", function () {
      console.log(this); // 输出:点击的按钮元素
    });

    在这个例子中,this 在事件处理器函数中指向触发点击事件的按钮元素。

  6. Function 与箭头函数

js
var a = 2;
var obj = {
  a: 1,
  test: () => {
    console.log(this.a);
  },
  do: function () {
    console.log(this.a);
  },
  testThis: function () {
    console.log("testThis", this); // 最近引用作用域 -> obj
    const test1 = () => {
      console.log("test1", this); // 上层作用域 -> obj
      const test2 = () => {
        console.log("test2", this); // 上层作用域 -> 上层作用域 -> obj
        const test3 = function () {
          console.log("test3", this); // 最近引用作用域 -> function -> window
          const test4 = () => {
            console.log("test4", this); // 上层作用域 -> window
          };
          test4();
        };
        test3();
      };
      test2();
    };
    test1();
  },
  insideObj: {
    a: 3,
    test: function () {
      console.log(this.a); // 3
    },
  },
};
const test = () => {
  console.log(this.a);
};
test(); // 2
test.call(obj); // 2
test.apply(obj); // 2
test.bind(obj)(); // 2
obj.test(); // 2
obj.do(); // 1

以上就是 this 在 JavaScript 中的一些典型使用场景。理解这些场景可以帮助你更好地理解和使用 this 关键字。

class

是什么

在 JavaScript 中,class是 ES6(ECMAScript 2015)中引入的一个关键字,用于定义类。类是一种特殊的函数,它可以包含构造函数、方法、属性等。

类主要用于创建对象(实例)。它是对象创建的蓝图,定义了一个对象的属性和方法。

这是一个类的基本定义:

javascript
class MyClass {
  constructor() {
    // 初始化实例属性
  }

  myMethod() {
    // 定义一个方法
  }
}

你可以使用new关键字来创建一个类的实例:

javascript
let instance = new MyClass();

constructor方法是类的特殊方法,它在创建新实例时被自动调用。

类也支持继承,你可以通过extends关键字来创建一个类的子类:

javascript
class MySubClass extends MyClass {
  // ...
}

在子类中,你可以使用super关键字来调用父类的方法。

执行过程

JavaScript 中的class的执行过程可以分为以下几个步骤:

  1. 声明和定义:首先,我们需要声明并定义一个类。这包括定义类的构造函数、方法和属性。这些方法和属性将被存储在类的原型对象中。
javascript
class MyClass {
  constructor() {
    // 初始化实例属性
  }

  myMethod() {
    // 定义一个方法
  }
}
  1. 实例化:当我们使用new关键字创建一个类的实例时,JavaScript 会创建一个新的对象,并将类的原型对象设置为这个新对象的原型。然后,JavaScript 会执行类的构造函数,初始化新对象的属性。
javascript
let instance = new MyClass();
  1. 方法调用:当我们调用实例的方法时,JavaScript 会首先在实例自身的属性中查找这个方法。如果没有找到,它会在实例的原型(也就是类的原型对象)中查找。如果在原型中找到了这个方法,JavaScript 会执行它。
javascript
instance.myMethod();
  1. 继承:如果我们定义了一个继承自其他类的类,那么在实例化这个子类时,JavaScript 会首先创建一个新的对象,并将子类的原型对象设置为这个新对象的原型。然后,JavaScript 会执行父类的构造函数,初始化新对象的属性。最后,JavaScript 会执行子类的构造函数,进一步初始化新对象的属性。
javascript
class MySubClass extends MyClass {
  constructor() {
    super(); // 调用父类的构造函数
    // 进一步初始化新对象的属性
  }
}
  1. 子类方法调用:当我们调用子类实例的方法时,JavaScript 的查找顺序是:首先在实例自身的属性中查找,然后在子类的原型中查找,最后在父类的原型中查找。如果在这个过程中找到了这个方法,JavaScript 会执行它。

以上就是 JavaScript 中class的基本执行过程。

模拟实现

在 ES6 之前,JavaScript 并没有类(class)这个概念,但我们可以通过函数和原型(prototype)来模拟实现类的功能。以下是一个例子:

javascript
// 定义构造函数
function MyClass() {
  // 初始化实例属性
  this.myProperty = "some value";
}

// 在原型上定义方法
MyClass.prototype.myMethod = function () {
  // 定义一个方法
  console.log("This is my method");
};

// 实例化
var instance = new MyClass();
instance.myMethod(); // 输出: 'This is my method'

// 模拟实现继承
function MySubClass() {
  MyClass.call(this); // 调用父类构造函数
  this.mySubProperty = "some other value";
}

// 设置原型为父类的实例,实现继承
MySubClass.prototype = Object.create(MyClass.prototype);
MySubClass.prototype.constructor = MySubClass;

// 在子类原型上添加方法
MySubClass.prototype.mySubMethod = function () {
  console.log("This is my sub method");
};

// 实例化子类
var subInstance = new MySubClass();
subInstance.myMethod(); // 输出: 'This is my method'
subInstance.mySubMethod(); // 输出: 'This is my sub method'

在上面的代码中,我们使用了函数和原型来模拟实现了类、实例化、方法定义和继承等功能。这就是在 ES6 引入class关键字之前,JavaScript 是如何模拟实现类的功能的。

使用场景

JavaScript 的class主要用于创建对象模型,它的使用场景非常广泛。以下是一些常见的使用场景:

  1. 封装:我们可以使用class来封装相关的数据和操作。例如,我们可以定义一个Person类来封装人的属性和方法:
javascript
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

let john = new Person("John", 30);
john.greet(); // 输出: 'Hello, my name is John'
  1. 继承:我们可以使用class来实现继承,以复用和扩展现有的代码。例如,我们可以定义一个Student类继承自Person类,并添加新的方法:
javascript
class Student extends Person {
  constructor(name, age, grade) {
    super(name, age);
    this.grade = grade;
  }

  study() {
    console.log(`${this.name} is studying`);
  }
}

let alice = new Student("Alice", 20, "A");
alice.greet(); // 输出: 'Hello, my name is Alice'
alice.study(); // 输出: 'Alice is studying'
  1. 多态:我们可以使用class来实现多态,即同一个方法在不同的类中有不同的实现。例如,我们可以定义一个Animal类和两个继承自它的DogCat类,它们都有一个makeSound方法,但实现不同:
javascript
class Animal {
  makeSound() {
    throw new Error("This method must be overridden in a subclass");
  }
}

class Dog extends Animal {
  makeSound() {
    console.log("Woof!");
  }
}

class Cat extends Animal {
  makeSound() {
    console.log("Meow!");
  }
}

let dog = new Dog();
dog.makeSound(); // 输出: 'Woof!'

let cat = new Cat();
cat.makeSound(); // 输出: 'Meow!'
  1. 模块化和代码复用:我们可以将类定义在一个模块中,然后在其他模块中导入和使用它,从而实现代码的模块化和复用。例如,我们可以在一个模块中定义一个Vector类,然后在其他模块中导入它:
javascript
// 在Vector.js模块中
export class Vector {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  add(vector) {
    return new Vector(this.x + vector.x, this.y + vector.y);
  }
}

// 在其他模块中
import { Vector } from "./Vector.js";

let v1 = new Vector(1, 2);
let v2 = new Vector(2, 3);
let v3 = v1.add(v2);

以上就是class在 JavaScript 中的一些常见使用场景。

new

是什么

在 JavaScript 中,new 关键字用于创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。使用 new 关键字创建对象的过程主要有以下几个步骤:

  1. 创建一个新对象:JavaScript 首先会创建一个新的空对象。

  2. 设置原型:新创建的对象的 __proto__ 属性会被设置为构造函数的 prototype 对象。这样新对象就可以访问构造函数原型中定义的方法和属性。

  3. 绑定 this:在构造函数内部,this 关键字被设置为新创建的对象。如果构造函数中的代码引用了 this,那么这个 this 就指向新创建的对象。

  4. 执行构造函数中的代码:构造函数内部的代码(对 this 的属性和方法的引用)被执行,通常这些代码会初始化新对象的状态。

  5. 返回新对象:如果构造函数没有显式返回一个对象,则会自动返回新创建的对象。如果构造函数返回了一个对象,那么这个对象会作为 new 表达式的结果返回;如果构造函数返回了一个非对象类型的值,那么这个返回值会被忽略,还是会返回新创建的对象。

以下是一个简单的例子:

javascript
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHello = function () {
  console.log("Hello, my name is " + this.name);
};

var john = new Person("John", 30);
john.sayHello(); // 输出 "Hello, my name is John"

在这个例子中,new Person('John', 30) 创建了一个新的 Person 对象,并将 nameage 属性初始化为 'John' 和 30。新对象的 __proto__ 属性被设置为 Person.prototype,所以它可以访问 sayHello 方法。

执行过程

在 JavaScript 中,new操作符的执行过程可以分为以下四个步骤:

  1. 创建一个新对象:首先,new操作符会创建一个空的 JavaScript 对象。这个新对象是一个实例,它的类型是定义的特定构造函数。

  2. 设置原型:新创建的对象的__proto__属性会被指向构造函数的prototype属性,从而继承构造函数原型上的属性和方法。这是 JavaScript 的原型继承机制。

  3. 执行构造函数:然后,new操作符会调用构造函数,执行其中的代码。注意,构造函数中的this会被指向新创建的对象,因此构造函数中的属性和方法都会被添加到新对象上。

  4. 返回新对象:如果构造函数没有手动返回一个对象,那么new操作符会自动返回新创建的对象。如果构造函数返回的是一个非对象类型,那么这个返回值会被忽略,仍然返回新创建的对象。但是,如果构造函数返回的是一个新对象,那么new操作符会返回这个新对象,而不是之前创建的对象。

以下是一个简单的例子来说明new操作符的执行过程:

javascript
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHello = function () {
  console.log("Hello, my name is " + this.name);
};

var john = new Person("John", 30);
john.sayHello(); // 输出:Hello, my name is John

在这个例子中,new Person('John', 30)的执行过程如下:

  1. 创建一个新对象{}
  2. 设置新对象的__proto__属性指向Person.prototype,从而继承Person.prototype上的方法。
  3. 执行Person构造函数,其中的this被指向新对象,因此nameage属性被添加到新对象上。
  4. new操作符返回新创建的对象。

模拟实现

在 JavaScript 中,我们可以模拟实现 new 操作符的功能。以下是一个简单的实现:

javascript
function myNew(Constructor, ...args) {
  // 创建一个新的空对象
  var obj = {};
  // 设置新对象的 __proto__ 属性指向构造函数的 prototype 对象
  obj.__proto__ = Constructor.prototype;
  // 执行构造函数,并将 this 指向新创建的对象
  var result = Constructor.apply(obj, args);
  // 如果构造函数执行的结果是一个对象,就返回这个对象;否则,返回新创建的对象
  return typeof result === "object" ? result : obj;
}

这个 myNew 函数首先创建一个新的空对象,然后设置这个新对象的 __proto__ 属性指向构造函数的 prototype 对象,这样就可以让新对象继承构造函数的 prototype 对象上的属性和方法。接着,执行构造函数,并将 this 指向新创建的对象。最后,如果构造函数执行的结果是一个对象,就返回这个对象;否则,返回新创建的对象。

下面是一个简单的例子来说明 myNew 函数的使用方法:

javascript
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHello = function () {
  console.log(
    "Hello, my name is " + this.name + ", I am " + this.age + " years old."
  );
};

var john = myNew(Person, "John", 20);
john.sayHello(); // 输出:Hello, my name is John, I am 20 years old.

在这个例子中,myNew(Person, 'John', 20) 创建了一个新的 Person 对象,并返回这个对象。这个新的对象可以访问 Person.prototype 对象上的 sayHello 方法。

使用场景

new关键字在 JavaScript 中主要用于创建对象的实例,这在以下几种场景中特别有用:

  1. 创建自定义对象类型:当你需要创建具有特定属性和方法的多个对象时,可以创建一个构造函数,并使用new关键字来创建新的对象实例。
javascript
function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}

var myCar = new Car("Toyota", "Corolla", 2005);
console.log(myCar.make); // 输出 "Toyota"
  1. 使用内置对象类型:JavaScript 内置了一些对象类型,如DateArraySetMap等,你可以使用new关键字来创建这些对象的实例。
javascript
// 创建一个新的日期对象
var now = new Date();
console.log(now); // 输出当前日期和时间

// 创建一个新的数组对象
var arr = new Array(1, 2, 3, 4, 5);
console.log(arr); // 输出 [1, 2, 3, 4, 5]
  1. 创建继承其他对象的对象:你可以使用new关键字和Object.create方法来创建一个新对象,该对象的原型被设置为另一个对象。
javascript
var person = {
  name: "Alice",
  sayHello: function () {
    console.log("Hello, my name is " + this.name);
  },
};

var employee = Object.create(person);
employee.name = "Bob";
employee.sayHello(); // 输出 "Hello, my name is Bob"

在这个例子中,employee对象是通过Object.create(person)创建的,所以employee的原型就是person,并且employee继承了personsayHello方法。

hancenter808@outlook.com