Yuan的博客
EN

原型、原型链、原型继承

原型 (prototype)、原型链 (prototype chain) 、原型继承 (prototypal inheritance)

  • prototype和 【proto】二者不同:prototype存在于函数中,【proto】是物件的属性
  • [[Prototype]] 是在 JavaScript 中物件的特殊隐藏属性,但因为无法直接被访问到,因此可以透过 proto 的访问方法。
  • 我们会将属性或方法加到原型 (prototype) 上,那么所有从这个物件中实例出来的物件,都有办法使用这个方法或属性。
// 构造函式 Animal
function Animal() {}

// 实例
const cat = new Animal();

// 往原型对象加上方法
Animal.prototype.sleep = function () {
  console.log("sleep");
};

// 使用构造函式的 prototype 的方法
cat.sleep(); // sleep

解释 JavaScript 中 this 的值?

  • this 值 5 种判断方式:his 的值是动态的,通常会由被呼叫的函式来决定。所以,影响 this 的值不是宣告的时机,关键在于在哪里被调用。谁在‘调用’这个函数,谁就是 this。
  1. 函式调用:当一个函式不是属于一个物件中的方法时,直接作为函式来调用时,this 会指向全局物件,在浏览器中,默认为 Window 。但有一点要注意的是,如果是在严格模式下,this 为 undefined。
var name = "John";
function callThis() {
  console.log(this);
  console.log(this.name);
}
callThis();
// Window
// John
  1. 物件方法调用:当一个函式是做为一个物件的方法来调用时,this 会指向这个物件。
const john = {
  name: "john",
  callJohn() {
    console.log(`hello, ${this.name}`);
  },
};

john.callJohn(); // hello, john
  1. 构造函式调用:当一个函式用 new 关键字调用时,此函式执行前会先创造一个新的物件,this 会指向这个新组成的物件。
function Cellphone(brand) {
  this.brand = brand;
}

Cellphone.prototype.getBrand = function () {
  return this.brand;
};

let newIPhone = new Cellphone("Apple");
let newPixelPhone = new Cellphone("Google");

console.log(newIPhone.getBrand()); // Apple
console.log(newPixelPhone.getBrand()); // Google
  1. apply、call、bind 方法(手动设置this值)调用
  • call:fn.call(thisArg, arg1, arg2, …)
function greet(greeting) {
  console.log(greeting, this.name);
}

const user = { name: "Tom" };

greet.call(user, "Hello");  // Hello Tom
  • apply:fn.apply(thisArg, [arg1, arg2])
function sum(a, b, c) {
  return a + b + c;
}

console.log(sum.apply(null, [1, 2, 3])); // 6
  • bind:const newFn = fn.bind(thisArg, arg1, arg2)
function sum(a, b, c) {
  return a + b + c;
}

console.log(sum.apply(null, [1, 2, 3])); // 6
  1. 箭头函式中的 this:箭头函式的this 会从他的外在函式继承,若他的外在函式也同为箭头函示,则回继续往上寻找,直到找到全域环境的预设this

ES6 中的 class 是什么?和函式构造函式差别是什么?

  1. class 的常见方法继承:在这个例子中,Dog class 继承了 Animal class 的所有属性和方法,也可以重写 speak 方法。
class Animal {
  constructor(name) {
    this.name = name;
  }

  eat() {
    console.log(`${this.name} eat food.`);
  }

  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name);
  }

  speak() {
    console.log(`${this.name} barks.`);
  }
}
  1. static 静态方法:静态方法不能被物件实例继承,只能通过 class 本身调用。这意味着,你不能通过物件实例调用 class 方法,而只能通过 class 名本身调用。
class MathHelper {
  static add(a, b) {
    return a + b;
  }
}

const math = new MathHelper();
// 尝试通过对象实例访问静态方法,将抛出一个错误
console.log(math.add(2, 3)); // Uncaught TypeError: math.add is not a function

// 只能通过 class 本身访问
console.log(MathHelper.add(2, 3)); // 5
  1. Private fields:可以通过使用前缀 # 来实现 class 的私有领域 (Private fields),包括建立私有的属性或是方法,而私有领域只能在 class 内部使用,外部无法存取。
class Example {
  #privateField = 100;

  getPrivateField() {
    return this.#privateField;
  }
}

const example = new Example();
console.log(example.getPrivateField()); // 100
console.log(example.#privateField); // SyntaxError: Private field '#privateField' must be declared in an enclosing class

JavaScript 中的浅拷贝 (shallow copy) 和深拷贝 (deep copy) 差别是什么? 要如何实践?

  • 浅拷贝只复制第一层属性。如果属性是原始类型(number / string / boolean),值会被复制。如果属性是对象或数组,复制的只是引用,不会新建一份独立对象。
    • 方法一:手动复制值
    • 方法二:使用 spread syntax
    • 方法三:使用 Object.assign let objB = Object.assign({}, objA);
  • 深拷贝会把对象的每一层都完整复制一份,彻底独立。所有嵌套对象都会重新开辟新的内存位置,不共享引用。
    • JSON.parse(JSON.stringify(…)):JSON.parse(JSON.stringify(obj)) 会先把物件转换成 JSON 字串,再重新建立一个新物件,因此对「可被 JSON 序列化」的数据来说,它能做到深拷贝。但它无法处理函数、Date、RegExp、Map/Set,以及循环引用等类型。
    • 使用 structuredClone(value)