普通视图

发现新文章,点击刷新页面。
今天 — 2025年12月2日首页

你真的理解了 javascript 中的原型及原型链?

2025年12月2日 18:04

JavaScript原型与原型链:javascript 继承的基础

引言

故事开始于

面试官:“说说你对原型以及原型链的理解”

我:原型是这样的..., 原型链是这样的..., 说的很抽象,听得也很抽象

面试官接着问:“说说javascript 怎么实现继承的

作为前端开发者,我们经常会听到「原型」和「原型链」这两个概念,但你真的理解它们吗?它们是JavaScript面向对象编程的核心机制,掌握它们对于理解JavaScript的运行原理至关重要。

本文将从基础概念出发,逐步深入解析JavaScript原型与原型链的工作原理,结合大量代码示例和可视化图解,让你轻松掌握这一核心知识点。

一、原型的基本概念

1. 什么是原型?

在JavaScript中,每个对象都有一个原型对象(__proto__),对象可以从原型中继承属性和方法。原型对象也可以有自己的原型,这样就形成了一个链式结构,称为「原型链」。

通过console控制台,可以看到foo对象_proto_对象上面有个age等于18的属性,而age又是通过构造函数Foo的prototype添加上去的。

c4c351bf-727d-4cae-b1e7-f689626c09f9.png

所以有了 foo.__proto__ === Foo.prototype

2. 原型的作用

原型主要有两个作用:

  • 属性继承:对象可以继承原型的属性
  • 方法共享:多个对象可以共享原型上的方法,节省内存空间

3. 代码示例:原型的基本使用

// 创建一个普通对象
const person = {
  name: 'John',
  age: 30
};

// 获取person的原型
const proto = Object.getPrototypeOf(person);
console.log(proto); // 输出:[Object: null prototype] {}
console.log(proto === Object.prototype); // 输出:true

二、__proto__与prototype的区别

这是初学者最容易混淆的两个概念,让我们来彻底搞清楚它们:

1. proto(隐式原型)

  • 定义:每个对象都有一个__proto__属性,指向它的原型对象
  • 作用:用于实现原型链查找
  • 注意:这是一个非标准属性,推荐使用Object.getPrototypeOf()Object.setPrototypeOf()代替

2. prototype(显式原型)

  • 定义:只有函数才有prototype属性
  • 作用:当函数作为构造函数使用时,新创建的对象会将这个prototype作为自己的__proto__
  • 组成prototype对象包含constructor属性,指向构造函数本身

3. 可视化对比

特性 proto prototype
所属对象 所有对象 只有函数
指向 对象的原型 构造函数创建的实例的原型
作用 实现原型继承 定义构造函数的实例共享属性和方法
标准性 非标准(建议使用Object.getPrototypeOf) 标准属性

4. 代码示例:__proto__与prototype

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

// 构造函数的prototype属性
console.log(Person.prototype); // 输出:Person {}(包含constructor属性)

// 创建实例
const alice = new Person('Alice');

// 实例的__proto__指向构造函数的prototype
console.log(alice.__proto__ === Person.prototype); // 输出:true

// 构造函数的prototype的constructor指向构造函数
console.log(Person.prototype.constructor === Person); // 输出:true

三、构造函数与原型的关系

1. 构造函数创建实例的过程

当使用new关键字调用构造函数创建实例时,发生了以下几件事:

  1. 创建一个新的空对象
  2. 将这个新对象的__proto__指向构造函数的prototype
  3. 将构造函数的this指向这个新对象
  4. 执行构造函数体内的代码
  5. 如果构造函数没有返回对象,则返回这个新对象

2. 代码示例:构造函数创建实例

// 构造函数
function Car(brand, model) {
  this.brand = brand;
  this.model = model;
}

// 在原型上添加方法
Car.prototype.drive = function() {
  console.log(`驾驶 ${this.brand} ${this.model}`);
};

// 创建两个实例
const car1 = new Car('Toyota', 'Camry');
const car2 = new Car('Honda', 'Accord');

// 调用原型上的方法
car1.drive(); // 输出:驾驶 Toyota Camry
car2.drive(); // 输出:驾驶 Honda Accord

// 两个实例共享同一个原型方法
console.log(car1.drive === car2.drive); // 输出:true

四、原型链的形成与查找机制

1. 什么是原型链?

