百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

游戏开发之旅-JavaScript原型类构建

myzbx 2025-05-15 20:02 4 浏览

本节是第四讲的第十五小节,上一节我们为大家介绍了JavaScript面向对象编程(OOP)的基础概念,本节为大家介绍原型链的原理以及如何构造原型类。

基于原型的语言(Prototype-Based Language)

JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非对象实例本身。

在传统的 OOP 中,首先定义“类”,此后创建对象实例时,类中定义的所有属性和方法都被复制到实例中。在 JavaScript 中并不如此复制——而是在对象实例和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。

Note: 理解对象的原型(可以通过Object.getPrototypeOf(obj)或者已被弃用的__proto__属性获得)与构造函数的prototype属性之间的区别是很重要的。前者是每个实例上都有的属性,后者是构造函数的属性。也就是说,Object.getPrototypeOf(new Foobar())和Foobar.prototype指向着同一个对象。

使用Javascript中的原型

在javascript中,函数可以有属性。 每个函数都有一个特殊的属性叫作原型(prototype) ,正如下面所展示的。

function doSomething(){}

console.log( doSomething.prototype );

在JavaScript控制台看到的结果如下:

{

constructor: f doSomething(),

__proto__: {

constructor: f Object(),

hasOwnProperty: f hasOwnProperty(),

isPrototypeOf: f isPrototypeOf(),

propertyIsEnumerable: f propertyIsEnumerable(),

toLocaleString: f toLocaleString(),

toString: f toString(),

valueOf: f valueOf()

}}

现在,我们可以添加一些属性到 doSomething 的原型上面,

function doSomething(){}

doSomething.prototype.foo = "bar";

console.log( doSomething.prototype );

得到结果如下所示:

{

foo: "bar",

constructor: f doSomething(),

__proto__: {

constructor: f Object(),

hasOwnProperty: f hasOwnProperty(),

isPrototypeOf: f isPrototypeOf(),

propertyIsEnumerable: f propertyIsEnumerable(),

toLocaleString: f toLocaleString(),

toString: f toString(),

valueOf: f valueOf()

}}

然后,我们可以使用 new 运算符来在现在的这个原型基础之上,创建一个 doSomething 的实例。正确使用 new 运算符的方法就是在正常调用函数时,在函数名的前面加上一个 new 前缀. 通过这种方法,在调用函数前加一个 new ,它就会返回一个这个函数的实例化对象. 然后,就可以在这个对象上面添加一些属性。

function doSomething(){}

doSomething.prototype.foo = "bar"; // add a property onto the prototype

var doSomeInstancing = new doSomething();

doSomeInstancing.prop = "some value"; // add a property onto the object

console.log( doSomeInstancing );

得到结果如下所示:

{

prop: "some value",

__proto__: {

foo: "bar",

constructor: f doSomething(),

__proto__: {

constructor: f Object(),

hasOwnProperty: f hasOwnProperty(),

isPrototypeOf: f isPrototypeOf(),

propertyIsEnumerable: f propertyIsEnumerable(),

toLocaleString: f toLocaleString(),

toString: f toString(),

valueOf: f valueOf()

}

}

}

当你访问 doSomeInstancing 的一个属性, 浏览器首先查找 doSomeInstancing 是否有这个属性. 如果 doSomeInstancing 没有这个属性, 然后浏览器就会在 doSomeInstancing 的 __proto__ 中查找这个属性(也就是 doSomething.prototype). 如果 doSomeInstancing 的 __proto__ 有这个属性, 那么 doSomeInstancing 的 __proto__ 上的这个属性就会被使用. 否则, 如果 doSomeInstancing 的 __proto__ 没有这个属性, 浏览器就会去查找 doSomeInstancing 的 __proto__ 的 __proto__ ,看它是否有这个属性. 默认情况下, 所有函数的原型属性的 __proto__ 就是 window.Object.prototype. 所以 doSomeInstancing 的 __proto__ 的 __proto__ (也就是 doSomething.prototype 的 __proto__ (也就是 Object.prototype)) 会被查找是否有这个属性. 如果没有在它里面找到这个属性, 然后就会在 doSomeInstancing 的 __proto__ 的 __proto__ 的 __proto__ 里面查找. 然而这有一个问题: doSomeInstancing 的 __proto__ 的 __proto__ 的 __proto__ 不存在. 最后, 原型链上面的所有的 __proto__ 都被找完了, 浏览器所有已经声明了的 __proto__ 上都不存在这个属性,然后就得出结论,这个属性是 undefined.

