Skip to content

原型和属性的区别

前置:来自 <https://zhuanlan.zhihu.com/p/35458229>

JavaScript在设计之初,作为一种网页脚本语言,没有设计得很复杂,这种语言只要能够完成一些简单操作就够了。Javascript里面所有的数据类型都是对象(object)。在ES6之前,js中是没有Class的概念的(ES6中的类也是语法糖,本质还是基于原型),为了实现实例对象的属性和方法共享,就给function设计了一个prototype的概念。每个对象都有一个原型属性,原型属性指向另一个对象,而原型属性中的对象是会被其他对象所继承的,也就是共用的,对象自身的有些属性和方法则不可以。原型对象(prototype)也有一个自己的原型对象(_proto_),普通属性并非为对象

关于jQuery框架

在前端入门时必须掌握的一个框架就是jQuery,其实你每次调用$(“…”)时,都会返回一个实例化的新的jQuery对象出来(内部帮你执行了new方法,关于jQuery初始化这一段也是jQuery的精髓之一,实现的相当巧妙,有兴趣可以去看看),这样做既没有使实例对象私有属性相互影响(如上面的propA),又能共用方法(如上面的methodB)。

原型链

下图通过原型链从而找到toString方法,而其他对象同样具有此方法,所以并不是对象会把这些方法和属性复制过来,只是通过原型(prototype)找到被继承的对象的原型。

image-20221026223200603

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

对象这些公用属性和方法定义在 Object 的构造器函数 (constructor functions) 之上的prototype属性上(意味着可以被继承),而非对象实例本身。

_proto_和prototype的区别

Object.prototype的proto属性是一个访问器属性(一个getter函数和一个setter函数),它公开访问它的对象的内部[[Prototype]](对象或null)。尽量别用proto属性,存在争议。

prototype 指向一块内存,这个内存里面有共用属性;

proto 指向同一块内存;

prototype 和 proto 的不同点在于prototype 一般是构造函数的属性,而 proto 是对象的属性。

难点在于……构造函数也是对象!

如果没有 prototype,那么共用属性就没有立足之地,

如果没有 proto,那么一个对象就不知道自己的共用属性有哪些。上面所说proto属性是一个访问器属性,通俗来说,prototype就是用它来查找有哪些对象是可继承,且继承了的。

  1. prototype存储共用的属性和方法,
  2. proto用来将对象与该对象的原型相连,
  3. constuctor是用来将原型对象指向关联的构造函数。

问题/object有详细的介绍。

_proto_的搜索

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

javascript
function doSomething(){}
doSomething.prototype.foo = "bar"; // 在原型上添加一个property(属性)
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value"; // 在对象上添加property(属性)
console.log( doSomeInstancing );

console.log("doSomeInstancing.prop:      " + doSomeInstancing.prop);//some value
console.log("doSomeInstancing.foo:       " + doSomeInstancing.foo);//bar
console.log("doSomething.prop:           " + doSomething.prop);//underfined
console.log("doSomething.foo:            " + doSomething.foo);//underfined
console.log("doSomething.prototype.prop: " + doSomething.prototype.prop);//underfined
console.log("doSomething.prototype.foo: " + doSomething.prototype.foo);//bar
javascript
{ //在底层代码里面是有点套娃操作
    prop: "some value",
    __proto__: {
        foo: "bar",
        constructor: function doSomething(),
        __proto__: {
            constructor: function Object(),
            hasOwnProperty: function hasOwnProperty(),
            isPrototypeOf: function isPrototypeOf(),
            propertyIsEnumerable: function propertyIsEnumerable(),
            toLocaleString: function toLocaleString(),
            toString: function toString(),
            valueOf: function valueOf()
        }
    }
}

doSomeInstancing 的 __proto__ 属性就是doSomething.prototype

个人理解,prototype有点类似于指针。但在object上一层的prototype则有点虚拟头的意思。(些许偏差,因为指针指向是一个节点,而prototype不断向上查找其他对象的prototype,直至找到所需要的方法或属性,没有则null,每个对象都具有prototype,而prototype指向另一个对象)

image-20221026204730984

原型的实践

create()

我们可以使用Object.create()的方法,创建原型对象,例如:

javascript
var person1=Object.create(person);

以person为原型对象创建了一个person1对象,在控制台输入以下代码返回person:

javascript
person1._proto_

constructor属性

每个实例对象都从原型中继承了一个constructor属性,该属性指向了用于该构造此实例对象的构造函数,,

也就是可以使用它找到构造函数

javascript
person1.constructor; //如果构造此对象的是person函数,返回person(),没有的话就返回object()

使用这个属性返回构造器,构造器是一个函数,故可以通过圆括号调用;只需在前面添加 new 关键字,便能将此函数作为构造器使用。当没有原始构造器的引用时,就可以使用此种方式。

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

修改原型

javascript
//Person是构造person1的函数
Person.prototype.farewell = function() {
  alert(this.name.first + ' has left the building. Bye for now!');


}

在控制台输入person1.farewell()会看到一条警告信息,其中还显示了构造器中定义的人名;这很有用。但更关键的是,整条继承链动态地更新了,任何由此构造器创建的对象实例都自动获得了这个方法.

证明了先前的原型链模型这种继承模型下,上游对象的方法不会复制到下游的对象实例中;下游对象本身虽然没有定义这些方法,但浏览器会通过上溯原型链、从上游对象中找到它们。这种继承模型提供了一个强大而可扩展的功能系统。

关于属性定义在prototype,由于定义起来并不灵活,this的指向需要特别注意,例如:

image-20221027133055961

一种常见的定义模型

在构造器(函数体)中定义属性、在 prototype 属性上定义方法。如此,构造器只包含属性定义,而方法则分装在不同的代码块,代码更具可读性

javascript
// 构造器及其属性定义
function Test(a,b,c,d) {
  // 属性定义
};
// 定义第一个方法
Test.prototype.x = function () { ... }

// 定义第二个方法
Test.prototype.y = function () { ... }

典型的例子:school plan app

Released under the MIT License.