前端修行日记之JS 原型与 AI基础常识

一、原型链

js中没有传统的类继承,实现继承的方式就是通过原型链。理解原型链,是真正掌握 JS 继承、this、对象机制的关键。

1、核心概念

(1)每一个对象的内部都有一个\[Prototype],也就是它的原型

在标准实现中,该属性是不可访问的,但是浏览器提供了 proto(非标准)Object.getPrototypeOf()(标准方法) 来获取和访问它的原型。

通过打印原型,我们可以看到,有一个constructor属性,这个属性指回构造函数本身。

这里我们可以思考一下,普通函数、通过字面量直接创建的函数,并没有调用new去构造函数,为什么也能这样访问?答案就是:虽然我们没有手动去new,但底层js引擎帮我们自动完成了这一步,事实上都是通过new来实现的,只是通过字面量创建的对象,new的是Object这个对象(Object同时也是函数)。

(2)函数特有的属性------prototype 属性(除了箭头函数和某些内置函数)

该属性指向一个对象,称其为"原型对象"。

当该函数作为构造函数(执行new操作)时,新创建的对象的\[Prototype] 会指向构造函数的 prototype 对象。

补充面试题 :在一个函数作为构造函数被new的时候,都发生了哪些事情?
答案

(1)在一个构造函数被new的时候,首先js引擎在内存开辟一个新的空间,创建一个空对象

javascript 复制代码
let obj = {};

(2)将这个新对象的\[Prototype]属性指向构造函数的 prototype 属性,建立原型链关系(这一步使得新对象能访问构造函数原型上的所有属性和方法)

javascript 复制代码
obj.__proto__ = Constructor.prototype;
// 或使用更现代的写法
Object.setPrototypeOf(obj, Constructor.prototype);

(3)将构造函数内部的this关键字绑定到这个新创建的对象上,然后使用传入的参数执行构造函数体内的代码

javascript 复制代码
Constructor.call(obj, arg);
// 或
Constructor.apply(obj, [arg]);

(4)最后js引擎检查构造函数的返回值:如果没有返回值,或者返回一个基本数据类型,那么new就会忽略这个返回值,直接返回创建的对象;如果返回值是一个引用类型,那么new表达式就会放弃之前创建的对象,直接返回这个引用类型的对象

javascript 复制代码
return (typeof result === 'object' && result !== null) ? result : obj;

(3)原型对象本身也是一个对象

所以它也有自己的 \[Prototype],形成链式结构。

(4)原型链的终点

Object.prototype 的原型是 null,表示原型链的终点。null 没有任何属性,因此查找失败时返回 undefined。

代码示例

javascript 复制代码
function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function() {
  console.log(`Hello, I'm ${this.name}`);
};

const alice = new Person('Alice');
// alice.__proto__ === Person.prototype
// Person.prototype.__proto__ === Object.prototype
// Object.prototype.__proto__ === null

原型链示意图

alice → Person.prototype → Object.prototype → null

2、属性查找的过程

当访问 alice.sayHello 时:

  1. 先查找 alice 自身是否有 sayHello(没有)

  2. 沿着 proto 到 Person.prototype,找到了,调用它。

  3. 如果还没找到,继续到 Object.prototype,再没有则返回 undefined。

注意:设置属性(如 alice.age = 20)永远是在对象自身创建属性,不会修改原型链上的同名属性。

3、最容易混淆的点

(1)prototype 和 \[Prototype] 的区别

(2)Object、Function 特殊关系

js 复制代码
// 1
Function.__proto__ === Function.prototype  

// 2
Object.__proto__ === Function.prototype  

// 3
Function.prototype.__proto__ === Object.prototype  

// 4
Object.prototype.__proto__ === null

