精读《你不知道的JavaScript(上卷)》第二部分

929 阅读7分钟

第 1、2 章关于 this

默认绑定

默认绑定是 this 最常见的绑定规则。当一个函数独立调用(而不是作为对象的方法调用)时,this 默认绑定到全局对象(在浏览器中是 window 对象,nodejs 则是 global 对象),在严格模式(strict mode)下,this 值为 undefined。。

function exampleFunction() {
  console.log(this);
}

exampleFunction(); // 输出全局对象(浏览器中为 window)

function strictFunction() {
  "use strict";
  console.log(this);
}

strictFunction(); // 输出 undefined

隐式绑定

当一个函数作为对象的方法调用时,this 隐式绑定到调用该方法的对象。

const obj = {
  value: "I am obj",
  showThis: function() {
    console.log(this);
  }
};

obj.showThis(); // 输出 {value: "I am obj", showThis: ƒ}

显式绑定

使用 call、apply 和 bind 方法,可以显式地将 this 绑定到指定的对象。

使用 call apply 示例:

function showValue() {
  console.log(this.value);
}

const obj1 = {
  value: "I am obj1"
};

const obj2 = {
  value: "I am obj2"
};

showValue.call(obj1); // 输出 "I am obj1"
showValue.apply(obj2); // 输出 "I am obj2"

const boundShowValue = showValue.bind(obj1);
boundShowValue(); // 输出 "I am obj1"

new 绑定

当使用 new 关键字调用一个构造函数时,this 被绑定到新创建的对象实例。

function Person(name) {
  this.name = name;
}

const person = new Person("John Doe");
console.log(person.name); // 输出 "John Doe"

箭头函数中的 this

箭头函数没有自己的 this 值。它是外层作用域的 this,这使得在事件处理程序和回调函数中使用箭头函数变得非常方便。

const obj = {
  value: "I am obj",
  showThis: function() {
    setTimeout(() => {
      console.log(this);
    }, 1000);
  }
};

obj.showThis(); // 输出 {value: "I am obj", showThis: ƒ}(1秒后)

第 3 章对象

对象是JavaScript中最重要的数据结构,它们用于存储键值对

创建对象

可以使用以下三种方法创建对象:

  • 对象字面量(Object Literal):
const obj = {
  key1: "value1",
  key2: "value2"
};
  • 使用 Object.create():
const prototypeObj = {
  key1: "value1",
  key2: "value2"
};

const obj = Object.create(prototypeObj);
  • 使用构造函数:
function Obj() {
  this.key1 = "value1";
  this.key2 = "value2";
}

const obj = new Obj();

属性访问

可以使用点表示法或方括号表示法访问对象属性:

const obj = {
  key1: "value1",
  key2: "value2"
};

console.log(obj.key1); // 输出 "value1"
console.log(obj["key2"]); // 输出 "value2"

属性遍历

可以使用 for...in 循环遍历对象属性:

const obj = {
  key1: "value1",
  key2: "value2"
};

for (const key in obj) {
  console.log(key + ": " + obj[key]);
}

属性描述符

每个对象属性都具有一组属性描述符,它们定义了属性的特性(如可写、可枚举和可配置)。可以使用 Object.getOwnPropertyDescriptor() 获取属性描述符,使用 Object.defineProperty() 修改属性描述符。

示例:

const obj = {
  key1: "value1"
};

const descriptor = Object.getOwnPropertyDescriptor(obj, "key1");
console.log(descriptor); // 输出 {value: "value1", writable: true, enumerable: true, configurable: true}

Object.defineProperty(obj, "key1", {
  writable: false
});

obj.key1 = "new value";
console.log(obj.key1); // 输出 "value1"(未修改,因为属性不可写)

原型链

JavaScript对象通过原型链实现继承。每个对象都有一个内部属性 [[Prototype]],它指向该对象的原型。

原型链是一系列原型对象的链接,直到 null。

示例:

function Person(name) {
  this.name = name;
}

Person.prototype.sayName = function() {
  console.log(this.name);
};

const person1 = new Person("John Doe");
person1.sayName(); // 输出 "John Doe"

console.log(person1.__proto__ === Person.prototype); // 输出 true
console.log(Person.prototype.__proto__ === Object.prototype); // 输出 true

第 4 章混合对象“类”

类的定义

JavaScript中并没有传统意义上的类,但我们可以使用构造函数和原型链来模拟类的行为。

示例:

function Person(name) {
  this.name = name;
}

Person.prototype.sayName = function() {
  console.log(this.name);
};

const person1 = new Person("John Doe");
person1.sayName(); // 输出 "John Doe"

类的继承

使用原型链,我们可以实现类的继承。子类的原型指向父类的实例,从而实现继承。

示例:

function Employee(name, position) {
  Person.call(this, name); // 继承 Person 的属性
  this.position = position;
}

// 继承 Person 的方法
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;

Employee.prototype.sayPosition = function() {
  console.log(this.position);
};

const employee1 = new Employee("Jane Doe", "Engineer");
employee1.sayName(); // 输出 "Jane Doe"
employee1.sayPosition(); // 输出 "Engineer"

混合模式

混合模式是一种在 JavaScript 中实现多重继承的方法。我们可以使用混合函数将一个对象的属性和方法复制到另一个对象。

示例:

function mixin(sourceObj, targetObj) {
  for (const key in sourceObj) {
    if (!(key in targetObj)) {
      targetObj[key] = sourceObj[key];
    }
  }
}

const Vehicle = {
  engines: 1,
  ignition: function() {
    console.log("Turning on the engine...");
  },
  drive: function() {
    this.ignition();
    console.log("Driving the vehicle...");
  }
};

const Car = {
  wheels: 4,
  drive: function() {
    Vehicle.drive.call(this);
    console.log("Driving the car...");
  }
};

