【JavaScript】深入浅出JavaScript继承机制:解密原型、原型链与面向对象实战攻略

简介: JavaScript的继承机制基于原型链,它定义了对象属性和方法的查找规则。每个对象都有一个原型,通过原型链,对象能访问到构造函数原型上的方法。例如`Animal.prototype`上的`speak`方法可被`Animal`实例访问。原型链的尽头是`Object.prototype`,其`[[Prototype]]`为`null`。继承方式包括原型链继承(通过`Object.create`)、构造函数继承(使用`call`或`apply`)和组合继承(结合两者)。ES6的`class`语法是语法糖,但底层仍基于原型。继承选择应根据需求,理解原型链原理对JavaScript面向对象编程至关重要

🔥 引言

在深入探索JavaScript编程的旅程中,理解继承机制是攀登至高技能水平的关键一步。作为这门语言的基石之一,继承不仅支撑着代码的复用性和模块化的实现,还深刻影响着对象间关系的构建与数据结构的设计。其中,原型链扮演着核心角色,它定义了对象属性和方法的查找规则,串联起JavaScript对象的血缘与能力传承。本篇讨论将详尽剖析继承的概念,从基本原理到多种实现方式,旨在为您铺设一条通向JavaScript面向对象编程高手之路的坚实桥梁。


🧱 原型基础

首先,每个JavaScript对象都有一个内置的属性叫做[[Prototype]],通常通过__proto__访问(非标准但广泛支持),它指向创建该对象的构造函数的prototype属性。构造函数的prototype本身也是一个对象,拥有自己的属性和方法。

示例代码

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

Animal.prototype.speak = function() {
   
   
    console.log('I am an animal');
};

let cat = new Animal('Kitty'); // 创建Animal的实例
console.log(cat)

在这里,cat__proto__指向Animal.prototype,这意味着cat可以访问Animal.prototype上的方法,如speak
Snipaste_2024-06-19_09-32-00.png


⛓️ 原型链的形成

当试图访问一个对象的属性或方法时,如果该对象本身没有定义,JavaScript引擎会向上查找其原型(__proto__指向的对象),这一过程会一直追溯到原型链的顶部,通常是Object.prototype。如果在那里还找不到,就会返回undefined

示例代码

console.log(cat.speak === Animal.prototype.speak); // true

这行代码确认了cat实例的speak方法确实是指向Animal.prototype上的speak方法,证实了继承关系的存在。

cat.speak(); // 输出: "I am an animal"

调用cat.speak()成功执行并打印出"I am an animal",这证明了cat实例能够正确地沿原型链访问到Animal.prototype上定义的speak方法。

console.log(cat.toString());

尽管在Animal构造函数或其原型上没有直接定义toString方法,cat.toString()仍然能够执行并按预期工作。这是因为所有JavaScript对象(除非被特殊修改)都默认从Object.prototype继承了toString方法。toString方法通常用于返回对象的字符串表示,对于普通的对象实例,默认情况下返回的是"[object Object]"

原型链是JavaScript实现继承的核心机制,它允许对象间接访问其原型链上定义的属性和方法,直至达到Object.prototype。这一机制不仅简化了代码复用,也是理解JavaScript面向对象编程的关键。通过上述示例,我们可以看到即便没有在每个对象或构造函数中显式定义所有方法,也可以通过原型链继承自上层原型或最终的Object.prototype,从而获得这些功能。


🔄 修改原型的影响

修改原型对象会影响所有通过该构造函数创建的实例。这是因为所有实例共享同一个原型对象。

Animal.prototype.speak = function() {
   
   
    console.log('Now I can talk too!');
};

cat.speak(); // 输出变为 "Now I can talk too!"

这里,我们修改了Animal.prototype上的speak方法,所有Animal的实例调用speak时都会反映出这一变化。

由于修改原型会影响到所有通过该构造函数创建的实例,开发中应当谨慎操作,以防止原型污染。一种常见做法是使用不可变(Immutable)的设计模式,或者在必要时为每个实例单独添加方法,而不是修改原型。

function giveUniqueVoice(animal, voice) {
   
   
    animal.speak = function() {
   
   
        console.log(voice);
    };
}