function doSomething(){}

doSomething.prototype.foo = "bar";

var doSomeInstancing = new doSomething();

doSomeInstancing.prop = "some value";

console.log("doSomeInstancing.prop: " + doSomeInstancing.prop);

console.log("doSomeInstancing.foo: " + doSomeInstancing.foo);

console.log("doSomething.prop: " + doSomething.prop);

console.log("doSomething.foo: " + doSomething.foo);

console.log("doSomething.prototype.prop: " + doSomething.prototype.prop);

console.log("doSomething.prototype.foo: " + doSomething.prototype.foo);

得出的结果如下:

doSomeInstancing.prop: some value

doSomeInstancing.foo: bar

doSomething.prop: undefined

doSomething.foo: undefined

doSomething.prototype.prop: undefined

doSomething.prototype.foo: bar

理解原型对象

function Person(first, last, age, gender, interests) {

// 属性与方法定义

};

var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);

person1.valueOf();

浏览器首先检查,person1 对象是否具有可用的 valueOf() 方法。

如果没有,则浏览器检查 person1 对象的原型对象(即 Person构造函数的prototype属性所指向的对象)是否具有可用的 valueof() 方法。

如果也没有,则浏览器检查 Person() 构造函数的prototype属性所指向的对象的原型对象(即 Object构造函数的prototype属性所指向的对象)是否具有可用的 valueOf() 方法。这里有这个方法,于是该方法被调用。

note:必须重申,原型链中的方法和属性没有被复制到其他对象——它们被访问需要通过前面所说的“原型链”的方式。

note:没有官方的方法用于直接访问一个对象的原型对象——原型链中的“连接”被定义在一个内部属性中,在 JavaScript 语言标准中用 [[prototype]] 表示(参见 ECMAScript)。然而,大多数现代浏览器还是提供了一个名为 __proto__ (前后各有2个下划线)的属性,其包含了对象的原型。

prototype 属性:继承成员被定义的地方

Object 构造函数的属性

Object.length 值为 1。

Object.prototype 可以为所有 Object 类型的对象添加属性。

Object 构造函数的方法

Object.assign() 通过复制一个或多个对象来创建一个新的对象。

Object.create() 使用指定的原型对象和属性创建一个新对象。

Object.defineProperty() 给对象添加一个属性并指定该属性的配置。

Object.defineProperties() 给对象添加多个属性并分别指定它们的配置

Object.entries() 返回给定对象自身可枚举属性的 [key, value] 数组。

Object.freeze() 冻结对象:其他代码不能删除或更改任何属性。


Object.getOwnPropertyDescriptor() 返回对象指定的属性配置。


Object.getOwnPropertyNames() 返回一个数组,它包含了指定对象所有的可枚举或不可枚举的属性名。


Object.getOwnPropertySymbols() 返回一个数组,它包含了指定对象自身所有的符号属性。

Object.getPrototypeOf() 返回指定对象的原型对象。

Object.is() 比较两个值是否相同。所有 NaN 值都相等(这与==和===不同)。

Object.isExtensible() 判断对象是否可扩展。

Object.isFrozen() 判断对象是否已经冻结。

Object.isSealed() 判断对象是否已经密封。

Object.keys() 返回一个包含所有给定对象自身可枚举属性名称的数组。

Object.preventExtensions() 防止对象的任何扩展。

Object.seal() 防止其他代码删除对象的属性。

Object.setPrototypeOf() 设置对象的原型(即内部 [[Prototype]] 属性)。

Object.values() 返回给定对象自身可枚举值的数组。