到这里可能很多人还是不理解原型链,特别懵逼,没关系,我来解释几个比较难理解的地方:

  1. 首先js中,一切皆对象(基本类型除外),所以不管是函数还是数组等都是具有\[Prototype]的,我们都可以通过__proto__或者Object.getPrototypeOf()去访问它的原型。
  2. 原型是什么呢?原型实际上就是构造函数 prototype 属性所指向的那个对象。这个属性的作用主要是挂载一些公共的方法、属性,实现复用。
  3. constructor在原型对象上,也就是在构造函数的prototype属性上,指回构造函数本身。

我们可以尝试手动画一个原型链出来,帮助更好的理解原型链:

这个是我画的原型链,看着可能费劲点,原型链本身也比较复杂,所以还是建议自己尝试着画一画。

个人建议使用Mermaid语法尝试去画,因为它是代码形式,逻辑更清晰,画完之后还能让AI帮忙检查;如果直接画图,AI识别效果可能不好,再加上自己逻辑不清晰,容易出错(这里我就吃了个大亏)。

4、原型链常见面试考点

考点1:原型链的基本结构

题目形式:问 实例.proto === 构造函数.prototype 是否成立;或者画原型链关系图。

核心

  • 每个对象都有 proto(即 \[Prototype])。

  • 每个函数(除箭头函数等)都有 prototype 属性,指向其实例的原型对象。

  • 构造函数.prototype.proto === Object.prototype,Object.prototype.proto === null。

常考示例

javascript 复制代码
function Foo() {}
const obj = new Foo();
console.log(obj.__proto__ === Foo.prototype);        // true
console.log(Foo.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__);            // null

考点2:属性查找与遮蔽(遮蔽效应)

题目形式:访问对象属性时,JS 引擎如何查找?如何判断属性是自身还是继承来的?

核心

  • 先找自身属性,没有则沿原型链向上找。

  • 自身设置属性不会影响原型(除非同名属性已存在于原型上,但赋值仍是在自身创建或覆盖自身属性)。

考点示例

javascript 复制代码
const parent = { a: 1, b: 2 };
const child = Object.create(parent);
child.a = 100;
console.log(child.a);      // 100(自身属性)
console.log(child.b);      // 2(继承自 parent)
delete child.a;
console.log(child.a);      // 1(又回到 parent.a)

考点3:hasOwnProperty 与 in 操作符

题目形式:判断某属性是自身属性还是继承属性?in 和 hasOwnProperty 的区别。

核心

  • obj.hasOwnProperty('prop'):只检查自身属性。

  • 'prop' in obj:检查整个原型链。

示例

javascript 复制代码
function Person(name) { this.name = name; }
Person.prototype.age = 18;
const p = new Person('Tom');
console.log(p.hasOwnProperty('name')); // true
console.log(p.hasOwnProperty('age'));  // false
console.log('age' in p);               // true

考点4:instanceof 原理

题目形式:手写 instanceof;或者问 \[\] instanceof Array、\[\] instanceof Object 的结果及原因。

核心

  • A instanceof B:检查 B.prototype 是否出现在 A 的原型链上。

  • 对于基本类型(非对象)始终返回 false。

手写版本

javascript 复制代码
function myInstanceof(left, right) {
  let proto = Object.getPrototypeOf(left);
  const prototype = right.prototype;
  while (proto) {
    if (proto === prototype) return true;
    proto = Object.getPrototypeOf(proto);
  }
  return false;
}

考点5:创建对象与原型关系的多种方式

题目形式:列举创建对象的方法,并说明其原型指向。

常见方法

  • 字面量:{} → Object.prototype

  • new Object() → 同字面量

  • 构造函数 new Foo() → Foo.prototype

  • Object.create(proto) → 指定原型

  • Object.setPrototypeOf(obj, proto)(不推荐)

  • ES6 class(本质同构造函数)

考点:Object.create(null) 创建的对象没有原型,也没有 hasOwnProperty 等方法。

考点6:原型链继承的实现方式与问题

题目形式:如何实现继承?组合继承与寄生组合继承的区别?Class 继承的本质?