let specialCat = new Animal('Whiskers');
giveUniqueVoice(specialCat, 'Meow!');

specialCat.speak(); // 输出 "Meow!"
cat.speak(); // 输出 "My behavior has been changed!"

在这个例子中,我们通过giveUniqueVoice函数为特定实例specialCat添加了一个独特的speak方法,这样做不会影响到其他Animal实例的行为。


🏁 原型链的尽头

原型链的尽头,指的是JavaScript中对象原型链层级结构的最终点,这个终点是null。在JavaScript中,每个对象(除null外)都有一个内部属性称为[[Prototype]],它指向创建该对象的原型对象。这个原型对象本身也可能是一个对象,同样拥有自己的[[Prototype]],如此形成了所谓的原型链。

当我们尝试访问一个对象的属性或方法时,如果在该对象自身找不到,JavaScript引擎会继续在其原型对象中查找,即沿着原型链向上遍历。这一过程会一直持续到遇到一个原型对象的[[Prototype]]null的点,这标志着原型链的终点。换句话说,null作为原型链的终点,表示没有更进一步的原型可以继承或查找。

Object.prototype是大多数对象原型链中倒数第二层的对象,几乎所有JavaScript对象(直接或间接)的原型链最终都会追溯到Object.prototype,而Object.prototype[[Prototype]]则为null,形成了原型链的闭环。

如下代码所示:

class Animal {
   
   
    name = 'Animal';
    speak() {
   
   
        console.log('I am an animal');
    }
    constructor(name) {
   
   
        this.name = name;
    }
}
class Dog extends Animal {
   
   
    constructor(name) {
   
   
        super(name); 
    }
}
const myDog = new Dog('Rex');
console.log(myDog)

Snipaste_2024-06-19_09-33-46.png

  1. myDog (Dog实例):
    • 直接属性:name = 'Rex',这是因为在Dog类的构造函数中,通过super(name)调用了父类Animal的构造函数,并将'Rex'作为参数传递,从而设置了实例的name属性。
    • 内部属性[[Prototype]]指向Dog.prototype
  1. Dog.prototype:
    • 这是Dog类的原型对象,默认包含一个constructor属性指向Dog构造函数自身。
    • 内部属性[[Prototype]]指向Animal.prototype,因为Dog类通过extends Animal继承了Animal类,所以其原型链会链接到Animal类的原型对象。
  1. Animal.prototype:
    • 包含了Animal类定义的方法,如speak()
    • 内部属性[[Prototype]]指向Object.prototype,这是所有JavaScript对象原型链的标准终点前一站,表明Animal类的原型也是基于基础的JavaScript对象构建的。
  1. Object.prototype:
    • 这是所有JavaScript对象的原型链最终到达的地方,包含了像toString(), valueOf()等基本方法。
    • 内部属性[[Prototype]]null,标志着原型链的终点。

综上所述,myDog的原型链路径如下:

  • myDog -> Dog.prototype -> Animal.prototype -> Object.prototype -> null

这条链展示了从myDog实例出发,逐级向上通过原型链查找属性和方法的过程,直到抵达null,即原型链的顶层。

为什么null标志着结束?

null作为Object.prototype[[Prototype]],是一个特意的设计选择,它表示这条原型链到此为止,没有更进一步的原型可供查找。null既不是对象也不是函数,它是一种特殊的值,用来表示空值或者尚未赋值的状态。在原型链上下文中,它起到了终止链式查找的作用,防止无限循环查找。

实际意义 🌐

理解原型链的这一终端特性,对开发者来说有几个重要含义:

  1. 性能考量:它确保了属性查找有一个明确的终点,避免了无止境的循环搜索,从而优化了访问速度。
  2. 对象基础:揭示了所有对象共享的基本行为,强调了JavaScript中一切皆对象的原则,即使是null这样的特殊值也是对象行为逻辑的一部分。
  3. 继承体系的清晰度:有助于开发者构建清晰的继承结构,知道何时应该直接在对象上定义方法,何时通过原型链继承,以及如何避免无意中修改基础对象的行为。