那些继承的属性和方法在哪儿定义呢?Object如上所示——大大超过我们在 person1 对象中看到的继承成员的数量。某些属性或方法被继承了,而另一些没有——为什么呢?

原因在于,继承的属性和方法是定义在 prototype 属性之上的(你可以称之为子命名空间 (sub namespace) )——那些以 Object.prototype. 开头的属性,而非仅仅以 Object. 开头的属性。prototype 属性的值是一个对象,我们希望被原型链下游的对象继承的属性和方法,都被储存在其中。

于是 Object.prototype.watch()、Object.prototype.valueOf() 等等成员,适用于任何继承自 Object() 的对象类型,包括使用构造器创建的新的对象实例。

Object.is()、Object.keys(),以及其他不在 prototype 对象内的成员,不会被“对象实例”或“继承自 Object() 的对象类型”所继承。这些方法/属性仅能被 Object() 构造器自身使用。

note:JavaScript中的所有对象都来自 Object;所有对象从Object.prototype继承方法和属性,尽管它们可能被覆盖。例如,其他构造函数的原型将覆盖 constructor 属性并提供自己的 toString() 方法。Object 原型对象的更改将传播到所有对象,除非受到这些更改的属性和方法将沿原型链进一步覆盖。

constructor 属性

每个实例对象都从原型中继承了一个constructor属性,该属性指向了用于构造此实例对象的构造函数。例如,在控制台中尝试下面的指令:person1.constructor;person2.constructor;都将返回 Person() 构造器,因为该构造器包含这些实例的原始定义。

一个小技巧是,你可以在 constructor 属性的末尾添加一对圆括号(括号中包含所需的参数),从而用这个构造器创建另一个对象实例。毕竟构造器是一个函数,故可以通过圆括号调用;只需在前面添加 new 关键字,便能将此函数作为构造器使用。

var person3 = new person1.constructor('Karen', 'Stephenson', 26, 'female', ['playing drums', 'mountain climbing']);

通常你不会去用这种方法创建新的实例;但如果你刚好因为某些原因没有原始构造器的引用,那么这种方法就很有用了。

此外,constructor 属性还有其他用途。比如,想要获得某个对象实例的构造器的名字,可以这么用:


instanceName.constructor.name 具体地,像这样:person1.constructor.name

修改原型

Person.prototype.fullName = this.name.first + ' ' + this.name.last;

这么做是无效的,因为本例中 this 引用全局范围,而非函数范围。访问这个属性只会得到 undefined undefined。但这个语句若放在 先前定义在 prototype 上的方法中则有效,因为此时语句位于函数范围内,从而能够成功地转换为对象实例范围。你可能会在 prototype 上定义常属性 (constant property) (指那些你永远无需改变的属性),但一般来说,在构造器内定义属性更好。

以下是JavaScript原型类的定义模式:

// 构造器及其属性定义

function Test(a,b,c,d) {

// 属性定义

};

// 定义第一个方法

Test.prototype.x = function () { ... }

// 定义第二个方法

Test.prototype.y = function () { ... }

事实上,一种极其常见的对象定义模式是,在构造器(函数体)中定义属性、在 prototype 属性上定义方法。如此,构造器只包含属性定义,而方法则分装在不同的代码块,代码更具可读性。

以上内容部分摘自视频课程04网页游戏编程JavaScript-15原型类构建,更多示例请参见网站示例。跟着张员外讲编程,学习更轻松,不花钱还能学习真本领。


相关推荐

js实现一款简单踩白块小游戏(曾经很火)

效果图如下所示:htmljscss总结以上所述是小编给大家介绍的js实现一款简单踩白块小游戏,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对小编的支持!...

腾讯游戏健康系统怎么解除 腾讯游戏健康系统解除解绑官网

[海峡网]腾讯游戏健康系统怎么解除?腾讯游戏健康系统解除解绑官网是多少?不少玩家都想解除腾讯游戏健康系统!那么是否有方法呢?下面,就来看看官方是怎么说的吧!腾讯游戏健康系统怎么解除?目前腾讯游戏健康系...