当访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript会沿着__proto__属性向上查找,直到找到该属性或方法,或者到达原型链的末端(null)。这个链式查找结构就是「原型链」。

2. 原型链的末端

原型链的末端是Object.prototype,它的__proto__指向null,表示原型链的结束。

// Object.prototype是原型链的顶端之一
console.log(Object.prototype.__proto__); // 输出:null

3. 代码示例:原型链查找

// 创建对象
const obj = {};

// obj自身没有toString方法
console.log(obj.hasOwnProperty('toString')); // 输出:false

// 但可以调用toString方法,因为它继承自Object.prototype
console.log(obj.toString()); // 输出:[object Object]

// 原型链:obj -> Object.prototype -> null
console.log(obj.__proto__ === Object.prototype); // 输出:true
console.log(Object.prototype.__proto__ === null); // 输出:true

4. 完整原型链示例

// 构造函数
function Animal(type) {
  this.type = type;
}

// 原型方法
Animal.prototype.eat = function() {
  console.log('进食中...');
};

// 子类构造函数
function Dog(name, breed) {
  Animal.call(this, 'dog'); // 调用父类构造函数
  this.name = name;
  this.breed = breed;
}

// 设置Dog的原型为Animal的实例
Dog.prototype = Object.create(Animal.prototype);
// 修复constructor指向
Dog.prototype.constructor = Dog;

// Dog的原型方法
Dog.prototype.bark = function() {
  console.log('汪汪汪!');
};

// 创建实例
const myDog = new Dog('Buddy', 'Golden Retriever');

// 访问自身属性
console.log(myDog.name); // 输出:Buddy

// 访问继承自Dog.prototype的方法
myDog.bark(); // 输出:汪汪汪!

// 访问继承自Animal.prototype的方法
myDog.eat(); // 输出:进食中...

// 访问继承自Object.prototype的方法
console.log(myDog.toString()); // 输出:[object Object]

// 原型链:myDog -> Dog.prototype -> Animal.prototype -> Object.prototype -> null

五、原型链的实际应用

1. 实现继承

原型链是JavaScript实现继承的主要方式。通过将子类的原型设置为父类的实例,可以实现属性和方法的继承。

// 父类
function Parent(name) {
  this.name = name;
  this.family = 'Smith';
}

Parent.prototype.sayFamily = function() {
  console.log(`My family name is ${this.family}`);
};

// 子类
function Child(name, age) {
  Parent.call(this, name); // 继承父类属性
  this.age = age;
}

// 继承父类方法
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

Child.prototype.sayAge = function() {
  console.log(`I'm ${this.age} years old`);
};

// 使用
const child = new Child('John', 10);
child.sayFamily(); // 输出:My family name is Smith
child.sayAge(); // 输出:I'm 10 years old

2. 扩展内置对象

我们可以通过修改内置对象的原型来扩展其功能:

// 扩展Array原型,添加求和方法
Array.prototype.sum = function() {
  return this.reduce((total, item) => total + item, 0);
};

// 使用扩展后的方法
const numbers = [1, 2, 3, 4, 5];
console.log(numbers.sum()); // 输出:15

// 扩展String原型,添加反转方法
String.prototype.reverse = function() {
  return this.split('').reverse().join('');
};

// 使用扩展后的方法
const str = 'hello';
console.log(str.reverse()); // 输出:olleh

注意:虽然可以扩展内置对象,但不推荐在生产环境中使用,因为可能会与其他库冲突。

3. 原型链实现对象类型检查

// 判断对象类型的函数
function getType(obj) {
  if (obj === null) return 'null';
  if (typeof obj !== 'object') return typeof obj;
  
  // 使用原型链判断具体类型
  const proto = Object.getPrototypeOf(obj);
  const constructor = proto.constructor;
  return constructor.name;
}

// 测试
console.log(getType(123)); // 输出:number
console.log(getType('hello')); // 输出:string
console.log(getType(true)); // 输出:boolean
console.log(getType(null)); // 输出:null
console.log(getType([])); // 输出:Array
console.log(getType({})); // 输出:Object

六、常见误区与注意事项

1. 误区一:所有对象都是Object的实例

正确理解:除了Object.prototype本身,所有对象都是Object的实例吗?不完全是。比如:

// 创建一个没有原型的对象
const obj = Object.create(null);
console.log(obj.__proto__); // 输出:undefined
console.log(obj instanceof Object); // 输出:false