原型链的尽头指向Object.prototype,其[[Prototype]]null,这一设计精巧地构建了JavaScript对象继承的基础框架。掌握这一概念,对于深入理解对象间的继承关系、避免常见的原型链错误,以及高效地设计和维护代码结构都是至关重要的。它是通往JavaScript高级编程之路上的一块基石。


🔄 继承的实现方式

1. 原型链继承 🌀

最直接的继承方式就是通过原型链。上面的例子已经展示了这一点,但我们可以更明确地设置原型链:

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

// 使用Animal的prototype作为Dog.prototype的原型
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修复constructor指向

Dog.prototype.bark = function() {
   
   
    console.log('Woof!');
};

let myDog = new Dog('Rex');
myDog.speak(); // I am an animal
myDog.bark(); // Woof!

这段代码展示了如何使用原型链继承JavaScript中实现继承。这里是逐步解析:

  • 定义子类构造函数 Dog:

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

    Dog 构造函数接收一个参数 name,并将其作为实例的 name 属性。

  • 设置 Dog.prototype 的原型为 Animal.prototype 的副本:

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

    这行代码是关键,它使用 Object.create 方法创建了 Animal.prototype 的一个新对象,然后将其赋值给 Dog.prototype。这样一来,所有通过 Dog 构造函数创建的实例都会在其原型链上找到 Animal.prototype,从而继承了 Animal 的属性和方法。

  • . 修复 constructor 指向:

     Dog.prototype.constructor = Dog;
    

    由于我们直接改变了 Dog.prototype 的指向,原本指向 Dogconstructor 现在会指向 Animal。为了修正这一点,我们需要手动将其设置回 Dog

  • Dog.prototype 上定义 bark 方法:

    Dog.prototype.bark = function() {
         
         
        console.log('Woof!');
    };
    

    这为 Dog 的实例添加了一个独有的方法 bark

  • 创建 Dog 的实例并测试:

    let myDog = new Dog('Rex');
    myDog.speak(); // 输出 "I am an animal"
    myDog.bark(); // 输出 "Woof!"
    

    通过 new Dog('Rex') 创建了一个名为 "Rex" 的狗实例。由于 Dog.prototype 指向了 Animal.prototype 的副本,myDog 可以访问到 Animal 上的 speak 方法。同时,它也有自己特有的 bark 方法。

综上所述,这段代码演示了如何利用原型链实现JavaScript中的继承,让子类能够复用父类的属性和方法,同时也能够扩展自己的特性。

  • 特点:简单直接,通过将子类型的原型指向父类型的实例,实现方法的继承。
  • 优点:易于实现,节省内存(共享方法)。
  • 缺点:父类的引用类型属性会被所有子类实例共享;无法在构造函数中向父类传递参数。

    2. 构造函数继承 🏗️

另一种方式是通过在子类构造函数内部调用超类构造函数,这种方式不涉及原型链,而是直接复制属性。

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

function Dog(name) {
   
   
    Animal.call(this, name);
    this.species = 'Canine';
}

let myDog = new Dog('Rex');
console.log(myDog.name); // Rex
console.log(myDog.species); // Canine

这段代码展示了构造函数继承的方式实现JavaScript中的继承。下面是详细的解析:

  • 定义子类构造函数 Dog:

    function Dog(name) {
         
         
        Animal.call(this, name);
        this.species = 'Canine';
    }
    
    • Dog 构造函数内部,通过 Animal.call(this, name) 调用了 Animal 构造函数。这里的 call 方法改变了 Animal 内部 this 的指向,使其指向当前 Dog 实例,从而使得 Dog 实例能够继承 Animal 的属性和方法。这就是构造函数继承的核心所在。
    • 接着,Dog 构造函数还定义了自己的属性 species,设置为 'Canine'
  • 创建 Dog 实例并检查属性:

    let myDog = new Dog('Rex');
    console.log(myDog.name); // 输出 "Rex"
    console.log(myDog.species); // 输出 "Canine"
    

    通过 new Dog('Rex') 创建了一个 Dog 的实例,并传入名字 'Rex'。由于在 Dog 构造函数中调用了 Animal.call(this, name)myDog 实例继承了 Animalname 属性,值为 'Rex'。同时,myDog 实例还有自己特有的属性 species,值为 'Canine'