mixin(Vehicle, Car);
console.log(Car.engines); // 输出 1
Car.ignition(); // 输出 "Turning on the engine..."
Car.drive(); // 输出 "Driving the vehicle..." 和 "Driving the car..."

第 5 章原型

原型链

原型链是一种链接多个原型对象的机制,用于实现对象之间的继承。

每个对象都有一个内部属性 [[Prototype]],它指向该对象的原型。当试图访问一个对象的属性时,JavaScript引擎会沿着原型链查找该属性。

示例:

function Foo() {}
Foo.prototype.bar = "Hello, world!";

const obj = new Foo();

console.log(obj.bar); // 输出 "Hello, world!"

构造函数

构造函数是用于创建新对象的特殊函数。在JavaScript中,构造函数的原型属性(prototype)用于设置新创建对象的原型。

function Person(name) {
  this.name = name;
}

Person.prototype.sayName = function() {
  console.log(this.name);
};

const person1 = new Person("John Doe");
person1.sayName(); // 输出 "John Doe"

实例关系

可以使用 instanceof 操作符检查一个对象是否是某个构造函数的实例。此外,可以使用 isPrototypeOf 方法检查一个对象是否出现在另一个对象的原型链中。

function Foo() {}
const obj = new Foo();

console.log(obj instanceof Foo); // 输出 true
console.log(Foo.prototype.isPrototypeOf(obj)); // 输出 true

原型继承

原型继承是 JavaScript 中实现继承的一种方式。子类的原型指向父类的实例,从而继承父类的属性和方法。

示例:

function Animal(name) {
  this.name = name;
}

Animal.prototype.sayName = function() {
  console.log(this.name);
};

function Dog(name, breed) {
  Animal.call(this, name);
  this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.sayBreed = function() {
  console.log(this.breed);
};

const dog1 = new Dog("Buddy", "Golden Retriever");
dog1.sayName(); // 输出 "Buddy"
dog1.sayBreed(); // 输出 "Golden Retriever"

第 6 章行为委托

委托理论

是一种不同于类继承的代码复用方式,它依赖于对象之间的关系而不是类和实例,行为委托认为对象之间可以通过委托(即引用)其他对象来实现共享行为。

这种模式避免了类和继承带来的复杂性,而是直接在对象之间建立关系。

对象关联

行为委托关注的是在对象之间建立关联。这种关联可以通过原型链实现,让一个对象可以访问另一个对象的属性和方法。

示例:

const Task = {
  setID: function(ID) {
    this.id = ID;
  },
  outputID: function() {
    console.log(this.id);
  }
};

const XYZ = Object.create(Task);
XYZ.prepareTask = function(ID, Label) {
  this.setID(ID);
  this.label = Label;
};
XYZ.outputTaskDetails = function() {
  this.outputID();
  console.log(this.label);
};

const task = Object.create(XYZ);
task.prepareTask(1, "Create a demo");
task.outputTaskDetails(); // 输出 1 和 "Create a demo"

从类到委托

通过改变思维方式,从类和继承转向对象关联和行为委托,可以简化代码结构,提高代码复用率。

示例:

// 使用行为委托代替类继承

const Parent = {
  foo: function() {
    console.log("Hello from Parent!");
  }
};

const Child = Object.create(Parent);
Child.foo(); // 输出 "Hello from Parent!"

反模式:伪多态

伪多态可能会导致不必要的复杂性,因为它要求在不同级别的对象之间手动维护“多态”关系。使用行为委托可以避免这种反模式。

示例:

// 使用行为委托代替伪多态

const Controller = {
  makeRequest: function() {
    this.helper.makeRequest();
  }
};

const AjaxController = Object.create(Controller);
AjaxController.helper = {
  makeRequest: function() {
    console.log("Making an AJAX request...");
  }
};

const ProxyController = Object.create(Controller);
ProxyController.helper = {
  makeRequest: function() {
    console.log("Making a request through a proxy...");
  }
};

AjaxController.makeRequest(); // 输出 "Making an AJAX request..."
ProxyController.makeRequest(); // 输出 "Making a request through a proxy..."

附录A ES6 中的 Class

class是ES6引入的一个新语法糖,用于简化原型继承的编写过程

类声明

class 关键字用于声明一个类。类的方法不需要使用 function 关键字,只需在方法名后跟一个括号即可。类声明支持实例方法、静态方法和getter/setter。

class Person {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }

  static greeting() {
    console.log("Hello!");
  }

  get displayName() {
    return `Person: ${this.name}`;
  }

  set displayName(value) {
    this.name = value.split(": ")[1];
  }
}

const person = new Person("John Doe");
person.sayName(); // 输出 "John Doe"
Person.greeting(); // 输出 "Hello!"
console.log(person.displayName); // 输出 "Person: John Doe"
person.displayName = "Person: Jane Doe";
console.log(person.name); // 输出 "Jane Doe"

类继承

使用 extends 关键字可以实现类继承。在子类中,可以使用 super 关键字调用父类的构造函数或方法。

示例:

class Animal {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }

  sayBreed() {
    console.log(this.breed);
  }
}

const dog = new Dog("Buddy", "Golden Retriever");
dog.sayName(); // 输出 "Buddy"
dog.sayBreed(); // 输出 "Golden Retriever"

类表达式

与函数表达式类似,类也可以使用表达式形式定义。类表达式可以是命名或匿名的。

示例:

const MyClass = class {
  constructor(value) {
    this.value = value;
  }

  printValue() {
    console.log(this.value);
  }
};

const instance = new MyClass(42);
instance.printValue(); // 输出 42

尽管ES6的 class 提供了更简洁的语法来定义类和实现继承,它仍然基于原型继承。