2. 误区二:原型上的属性修改会立即反映到所有实例

正确理解:是的,但如果是直接给实例添加同名属性,会覆盖原型属性,而不是修改原型:

function Person() {}

Person.prototype.name = 'Anonymous';

const p1 = new Person();
const p2 = new Person();

console.log(p1.name); // 输出:Anonymous
console.log(p2.name); // 输出:Anonymous

// 修改原型属性
Person.prototype.name = 'Default';
console.log(p1.name); // 输出:Default
console.log(p2.name); // 输出:Default

// 给实例添加同名属性(覆盖)
p1.name = 'John';
console.log(p1.name); // 输出:John
console.log(p2.name); // 输出:Default(不受影响)

3. 注意事项:原型链查找的性能

原型链查找是有性能开销的,层级越深,查找速度越慢。因此:

  • 避免在原型链的深层定义常用属性和方法
  • 对于频繁访问的属性,可以考虑直接定义在对象本身

4. 注意事项:不要使用__proto__赋值

直接修改__proto__会影响对象的原型链,可能导致性能问题和意外行为。推荐使用:

  • Object.create()创建指定原型的对象
  • Object.setPrototypeOf()修改对象的原型

七、可视化理解原型链

为了更好地理解原型链,我们可以通过可视化的方式来呈现它的结构:

1. 简单对象的原型链

obj (实例对象)
  └── __proto__ → Object.prototype
                    └── __proto__ → null

2. 构造函数创建的对象原型链

instance (实例对象)
  └── __proto__ → Constructor.prototype
                    └── __proto__ → Object.prototype
                                      └── __proto__ → null

3. 继承关系的原型链

childInstance (子类实例)
  └── __proto__ → Child.prototype
                    └── __proto__ → Parent.prototype
                                      └── __proto__ → Object.prototype
                                                        └── __proto__ → null

4. 代码示例:可视化原型链

// 定义构造函数
function Grandparent() {
  this.grandparentProp = 'grandparent';
}

function Parent() {
  this.parentProp = 'parent';
}

function Child() {
  this.childProp = 'child';
}

// 设置继承关系
Parent.prototype = Object.create(Grandparent.prototype);
Child.prototype = Object.create(Parent.prototype);

// 创建实例
const child = new Child();

// 可视化原型链
console.log('child:', child);
console.log('child.__proto__ (Child.prototype):', child.__proto__);
console.log('child.__proto__.__proto__ (Parent.prototype):', child.__proto__.__proto__);
console.log('child.__proto__.__proto__.__proto__ (Grandparent.prototype):', child.__proto__.__proto__.__proto__);
console.log('child.__proto__.__proto__.__proto__.__proto__ (Object.prototype):', child.__proto__.__proto__.__proto__.__proto__);
console.log('child.__proto__.__proto__.__proto__.__proto__.__proto__ (null):', child.__proto__.__proto__.__proto__.__proto__.__proto__);

八、原型与现代JavaScript

1. ES6 Class与原型的关系

ES6引入了class语法,但它只是原型继承的语法糖,底层仍然是基于原型链实现的:

// ES6 Class
class Animal {
  constructor(type) {
    this.type = type;
  }
  
  eat() {
    console.log('进食中...');
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super('dog');
    this.name = name;
    this.breed = breed;
  }
  
  bark() {
    console.log('汪汪汪!');
  }
}

// 等价于原型继承
console.log(typeof Animal); // 输出:function
console.log(Dog.prototype.__proto__ === Animal.prototype); // 输出:true

2. 原型与组合继承

现代JavaScript中,我们通常使用组合继承模式,结合原型链和构造函数:

// 组合继承模式
function Parent(name) {
  this.name = name;
}

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

function Child(name, age) {
  // 继承属性
  Parent.call(this, name);
  this.age = age;
}

// 继承方法
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

Child.prototype.sayAge = function() {
  console.log(this.age);
};

九、总结

通过本文的学习,我们已经全面了解了JavaScript原型与原型链的核心概念:

  1. 原型:每个对象都有一个原型,可以继承原型的属性和方法
  2. proto:对象的隐式原型,指向它的原型对象
  3. prototype:函数的显式原型,用于构造函数创建实例时的原型指向
  4. 原型链:对象通过__proto__形成的链式结构,用于属性和方法的查找
  5. 继承:通过原型链实现对象间的继承关系