总结来说,这段代码演示了如何通过在子类构造函数内部手动调用父类构造函数(并使用 callapply 方法绑定正确的 this 上下文)来实现继承,这种方式允许子类继承父类的属性,同时可以扩展自己的属性和方法。

  • 特点:通过在子类构造函数内部调用父类构造函数,实现属性的继承。
  • 优点:每个实例都有自己的属性副本,解决了原型链继承中的属性共享问题。
  • 缺点:只能继承属性,无法继承方法;每次实例化都会创建方法的新副本,浪费内存。

    3. 组合继承(经典继承)👨‍👩‍👧‍👦

结合原型链继承和构造函数继承,是最常用的继承模式。

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

Animal.prototype.speak = function() {
   
   
    console.log('I am an animal');
};

function Dog(name) {
   
   
    Animal.call(this, name);
    this.species = 'Canine';
}

// 使用Animal的prototype作为Dog.prototype的原型
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() {
   
   
    console.log('Woof!');
};

let myDog = new Dog('Rex');
myDog.speak(); // I am an animal
myDog.bark(); // Woof!
console.log(myDog.species); // Canine

这段代码展示了JavaScript中的一种继承模式,结合了构造函数继承原型链继承(也称作组合继承),是实现继承的常用方式之一。下面是详细的解析:

  • 定义子类构造函数 Dog:

    function Dog(name) {
         
         
        Animal.call(this, name);
        this.species = 'Canine';
    }
    

    Dog 构造函数内部,使用 Animal.call(this, name) 调用了 Animal 构造函数,实现了属性的继承(构造函数继承)。同时,它还定义了特有的属性 species

  • 设置 Dog.prototype 并修复构造函数指针:

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

    这两行代码通过 Object.create(Animal.prototype) 设置了 Dog.prototype,使得 Dog 的实例可以通过原型链访问到 Animal.prototype 上的方法,实现了方法的继承(原型链继承)。然后,修正了构造函数指针,因为默认情况下,Object.create 会将原型链上原有的构造函数指针设为 Animal

  • Dog.prototype 上定义 bark 方法:

    Dog.prototype.bark = function() {
         
         
        console.log('Woof!');
    };
    

    Dog 类添加了特有的方法 bark

  • 创建 Dog 实例并测试:

    let myDog = new Dog('Rex');
    myDog.speak(); // 输出 "I am an animal"
    myDog.bark(); // 输出 "Woof!"
    console.log(myDog.species); // 输出 "Canine"
    

    myDog 既是 Dog 的实例,也能够访问到 Animalspeak 方法,同时具有 Dog 特有的 bark 方法和 species 属性,展示了组合继承的特性。

这种组合继承方式综合了构造函数继承和原型链继承的优点,既能够继承实例属性,又能有效复用方法,是JavaScript中较为完善的继承实现方式之一。

  • 特点:结合了原型链继承和构造函数继承的优点,是最常用的继承模式。
  • 优点:既能继承属性也能继承方法,且每个实例都有自己的属性副本,同时方法又是共享的。
  • 缺点:构造函数中调用了两次父类构造函数(一次在子类构造函数内部,一次在原型链设定时),稍微有些冗余。

    4. ES6 Class继承 🎉

ES6引入了基于class的语法糖,使得继承更加清晰易懂。

class Animal {
   
   
    constructor(name) {
   
   
        this.name = name;
    }
    speak() {
   
   
        console.log('I am an animal');
    }
}

class Dog extends Animal {
   
   
    constructor(name) {
   
   
        super(name); // 调用父类构造函数
        this.species = 'Canine';
    }
    bark() {
   
   
        console.log('Woof!');
    }
}

let myDog = new Dog('Rex');
myDog.speak(); // I am an animal
myDog.bark(); // Woof!
console.log(myDog.species); // Canine