Grok 现在可以生成文档、代码和浏览器游戏

xAI宣布新增一款名为“GrokStudio”的新工具,该工具允许用户创建和编辑文档及代码,甚至可以生成基本的浏览器游戏。该公司在其官方X账号的一篇文章中表示,该功能可供Grok.com上的免...

世界首款程序猿专属游戏公布 地狱级难度让人抓狂!

似乎大家对于程序员的印象都很固定,如果硬要用一个词来形容这份工作,“苦逼”肯定可以入选,关于这些IT从业人员的段子也是一抓一大把,在这里就不再伤害那些网络工程师脆弱的小心灵了,近日一款专门为这些程序...

游戏开发之旅-JavaScript原型类构建

本节是第四讲的第十五小节,上一节我们为大家介绍了JavaScript面向对象编程(OOP)的基础概念,本节为大家介绍原型链的原理以及如何构造原型类。基于原型的语言(Prototype-BasedLa...

打游戏了!解锁编程学习新姿势,极空间部署『CodeCombat』

打游戏了!解锁编程学习新姿势,极空间部署『CodeCombat』哈喽小伙伴们好,我是Stark-C~这几年的AI大火,让大家再次看到了互联网科技的神奇与魅力。考虑到今后各大行业的发展方向,编程已成为许...

DeepSeek如何写在线游戏?附带提示词,直接拿去用!

想做一个属于自己的小游戏网站?不会写代码也没关系,现在你只需要一个工具:DeepSeek大模型!它不仅能帮你写网页,还能一口气搞定贪吃蛇、2048、飞机大战等小游戏!关键是:完全免费,还能中文交流!新...

微信小游戏的开发方式及特点

微信小游戏是基于微信小程序的一个新增类目,它结合了H5游戏和微信社交的优势,为开发者提供了一个快速、便捷的开发游戏平台。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作。开发方式微信小游...

DeepSeek最新玩法,5分钟搭建html实用工具

大家好,我是程序员小灰。说起AI编程,早已不是一个新鲜的概念。早在2023年初,小灰就曾经尝试利用GPT-4帮我生成Python小游戏的代码。到了2024年,随着Cursor等AI编程工具的兴起,AI...

网页游戏的开发流程

网页游戏的开发流程可以根据项目的规模和复杂性而有所不同,但通常包括以下一般步骤,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作。1.需求分析:确定游戏的概念、目标受...

最热的第三人称射击单机游戏分享

在这个快节奏的时代,第三人称射击游戏已经成为了许多玩家的最爱。而单机游戏更是其中的佼佼者,它们拥有着更加丰富的情节和精美的画面。今天我们要分享的是一款最热的第三人称射击单机游戏。这款游戏名为《使命召唤...

十款免费的第三人称射击单机游戏分享

本文将为大家推荐十款免费的第三人称射击单机游戏,这些游戏不仅免费,而且质量上乘,值得玩家们一试。从经典之作到新近大作,从PC端到移动平台,涵盖了各种类型和风格的游戏。无论你是喜欢动作射击还是喜欢策略战...

推荐一些有趣的在线编程游戏,让你学习编程事半功倍

▌Robocode让坦克们互相博弈的游戏,你可以看到它们飞奔,碾碎一切挡道的东西。机器人配有雷达与火炮,选手在躲避对手进攻的同时攻击对手,以此来较量得分的多少。这个游戏很有意思,曾经令我沉迷…你可以用...

用DEEPSEEK 写的小游戏,直接运行太牛了!~

由于DEEPSEEK服务器繁忙,通过OLLAMA来部署各种大模型都可以,电脑配置不够高,能用DEEPSEEKR114B,也只能在CMD下运行,AI可视化软件Chatbox跑不动,具体写内容如下&g...

2021年游戏项目的十大编程语言:C++、Java、Net均上榜

前两天在某平台刷到了大家讨论腾讯游戏相关开发是不是真的薪资很高,然后就看到了Analticslnsight发布关于2021年游戏项目的十大编程语言。C++C++的效率做游戏是一个很重要的原因,近年来...