原型与原型链是JavaScript的核心机制,掌握它们对于理解JavaScript的运行原理、实现面向对象编程至关重要。希望本文的详细解析和丰富示例能帮助你彻底理解这一知识点。

思考与练习

  1. 为什么说原型链是JavaScript实现继承的基础?
  2. 如何优化原型链查找的性能?
  3. ES6 Class和传统原型继承有什么区别?
  4. 尝试实现一个完整的原型链继承案例
  5. 解释instanceof运算符的工作原理(提示:基于原型链)

欢迎在评论区分享你的理解和思考,让我们一起进步!

参考资料


如果你觉得本文对你有帮助,欢迎点赞、收藏、分享,也欢迎关注我,获取更多前端技术干货!

昨天 — 2025年12月1日首页

从border-image 到 mask + filer 实现圆角渐变边框

2025年12月1日 17:12

用 CSS Mask + Filter 实现高级渐变圆角边框

前言

故事开始于一张恶心人的设计UI稿开始,由于签了保密协议,只能切割设计稿,展示恶心的片段; 最近手头刚好有个大屏的项目,我们的设计师,于是乎,搞出了如下片段:

7784e481-facf-4d3b-a285-0a13b18f9101.png

90be2647-c1c8-450c-95c7-8684b83ecd8b.png

10c91969-8c93-4028-9036-f5a66479ec0a.png

650c9eaf-a76d-4502-a144-65d78a52aab2.png

cdc2af44-ae1d-43a9-90be-0f0c5001a10b.png

各位jym,你们想到哪些方案呢?评论区见! 最简单省事粗暴的方案,就是UI直接给切图,但俺们是有追求的(其实以前也干过),性能要有要求的于是乎采用以下方案实现!

前面2张图很好实现,border-image 渐变既可以很好实现; 后面三张设计图,是有圆角的,border-image 无法实现圆角,border-radius 可以实现圆角但无法实现渐变边框;

故事主角出现了mask + filter