这段代码展示了使用ES6的class语法来实现面向对象编程中的继承。下面是代码的详细解析:

  • 定义基类 Animal:

    class Animal {
         
         
        constructor(name) {
         
         
            this.name = name;
        }
        speak() {
         
         
            console.log('I am an animal');
        }
    }
    

    Animal 类通过 constructor 方法定义了一个构造器,用于初始化 name 属性,并定义了一个 speak 方法。

  • 定义子类 Dog 继承 Animal:

    class Dog extends Animal {
         
         
        constructor(name) {
         
         
            super(name); // 调用父类构造函数
            this.species = 'Canine';
        }
        bark() {
         
         
            console.log('Woof!');
        }
    }
    
    • extends 关键字表明 Dog 类继承自 Animal 类。
    • Dog 的构造函数中,super(name) 调用了父类的构造函数,传递了参数 name,这是继承父类属性的关键步骤。
    • 定义了 Dog 特有的属性 species 和方法 bark
  • 创建 Dog 实例并测试:

    let myDog = new Dog('Rex');
    myDog.speak(); // 输出 "I am an animal"
    myDog.bark(); // 输出 "Woof!"
    console.log(myDog.species); // 输出 "Canine"
    

    通过 new Dog('Rex') 创建了一个 Dog 的实例,它继承了 Animal 类的所有属性和方法,同时拥有自己的特有属性 species 和方法 bark

使用 class 语法实现继承简化了传统构造函数和原型链的复杂性,提供了更接近于其他面向对象语言的继承模型,使得代码更加清晰和易于理解。

  • 特点:引入了面向对象编程中的class语法,使得继承的语义更加清晰,更接近其他面向对象语言。
  • 优点:语法简洁,易于理解,支持静态方法和类属性,提高了代码的可读性和可维护性。
  • 缺点:本质上仍然是基于原型,只是语法糖,新手可能会误解为传统的类继承模型。

每种继承方式的选择应根据实际项目需求和团队习惯来决定。在ES6及以后的版本中,推荐使用class语法进行继承,它不仅代码更加优雅,而且更易于理解和维护。然而,理解背后的基本原理——原型链和构造函数——对于深入掌握JavaScript的面向对象编程是至关重要的。


🚀 实战示例:创建可扩展的动物王国

假设我们要构建一个简单的动物王国模拟器,其中包含各种动物,它们能发出不同的叫声。我们想要设计一个灵活的架构,使得新增动物种类时,无需修改现有代码,同时让每种动物都能继承通用行为(如发出叫声)和拥有特有行为。

1. 基础动物类 (Animal)

// 定义基础动物构造函数,接收一个name参数初始化动物名字
function Animal(name) {
   
   
    this.name = name; // 使用this关键字将传入的name赋值给新创建对象的name属性
}

// 在Animal的原型对象上定义一个speak方法,模拟动物发出声音
Animal.prototype.speak = function() {
   
   
    console.log('Some generic sound'); // 打印通用的声音文本
};

2. 具体动物类 (Dog & Cat)

// Dog构造函数,继承Animal
function Dog(name) {
   
   
    Animal.call(this, name); // 使用Animal.call调用超类构造函数,确保name属性被正确初始化
}

// 设置Dog的原型为Animal的原型的一个新对象实例,实现继承
Dog.prototype = Object.create(Animal.prototype);
// 修复构造函数指针,确保构造函数引用正确
Dog.prototype.constructor = Dog;

// 覆盖speak方法,使Dog有特定的叫声
Dog.prototype.speak = function() {
   
   
    console.log('Woof!'); // 打印Dog的叫声
};

// 类似的操作创建Cat类
function Cat(name) {
   
   
    Animal.call(this, name);
}

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

Cat.prototype.speak = function() {
   
   
    console.log('Meow!'); // 打印Cat的叫声
};

3. 实战应用

// 创建Dog和Cat的实例
const myDog = new Dog('Rex');
const myCat = new Cat('Whiskers');

// 调用各自的speak方法
myDog.speak(); // 输出: Woof!
myCat.speak(); // 输出: Meow!

// 动态添加行为到Animal原型,所有子类实例都能访问
Animal.prototype.sleep = function() {
   
   
    console.log(`${
     
     this.name} is sleeping.`); // 打印睡觉信息,使用模板字符串插入实例的名字
};