常见错误

  • 直接 Child.prototype = Parent.prototype:父子构造函数原型指向同一个对象,子类添加方法会污染父类。

  • 只调用父构造函数 Parent.call(this) 但没有继承原型上的方法。

  • 没有修复 constructor 指向。

标准寄生组合继承

javascript 复制代码
function inherit(Child, Parent) {
  Child.prototype = Object.create(Parent.prototype);
  Child.prototype.constructor = Child;
}
function Parent(name) { this.name = name; }
Parent.prototype.say = function() {};
function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}
inherit(Child, Parent);

考点7:修改原型的影响

题目形式:在实例创建后修改构造函数的 prototype,已有实例能否访问到新添方法?

核心

  • 实例的 proto 指向的是构造函数当时的 prototype 对象(该对象本身可动态添加属性)。

  • 直接替换构造函数的 prototype 为新对象,旧实例的原型链仍然指向旧对象,不会自动更新。

示例

javascript 复制代码
function Dog() {}
const d1 = new Dog();
Dog.prototype.bark = () => console.log('woof');
d1.bark(); // 可以(原型对象没变,只是添加了方法)

Dog.prototype = { jump: () => console.log('jump') };
const d2 = new Dog();
d2.jump(); // 可以
d1.jump(); // 报错,d1 的原型仍然是旧的 Dog.prototype

考点8:Function 与 Object 的原型关系(终极绕点)

题目形式:Function instanceof Object 和 Object instanceof Function 分别是什么?为什么?

核心

  • Function.prototype 是函数原型对象,所有函数(包括 Object、Function 自身)的 proto 都指向它。

  • Function.prototype.proto === Object.prototype(因为函数原型对象也是普通对象)。

  • Object 本身是函数,所以 Object.proto === Function.prototype。

因此:

  • Function instanceof Object → Function.proto === Function.prototype,Function.prototype.proto === Object.prototype,所以 true。

  • Object instanceof Function → Object.proto === Function.prototype,Function.prototype 在 Object 的原型链上,所以 true。

口诀:万物皆对象,函数皆函数;所有函数 proto 都指向 Function.prototype,Object.prototype 是最后的 null。

考点9:Object.getPrototypeOf 与 proto 的区别与规范

题目形式:不使用 proto,如何获取或设置原型?

答案

  • 获取:Object.getPrototypeOf(obj)(ES5)

  • 设置:Object.setPrototypeOf(obj, proto)(不推荐,性能差)或创建时用 Object.create(proto)

考点10:原型链与性能/内存

题目形式:过多层级的原型链有什么问题?为什么通常不建议动态修改原型?

要点

  • 原型链越长,属性查找越慢。

  • 修改原型会影响所有实例,可能导致意外副作用。

  • 引擎难以优化(隐藏类失效)。

二、AI:LLM 基础概念(仅作为了解,不必深入)

1、LLM 到底是什么?

大语言模型:一个超大规模的神经网络,训练语料包含互联网上的海量文本(网页、书籍、代码、论文等)。

本质工作方式:根据给定的上文,预测下一个最可能出现的 token(类似手机输入法的"联想输入",但强大无数倍)。

训练好之后,它不再学习新东西,完全靠"记忆"中的知识来回答。

2、Token ------ 一切计量的基础

模型不识别字符或单词,只识别 token。一个 token 大约对应:

  • 英文:0.75 个单词(例如 "ChatGPT" 是一个 token)

  • 中文:一个字可能占 1~2 个 token

为什么重要

  • 定价按 token 数(输入 token + 输出 token)。

  • 上下文窗口限制也是 token 数。

  • 前端小工具:很多模型提供商提供 token 计数器,可预估花费。

3、上下文窗口(Context Window)

模型一次能"看到"的最大 token 数量(包括你的提问和它生成的回答)。