图一:border-image:linear-gradient(90deg, #038AFE 0%, rgba(3, 138, 254, 0.3) 48%, #038AFE 100%) 0.5

图二:border-image: linear-gradient(90deg, #12c1ea 0%, rgba(3, 138, 254, .3) 50%, #12c1ea 100%) 1 1;

图三:

.mask{
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    border: 1px solid transparent;
    border-radius: 10px;
    -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
    mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    z-index: 0;
}

.mask::after{
    content: '';
    position: absolute;
    bottom: -5px;
    left: 0;
    right: 0;
    height: 100%;
    background: linear-gradient(180degrgba(01742550.250%#00AEFF 100%);
    filter: blur(10px);
    z-index: 0;
}

图四:跟图三其实一样的,只是伪类的高度设置不一样

.mask::after{
    content: '';
    position: absolute;
    bottom: -5px;
    left: 0;
    right: 0;
    height: 80%;
    background: linear-gradient(180degrgba(01742550.250%#00AEFF 100%);
    filter: blur(10px);
    z-index: 0;
}

图五:遮罩层都一样,不一样的是伪类,渐变色设置的技巧

.mask::after{
    content: '';
    position: absolute;
    bottom: -5px;
    left: 0;
    right: 0;
    height: 80%;
    background: linear-gradient(180deg#00AEFF 0%rgba(1179255019%rgba(1179255077%#00AEFF 96%);
    filter: blur(10px);
    z-index: 0;
}

核心概念

CSS Mask 属性

mask 属性允许我们使用图像、SVG 或渐变作为遮罩,控制元素的可见区域。它的工作原理类似于 Photoshop 中的遮罩层。

基本语法:

mask: <mask-source> <mask-mode> <mask-position> / <mask-size> <mask-repeat> <mask-origin> <mask-clip> <mask-composite>;

其中,mask-composite 属性定义了多个遮罩层如何组合。对于实现渐变边框,我们主要使用 exclude 值,它会显示两个遮罩层的非重叠区域。

daa70877-b139-4c1c-a697-43f66c387025.png

CSS Filter 属性

filter 属性用于对元素应用图形效果,如模糊、对比度、亮度等。我们可以结合 filter 来增强渐变边框的视觉效果。

常用 filter 函数:

函数名 描述 示例
blur() 模糊效果 blur(10px)
contrast() 对比度调整 contrast(150%)
brightness() 亮度调整 brightness(120%)
saturate() 饱和度调整 saturate(200%)
opacity() 透明度调整 opacity(0.8)

实现方法

方法一:基础 Mask 渐变边框

核心思路: 使用两层渐变遮罩,通过 mask-composite: exclude 实现边框效果。

7f260658-5a1a-4d1e-967b-93aba5445ceb.png

<div class="gradient-border basic">
    <h3>基础渐变边框</h3>
    <p>使用 mask-composite: exclude 实现</p>
</div>
.gradient-border.basic {
    /* 背景渐变 */
    background: linear-gradient(45deg, #96ceb4, #ffeead, #ff6b6b);
    
    /* 遮罩 */
    mask: 
        /* 内层遮罩:白色矩形,大小与元素相同,有圆角 */
        linear-gradient(#fff 0 0) content-box,
        /* 外层遮罩:白色矩形,大小与元素相同 */
        linear-gradient(#fff 0 0);
    /* 设置遮罩属性 */
    mask-composite: exclude;
    /* 内边距,控制边框宽度 */
    padding: 6px;
}

效果说明: 内层遮罩显示元素内容区域,外层遮罩显示整个元素,通过 exclude 组合,只显示两层遮罩的非重叠区域,即边框部分。

方法二:伪元素 + Mask

核心思路: 使用伪元素创建渐变背景,通过 mask 属性控制显示区域。

dfcf36b1-eb00-46f0-a482-472505e5dc0a.png

<div class='demo'>
    <div class="gradient-border pseudo-element">
        <h3>伪元素 + Mask</h3>
        <p>使用 ::before 伪元素创建渐变背景</p>
    </div>
</div>
.demo {
    position:relative;
}
.gradient-border.pseudo-element {
    position: absolute; 
    top: 0; 
    right: 0; 
    bottom: 0; 
    left: 0; 
    border: 1px solid transparent; 
    border-radius: 10px; -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0); 
    mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude; z-index: 0;
}

.gradient-border.pseudo-element::before {
    content: ''; 
    position: absolute; 
    bottom: -1px; 
    left: -1px; 
    right: -1px; 
    top:-1px;
    background: linear-gradient(180degrgba(01742550.250%#00AEFF 100%); filter: blur(10px);
    z-index: 0;
}

效果对比

实现方式 特点 适用场景
基础 Mask 简洁、性能好 简单渐变边框需求
伪元素 + Mask 灵活、易于控制 需要复杂边框样式

浏览器兼容性

CSS Mask 属性的浏览器支持情况如下:

浏览器 版本
Chrome 85+
Firefox 70+
Safari 15+
Edge 85+

对于不支持 mask-composite: exclude 的浏览器,我们可以使用 -webkit-mask-composite: xor 作为替代:

.gradient-border {
    mask-composite: exclude;
    /* Safari 兼容性 */
    -webkit-mask-composite: xor;
}

最佳实践

  1. 选择合适的实现方式:根据需求和浏览器支持情况,选择最适合的实现方式。
  2. 性能优化:避免在大量元素上同时使用复杂的 mask 和 filter 效果,这可能会影响页面性能。
  3. 降级方案:为不支持 mask 属性的浏览器提供降级样式,例如使用传统的 border 或伪元素方法。
  4. 渐变颜色选择:选择对比度适中、和谐的渐变颜色,避免过于刺眼的颜色组合。
  5. 边框宽度:边框宽度不宜过宽,一般建议在 2-8px 之间,这样视觉效果最佳。

总结

使用 CSS 的 maskfilter 属性实现渐变圆角边框是一种高级且灵活的方法,它具有以下优点:

  1. 代码简洁:相比传统的嵌套元素或复杂伪元素方法,代码更加简洁和易于维护。
  2. 效果丰富:可以实现多种高级效果,如毛玻璃边框、动态渐变边框等。
  3. 灵活可控:可以通过调整 mask 属性和 filter 属性,精确控制边框的外观和效果。
  4. 性能优良:相比 JavaScript 实现的动态边框,CSS 实现的性能更好。

虽然 mask 属性的浏览器支持还不是 100%,但在现代浏览器中已经得到了很好的支持。通过提供适当的降级方案,我们可以在项目中安全地使用这种方法。

希望本文对你理解和使用 CSS Mask + Filter 实现渐变圆角边框有所帮助!如果你有任何问题或想法,欢迎在评论区留言讨论。

参考资料

❌
❌