// 调用新添加的sleep方法
myDog.sleep(); // 输出: Rex is sleeping.
myCat.sleep(); // 输出: Whiskers is sleeping.

这个示例演示了如何利用原型链实现继承,保持代码的灵活性和扩展性。通过Object.create方法建立原型链关系,确保了子类能够访问父类的属性和方法,同时也能够覆盖或添加新方法以实现特有行为。动物王国的模拟展示了多态性,即不同对象对同一消息(如speak)做出不同响应的能力,以及代码的可维护性和扩展性。


📚 总结

本文全面解析了JavaScript中的继承机制,核心围绕原型链这一核心概念展开,阐述了其在对象继承中的作用与重要性,并介绍了几种主要的继承实现方式。以下是文章内容的概括:

📌 原型基础

  • 每个JavaScript对象都隐含一个[[Prototype]]属性,通常通过__proto__访问,指向创建它的构造函数的prototype对象。
  • 构造函数的prototype本身是个对象,包含可被实例共享的方法和属性。
  • 示例展示了如何通过原型链,实例能访问到构造函数原型上的方法。

📌 原型链的形成与查找规则

  • 当访问对象的属性或方法时,若对象自身未定义,则会沿其原型链向上查找,直至Object.prototype,最后到null终止。
  • 解释了所有对象共享Object.prototype上的基本方法,如toString()等。

📌 修改原型的影响

  • 修改原型对象会影响所有通过该构造函数创建的实例,因它们共享同一原型。
  • 强调需谨慎修改原型以防“原型污染”,建议采用不可变模式或针对实例单独添加方法。

📌 原型链的尽头

  • 深入探讨了Object.prototype[[Prototype]]null的意义,作为原型链的终点,保证了查找过程的终止。

📌 继承的实现方式

  1. 原型链继承:直接设置子类型的原型为父类型的实例,简单直接,共享方法,但需注意构造函数的修正。
  2. 构造函数继承:子类构造函数内部调用父类构造函数,实现属性继承,但不继承方法且方法不共享。
  3. 组合继承:结合上述两者,最常用,既继承属性也继承方法,但构造函数被调用两次。
  4. ES6 Class继承:引入class语法,简化继承表达,提供更清晰的面向对象编程风格,本质仍是基于原型。

每种继承方式都有其适用场景与优缺点,理解这些机制有助于开发者根据具体需求选择合适的继承策略,提升代码的效率与可维护性。文章强调了深入理解原型链与构造函数原理对于掌握JavaScript面向对象编程的重要性。


🔗 相关链接