例如 8K = 约 6000 个汉字,128K ≈ 一本《三体》第一部。

超出窗口的内容会被截断,模型完全遗忘。长对话时需要自己裁剪历史。

4、调用 LLM API 时的核心参数

json 复制代码
{
  "model": "gpt-3.5-turbo",
  "messages": [{"role": "user", "content": "你好"}],
  "temperature": 0.7,
  "max_tokens": 500,
  "top_p": 0.9,
  "stream": false
}

参数说明

  • messages:对话数组,包含 system(系统指令)、user(用户)、assistant(模型回复)。

  • temperature:范围 0~2。

    • 0:最确定,每次回复都差不多。

    • 1:平衡。

    • 2:想象力爆炸,可能跑题。

  • max_tokens:最多生成多少个 token(不是字数)。

  • top_p:核采样,与 temperature 类似,通常只调其中一个。

  • stream:是否流式输出(逐字显示,体验更好)。

5、角色(Role)体系

  • system:设定模型的"人设"或规则(例如:"你是一个客服助手,只回答产品问题")。

  • user:用户输入。

  • assistant:模型的回复(在 API 调用中,需要把历史回复也传回去,以维持对话记忆)。

6、常见模型家族(前端看到的名字)

模型 提供方 特点
GPT-3.5 / GPT-4 OpenAI 闭源,API 成熟
DeepSeek 深度求索 国内,性价比高,长上下文
LLaMA 2/3 Meta 开源可本地部署
Claude 3 Anthropic 安全、长上下文
Gemini Google 多模态

7、提示工程 ------ 前端也能用的技巧

  • 少样本:在提问前给 1~3 个示例,模型模仿能力很强。

  • 思维链:加上一句"让我们一步一步思考",数学/推理任务效果提升明显。

  • 结构化提示:用 --- 或 XML 标签分隔示例和真实输入。

  • 角色扮演:system 中写"你是一名资深前端工程师,解释概念时喜欢举例"。

8、流式响应(Streaming)

  • 普通 API 请求会等待所有 token 生成完才返回(几秒到几十秒)。

  • 流式响应:模型每生成一个 token 就发送一个 chunk,前端可以逐字显示,体验类似 ChatGPT 打字效果。

实现方式:EventSource 或 fetch + 读取 ReadableStream(各大 SDK 都有支持)。

9、函数调用(Function Calling / Tool Use)

以前端为例:你想让模型查询天气,它自己不知道实时天气,但可以输出一个结构化请求(如 { name: "get_weather", args: { city: "北京"} }),前端收到后调用真实天气 API,再把结果返回给模型。

这让 LLM 能操作外部系统,是Agent的基础。

10、常见坑与建议

  • 模型会幻觉:尤其问"有没有某个 npm 包"时可能编出来。

应对:让模型给出参考来源,或使用 RAG(检索增强生成,后面可了解)。

  • 计费不便宜:大模型 API 请求可能一次花几分钱,注意限制 max_tokens。

  • 安全性:用户可能尝试"提示注入"(让模型忽略你的系统指令)。

对策:对用户输入做清洗,或使用独立会话。

  • 长上下文不代表无限记忆:即使支持 128K,模型对中间部分的注意力会减弱("迷失在中间"现象)。

11、前端集成 LLM 的典型流程

plain 复制代码
用户输入 → 前端拼接 messages(含 system + 历史对话)→ 调用 API → 显示回复(流式)→ 保存 assistant 回复到历史

历史管理要控制在窗口限制内(先进先出截断或使用滑动窗口)。

注意:敏感信息(如 API Key)绝不能放在前端,应由后端代理请求。

作为前端,你并不需要训练模型或了解反向传播,但搞懂这些概念能让你自信地对接任何 LLM API、调试 prompt 以及评估模型能力是否满足业务需求。

相关推荐
weixin_382395238 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__8 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.9 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~10 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华11 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子13 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅13 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni13 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学15 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端