目录
相关文章
|
4月前
|
存储 安全 API
Next.js 实战 (九):使用 next-auth 完成第三方身份登录验证
这篇文章介绍了next-auth,一个为Next.js设计的身份验证库,支持多种认证方式,如电子邮件和密码、OAuth2.0提供商(如Google、GitHub、Facebook等)以及自定义提供商。文章包含了如何配置Github Provider以及会话管理,并提到了适配器Adapters在next-auth中的作用。最后,文章强调了next-auth的强大功能值得进一步探索。
258 10
|
6月前
|
JavaScript 前端开发
如何在 JavaScript 中使用 __proto__ 实现对象的继承?
使用`__proto__`实现对象继承时需要注意原型链的完整性和属性方法的正确继承,避免出现意外的行为和错误。同时,在现代JavaScript中,也可以使用`class`和`extends`关键字来实现更简洁和直观的继承语法,但理解基于`__proto__`的继承方式对于深入理解JavaScript的面向对象编程和原型链机制仍然具有重要意义。
|
2月前
|
监控 JavaScript 前端开发
MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver:原理与实战案例
MutationObserver 是一个非常强大的 API,提供了一种高效、灵活的方式来监听和响应 DOM 变化。它解决了传统 DOM 事件监听器的诸多局限性,通过异步、批量的方式处理 DOM 变化,大大提高了性能和效率。在实际开发中,合理使用 MutationObserver 可以帮助我们更好地控制 DOM 操作,提高代码的健壮性和可维护性。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver:原理与实战案例
|
2月前
|
数据采集 JavaScript 前端开发
一站搞定原型链:深入理解JavaScript的继承机制
综上所述,可以得出: 1. 原型链是对象通过原型实现属性和方法继承的一种机制。 2. 每个对象都有一个 __proto__ 属性,指向它的原型对象。 3. 每个函数(包括构造函数)都有一个 prototype 属性,指向一个对象,这个对象的属性和方法可以被实例共享。 4. 构造函数创建对象时,新对象的 __proto__ 属性指向构造函数的 prototype 对象。 5. 继承可以通过设置原型对象实现,也可以使用 ES6 的 class 语法糖。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一
|
4月前
|
设计模式 数据安全/隐私保护
Next.js 实战 (七):浅谈 Layout 布局的嵌套设计模式
这篇文章介绍了在Next.js框架下,如何处理中后台管理系统中特殊页面(如登录页)不包裹根布局(RootLayout)的问题。作者指出Next.js的设计理念是通过布局的嵌套来创建复杂的页面结构,这虽然保持了代码的整洁和可维护性,但对于特殊页面来说,却造成了不必要的布局包裹。文章提出了一个解决方案,即通过判断页面的skipGlobalLayout属性来决定是否包含RootLayout,从而实现特殊页面不包裹根布局的目标。
177 33
|
4月前
|
中间件 API
Next.js 实战 (八):使用 Lodash 打包构建产生的“坑”?
这篇文章介绍了作者在使用Nextjs15进行项目开发时遇到的部署问题。在部署过程中,作者遇到了打包构建时的一系列报错,报错内容涉及动态代码评估在Edge运行时不被允许等问题。经过一天的尝试和调整,作者最终删除了lodash-es库,并将radash的部分源码复制到本地,解决了打包报错的问题。文章最后提供了项目的线上预览地址,并欢迎读者留言讨论更好的解决方案。
112 10
|
5月前
|
前端开发 API 开发者
Next.js 实战 (五):添加路由 Transition 过渡效果和 Loading 动画
这篇文章介绍了Framer Motion,一个为React设计的动画库,提供了声明式API处理动画和页面转换,适合创建响应式用户界面。文章包括首屏加载动画、路由加载Loading、路由进场和退场动画等主题,并提供了使用Framer Motion和next.js实现这些动画的示例代码。最后,文章总结了这些效果,并邀请读者探讨更好的实现方案。
139 7
|
4月前
|
JavaScript 前端开发 API
Next.js 实战 (六):如何实现文件本地上传
这篇文章介绍了在Next.js中如何实现文件上传到本地的方法。文章首先提到Next.js官方文档中没有提供文件上传的实例代码,因此开发者需要自行实现,通常有两种思路:使用Node.js原生上传或使用第三方插件如multer。接着,文章选择了使用Node.js原生上传的方式来讲解实现过程,包括如何通过哈希值命名文件、上传到指定目录以及如何分类文件夹。然后,文章展示了具体的实现步骤,包括编写代码来处理文件上传,并给出了代码示例。最后,文章通过一个效果演示说明了如何通过postman模拟上传文件,并展示了上传后的文件夹结构。
Next.js 实战 (六):如何实现文件本地上传
|
5月前
Next.js 实战 (二):搭建 Layouts 基础排版布局
本文介绍了作者在Next.js v15.x版本发布后,对一个旧项目的重构过程。文章详细说明了项目开发规范配置、UI组件库选择(最终选择了Ant-Design)、以及使用Ant Design的Layout组件实现中后台布局的方法。文末展示了布局的初步效果,并提供了GitHub仓库链接供读者参考学习。
164 1
Next.js 实战 (二):搭建 Layouts 基础排版布局
|
4月前
|
监控 安全 中间件
Next.js 实战 (十):中间件的魅力,打造更快更安全的应用
这篇文章介绍了什么是Next.js中的中间件以及其应用场景。中间件可以用于处理每个传入请求,比如实现日志记录、身份验证、重定向、CORS配置等功能。文章还提供了一个身份验证中间件的示例代码,以及如何使用限流中间件来限制同一IP地址的请求次数。中间件相当于一个构建模块,能够简化HTTP请求的预处理和后处理,提高代码的可维护性,有助于创建快速、安全和用户友好的Web体验。