当前位置: 澳门新濠3559 > 操作系统 > 正文

原型对象,希望能得到大神的指点

时间:2019-12-21 19:57来源:操作系统
原型 原型链.png function Person(name) { this.name = name; } var p1 = new Person('Leo'); var obj = new Object(); var fn = new Function();// 对象的原型最终指向Object.prototype,Object.prototype的原型指向null console.log( o

澳门新濠3559 1

原型

澳门新濠3559 2

原型链.png

    function Person(name) {
        this.name = name;
    }
    var p1 = new Person('Leo');
    var obj = new Object();
    var fn = new Function();
// 对象的原型最终指向Object.prototype,Object.prototype的原型指向null
    console.log( obj.__proto__ === Object.prototype ); // true  普通对象的原型
    console.log( Person.prototype.__proto__ === Object.prototype ); // true 自定义构造函数原型的原型
    console.log( Function.prototype.__proto__ === Object.prototype ); // true Function构造函数原型的原型
    console.log( Object.prototype.__proto__ === null ); // true Object构造函数原型的原型
// 实例的原型指向创建该实例的构造函数的原型
    console.log( p1.__proto__ === Person.prototype ); // true 实例对象的原型
// 函数的原型指向Function.prototype
    console.log( fn.__proto__ === Function.prototype ); // true  普通函数的原型
    console.log( Person.__proto__ === Function.prototype ); // true 自定义构造函数的原型
    console.log( Function.__proto__ === Function.prototype ); // true Function构造函数的原型
    console.log( Object.__proto__ === Function.prototype ); // true Object构造函数的原型
// 实例的constructor属性,继承自其原型
    console.log( p1.hasOwnProperty('constructor') ); // false
    console.log( obj.hasOwnProperty('constructor') ); // false
    console.log( fn.hasOwnProperty('constructor') ); // false
// Function.prototype是一个函数
    console.log( typeof Function.prototype ); // function
  • prototype (原型)属性
    每个函数都有一个prototype属性,这个属性指向一个对象,这个对象就是原型对象。
    原型对象的作用就是定义所有实例对象共享的属性和方法。
    实例对象可以看作从原型对象衍生出来的子对象,原型对象上的所有属性和方法,都能被实例对象共享。
    实例对象本身没有某个属性或方法时,会到原型对象去寻找该属性或方法。
    原型对象上的变动会立刻体现在所有实例对象上。
    如果重写整个原型,会切断现有原型与任何之前已存在的对象实例之间的联系,它们引用的依旧是之前的原型。
    function Person(name) {
        this.name = name;
    };
    Person.prototype.sayHi = function () {
        console.log('Hi,'+ this.name);
    }
    var p1 = new Person('Leo');
// 先创建的实例对象,后修改原型,Person.prototype指向被新对象覆盖
    Person.prototype = { // 字面量相当于Object的实例
        constructor: Person, //校正constructor属性的指向,否则指向Object
        getName: function () {
            console.log(this.name);
        }
    }
    var p2 = new Person('Tom');
// 重写原型前创建的实例的原型依旧指向以前的原型
    p1.sayHi(); // Hi,Leo
    p1.getName(); // 报错
// 重写原型后创建的实例的原型依旧指向新的原型
    p2.sayHi(); // 报错
    p2.getName(); // Tom
  • constructor (构造函数)属性
    每个原型对象都会自动获得一个constructor属性,这个属性默认指向prototype属性所在函数(即构造函数)。
    创建了自定义构造函数后,其原型对象默认只会取得constructor属性,其他的方法则从Object继承而来。
    constructor属性定义在原型对象上面,可以被所有实例对象继承。
    constructor属性不是实例对象自身属性,而是继承自原型对象。
    constructor属性的作用,是分辨原型对象到底属于哪个构造函数.
    function Preson() {}
    var p1 = new Preson();
    console.log(p1.constructor); // function P() {}
    console.log(p1.hasOwnProperty('constructor'));// false
    console.log(p1.constructor === Preson); // true
    console.log(p1.constructor === Function); // false

constructor属性是一种原型对象与构造函数的关联关系,修改原型对象时,一般同时校正constructor的指向。

  • [[Prototype]] 内部属性
    当用构造函数创建一个实例对象后,该实例对象包含一个[[Prototype]]内部属性,指向构造函数的原型对象。
    [[Prototype]]存在于实例对象和原型对象之间,而不是实例和构造函数之间。
    [[Prototype]]内部属性通过proto属性来访问。

  • 原型链
    原型对象本身也是对象,也有自己原型对象,所以就形成了一条原型链(prototype chain)。
    所有对象的对象原型终点可以可以上溯到Object.prototype。
    Object.prototype对象的原型对象是没有任何属性和方法的null对象,null对象没有原型对象。
    读取对象的某个属性属性时,先寻找对象本身的属性,如果找不到就在原型链上找,直到Object.prototype。
    如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性.

  • instanceof 运算符
    instanceof返回一个布尔值,表示左边的实例对象是否为右边的构造函数的实例。
    instanceof的实质是检查构造函数的原型对象是否在实例对象的原型链上。
    instanceof对整个原型链上的对象都有效,因此同一个对象,可能会对多个构造函数都返回true。
    JavaScript之中,只要是对象,就有对应的构造函数。因此,instanceof运算符可以判断值的类型。
    instanceof只适用于对象,不适用基本类型的值。

    var d = new Date();
    console.log(d instanceof Date); // true
    console.log(d instanceof Object); // true
// 等同于
    console.log(Date.prototype.isPrototypeOf(d)); // true
  • in 运算符和 for…in 循环
    in运算符返回一个布尔值,表示一个对象是否具有某个属性。它不区分该属性是对象自身的属性,还是继承的属性。
    'length' in Date // true
    'toString' in Date // true

获得对象的所有可枚举属性(不管是自身的还是继承的),可以使用for...in循环。

    for ( var name in object ) {
      if ( object.hasOwnProperty(name) ) {
        /* loop code */
      }
    }
  • Object.prototype.hasOwnProperty()
    对象实例的hasOwnProperty方法返回一个布尔值,用于判断某个属性定义在对象自身,还是定义在原型链上。
    hasOwnProperty方法是JavaScript之中唯一一个处理对象属性时,不会遍历原型链的方法。
    Date.hasOwnProperty('length'); // true
    Date.hasOwnProperty('toString'); // false
  • Object.getOwnPropertyNames()
    Object.getOwnPropertyNames方法返回一个数组,成员是对象本身的所有属性的键名,不包含继承的属性键名
    Object.getOwnPropertyNames(String);
// ["length", "name", "arguments", "caller", "prototype", "fromCharCode", "fromCodePoint", "raw"]
  • Object.prototype.isPrototypeOf()
    对象实例isPrototypeOf方法,用来判断一个对象是否是另一个对象的原型。
    只有某个对象处在原型链上,isPrototypeOf都返回true。
    Object.prototype处在原型链顶端,所以对各种实例都返回true,只有继承null的对象除外。
    Object.prototype.isPrototypeOf({});// true
    Object.prototype.isPrototypeOf([]);// true
    Object.prototype.isPrototypeOf(/xyz/);// true
    Person.prototype.isPrototypeOf(p1);// true
    Object.prototype.isPrototypeOf(Object.create(null));// false
  • Object.prototype.proto
    proto方法可以获取和改写某个对象的原型对象。
    proto方法应少用,而是使用标准方法Object.getPrototypeOf()和Object.setPrototypeOf()。

  • Object.getPrototypeOf()
    Object.getPrototypeOf方法接受一个对象,返回该对象的原型,是获取原型对象的标准方法。

// 对象的原型是Object.prototype
    console.log(Object.getPrototypeOf({}) === Object.prototype); // true
// 函数的原型是Function.prototype
    function Person() {};
    console.log(Object.getPrototypeOf(Person) === Function.prototype); // true
// 实例对象p1的原型是创建该实例的构造函数的原型
    var p1 = new Person();
    console.log(Object.getPrototypeOf(p1) === Person.prototype); // true

获取原型对象方法的比较

    // 获取实例对象obj原型的三种方法:
    var obj = new Object();
    obj.__proto__; // 浏览器内部属性,其他环境不可以部署
    obj.constructor.prototype; // 手动修改原型对象时会失效,需要同时修改constructor属性
    Object.getPrototypeOf(obj)// 推荐
  • Object.setPrototypeOf()
    Object.setPrototypeOf方法可以为现有对象设置原型,返回一个新对象。
    该方法接受两个参数,第一个参数是现有对象,第二个参数是原型对象。
    var a = { name: 'Leo'};
    var b = Object.setPrototypeOf({}, a); // b对象本身为空,原型为a
    console.log(a.isPrototypeOf(b)); // true a是b的原型
    console.log(b.hasOwnProperty(name)); // false, b.name不是b自身的属性
    console.log(b.name); // Leo
    // 等价于
    var b = {__proto__: a};
    console.log(b.name); // Leo

new命令通过构造函数新建实例对象,实质是将实例对象的原型,指向构造函数的prototype属性,然后在实例对象上执行构造函数。

    function Person(name, age) {
        this.name = name;
        this.age = age;
    };
    var p1 = new Person('Leo'); // 使用new新建构造函数
// 等价于
    var p1 = Object.setPrototypeOf({}, Person.prototype);// 将实例对象的原型指向构造函数的prototype属性
    Person.call(p1, 'Leo', 26);// 在实例对象上执行构造函数,并传入参数
    console.log(p1.name); // Leo
    console.log(p1.age); // 26
  • Object.create()
    Object.create方法用于从原型对象生成新的实例对象,可以替代new命令。
    该方法接受一个对象为参数,返回一个新的对象,传入的对象成为新对象的原型。必须提供原有对象,否则报错。
    var A = {
        sayHi: function () {
            console.log('hello');
        }
    };
    var B = Object.create(A); // 在A的基础上生成B,此时A就成为B的原型
    B.sayHi(); // hello B继承了A的所有属性和方法
    // 等同于
    var A = function () {};
    A.prototype.sayHi = function () {
        console.log('hello');
    }
    B = new A();
    B.sayHi() // hello

Object.create方法实质是新建一个构造函数F,让F的prototype属性指向作为原型的对象o,最后返回一个F的实例。

if (typeof Object.create !== 'function') {
    Object.create = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

下面三种生成新对象的方式是等价的。

var o1 = Object.create({});
var o2 = Object.create(Object.prototype);
var o3 = new Object();

Object.create方法生成的新对象,动态的继承了原型。在原型上的修改,会立刻反映在新对象上。

var A = {x: 1};
var B = Object.create(A);
A.x = 2;
console.log(B.x); // 2

Object.create方法还可以接受第二个参数,该参数是一个属性描述对象,它所描述的对象属性,会添加到新对象。
Object.create方法生成的对象,继承了他的原型对象的构造函数。

function A() {}
var a = new A();
var b = Object.create(a); // b对象的原型是a,因此继承了a的构造函数A
console.log(b.constructor === A); // true
console.log(b instanceof A); // true

参考资料:
《JavaScript高级程序设计》
《JavaScript 标准参考教程》

1 构造函数的介绍:

所谓构造函数,就是提供一个生成对象的模板并描述对象基本结构的函数。一个构造函数,可以生成多个对象。每个对象都有相同的结构。构造函数就是对象的模板,对象就是构造函数的实例
构造函数的特点有:

a:构造函数的函数名首字母必须大写。

b:内部使用this对象,来指向将要生成的对象实例。

c:使用new操作符来调用构造函数,并返回对象实例。
例子

function Person(){
2         this.name = 'keith';
3     }
4 
5     var boy = new Person();
6     console.log(boy.name);    //'keith'

遇到一个异类的问题,json很变态。网站返回的Json数据,转为Java类。

大部分面向对象的编程语言,都是以“类”(class)作为对象体系的语法基础。JavaScript语言不是如此,它的面向对象编程基于“原型对象”。

2 prototype属性的作用

js中每个数据类型都是对象(除了null和undefined),而每个对象都继承自另外一个对象,后者称为“原型”(prototype)对象,只有null除外,它没有自己的原型对象。

原型对象上的所有属性和方法,都会被对象实例所共享。

通过构造函数生成对象实例时,会将对象实例的原型指向构造函数的prototype属性。每一个构造函数都有一个prototype属性,这个属性就是对象实例的原型对象。

function Person(name,height){
 2         this.name=name;
 3         this.height=height;
 4     }
 5 
 6     Person.prototype.hobby=function(){
 7         return 'watching movies';
 8     }
 9 
10     var boy=new Person('keith',180);
11     var girl=new Person('rascal',153);
12 
13     console.log(boy.name);    //'keith'
14     console.log(girl.name);    //'rascal'
15     console.log(boy.hobby===girl.hobby);  //true

对于构造函数来说,prototype是作为构造函数的属性;对于对象实例来说,prototype是对象实例的原型对象。所以prototype即是属性,又是对象。

原型对象的属性不是对象实例的属性。对象实例的属性是继承自构造函数定义的属性,因为构造函数内部有一个this关键字来指向将要生成的对象实例。对象实例的属性,其实就是构造函数内部定义的属性。只要修改原型对象上的属性和方法,变动就会立刻体现在所有对象实例上。

Person.prototype.hobby=function(){
2         return 'swimming';
3     }
4     console.log(boy.hobby===girl.hobby);  //true
5     console.log(boy.hobby());    //'swimming'
6     console.log(girl.hobby());    //'swimming'

总结
a:原型对象的作用,就是定义所有对象实例所共享的属性和方法。

** b:prototype,对于构造函数来说,它是一个属性;对于对象实例来说,它是一个原型对象。**

{result_list:{map_data:{0:{对象},1:{对象},2:{对象},3:{对象},4:{对象}}},total_results:"814860",request_id:"5z4usph4zf84"}

概述

3原型链

对象的属性和方法,有可能是定义在自身,也有可能是定义在它的原型对象。由于原型对象本身对于对象实例来说也是对象,它也有自己的原型,所以形成了一条原型链(prototype chain)。比如,a对象是b对象的原型,b对象是c对象的原型,以此类推。所有一切的对象的原型顶端,都是Object.prototype,即Object构造函数的prototype属性指向的那个对象。

当然,Object.prototype对象也有自己的原型对象,那就是没有任何属性和方法的null对象,而null对象没有自己的原型

console.log(Object.getPrototypeOf(Object.prototype));    //null
 console.log(Person.prototype.isPrototypeOf(boy))    //true

原型链(prototype chain)的特点有:

a:读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。

b:如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overiding)。

 c:一级级向上在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。

其中map_data中的元素个数不确定,有时为1,有时为19。0:{对象},1:{对象}....100:{对象}在写对应类时,懵圈了。在网上也没有查询到相关的知识。希望能得到大神的指点。

构造函数的缺点

JavaScript通过构造函数生成新对象,因此构造函数可以视为对象的模板。实例对象的属性和方法,可以定义在构造函数内部。

function Cat (name, color) {
  this.name = name;
  this.color = color;
}

var cat1 = new Cat('大毛', '白色');

cat1.name // '大毛'
cat1.color // '白色'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 澳门新濠3559,9

上面代码的Cat函数是一个构造函数,函数内部定义了name属性和color属性,所有实例对象都会生成这两个属性。但是,这样做是对系统资源的浪费,因为同一个构造函数的对象实例之间,无法共享属性。

function Cat(name, color) {
  this.name = name;
  this.color = color;
  this.meow = function () {
    console.log('mew, mew, mew...');
  };
}

var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');

cat1.meow === cat2.meow
// false
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

上面代码中,cat1cat2是同一个构造函数的实例。但是,它们的meow方法是不一样的,就是说每新建一个实例,就会新建一个meow方法。这既没有必要,又浪费系统资源,因为所有meow方法都是同样的行为,完全应该共享。

prototype属性的作用

JavaScript的每个对象都继承另一个对象,后者称为“原型”(prototype)对象。只有null除外,它没有自己的原型对象。

原型对象上的所有属性和方法,都能被派生对象共享。这就是JavaScript继承机制的基本设计。

通过构造函数生成实例对象时,会自动为实例对象分配原型对象。每一个构造函数都有一个prototype属性,这个属性就是实例对象的原型对象。

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

Animal.prototype.color = 'white';

var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');

cat1.color // 'white'
cat2.color // 'white'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

上面代码中,构造函数Animalprototype对象,就是实例对象cat1cat2的原型对象。在原型对象上添加一个color属性。结果,实例对象都能读取该属性。

原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。

Animal.prototype.color = 'yellow';

cat1.color // "yellow"
cat2.color // "yellow"
  • 1
  • 2
  • 3
  • 4

上面代码中,原型对象的color属性的值变为yellow,两个实例对象的color属性立刻跟着变了。这是因为实例对象其实没有color属性,都是读取原型对象的color属性。也就是说,当实例对象本身没有某个属性或方法的时候,它会到构造函数的prototype属性指向的对象,去寻找该属性或方法。这就是原型对象的特殊之处。

如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。

cat1.color = 'black';

cat2.color // 'yellow'
Animal.prototype.color // "yellow";
  • 1
  • 2
  • 3
  • 4

上面代码中,实例对象cat1color属性改为black,就使得它不再去原型对象读取color属性,后者的值依然为yellow

总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的含义,而实例对象可以视作从原型对象衍生出来的子对象。

Animal.prototype.walk = function () {
  console.log(this.name + ' is walking');
};
  • 1
  • 2
  • 3

上面代码中,Animal.prototype对象上面定义了一个walk方法,这个方法将可以在所有Animal实例对象上面调用。

由于JavaScript的所有对象都有构造函数,而所有构造函数都有prototype属性(其实是所有函数都有prototype属性),所以所有对象都有自己的原型对象。

原型链

对象的属性和方法,有可能是定义在自身,也有可能是定义在它的原型对象。由于原型本身也是对象,又有自己的原型,所以形成了一条原型链(prototype chain)。比如,a对象是b对象的原型,b对象是c对象的原型,以此类推。

如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性指向的那个对象。那么,Object.prototype对象有没有它的原型呢?回答可以是有的,就是没有任何属性和方法的null对象,而null对象没有自己的原型。

Object.getPrototypeOf(Object.prototype)
// null
  • 1
  • 2

上面代码表示,Object.prototype对象的原型是null,由于null没有任何属性,所以原型链到此为止。

“原型链”的作用是,读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined

如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overiding)。

需要注意的是,一级级向上,在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。

举例来说,如果让某个函数的prototype属性指向一个数组,就意味着该函数可以当作数组的构造函数,因为它生成的实例对象都可以通过prototype属性调用数组方法。

var MyArray = function () {};

MyArray.prototype = new Array();
MyArray.prototype.constructor = MyArray;

var mine = new MyArray();
mine.push(1, 2, 3);

mine.length // 3
mine instanceof Array // true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

上面代码中,mine是构造函数MyArray的实例对象,由于MyArrayprototype属性指向一个数组实例,使得mine可以调用数组方法(这些方法定义在数组实例的prototype对象上面)。至于最后那行instanceof表达式,我们知道instanceof运算符用来比较一个对象是否为某个构造函数的实例,最后一行就表示mineArray的实例。

下面的代码可以找出,某个属性到底是原型链上哪个对象自身的属性。

function getDefiningObject(obj, propKey) {
  while (obj && !{}.hasOwnProperty.call(obj, propKey)) {
    obj = Object.getPrototypeOf(obj);
  }
  return obj;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

constructor属性

prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。

function P() {}

P.prototype.constructor === P
// true
  • 1
  • 2
  • 3
  • 4

由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承。

function P() {}
var p = new P();

p.constructor
// function P() {}

p.constructor === P.prototype.constructor
// true

p.hasOwnProperty('constructor')
// false
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

上面代码中,p是构造函数P的实例对象,但是p自身没有contructor属性,该属性其实是读取原型链上面的P.prototype.constructor属性。

constructor属性的作用,是分辨原型对象到底属于哪个构造函数。

function F() {};
var f = new F();

f.constructor === F // true
f.constructor === RegExp // false
  • 1
  • 2
  • 3
  • 4
  • 5

上面代码表示,使用constructor属性,确定实例对象f的构造函数是F,而不是RegExp

有了constructor属性,就可以从实例新建另一个实例。

function Constr() {}
var x = new Constr();

var y = new x.constructor();
y instanceof Constr // true
  • 1
  • 2
  • 3
  • 4
  • 5

上面代码中,x是构造函数Constr的实例,可以从x.constructor间接调用构造函数。

这使得在实例方法中,调用自身的构造函数成为可能。

Constr.prototype.createCopy = function () {
  return new this.constructor();
};
  • 1
  • 2
  • 3

这也提供了继承模式的一种实现。

function Super() {}

function Sub() {
  Sub.superclass.constructor.call(this);
}

Sub.superclass = new Super();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

上面代码中,SuperSub都是构造函数,在Sub内部的this上调用Super,就会形成Sub继承Super的效果。

由于constructor属性是一种原型对象与构造函数的关联关系,所以修改原型对象的时候,务必要小心。

function A() {}
var a = new A();
a instanceof A // true

function B() {}
A.prototype = B.prototype;
a instanceof A // false
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

上面代码中,aA的实例。修改了A.prototype以后,constructor属性的指向就变了,导致instanceof运算符失真。

所以,修改原型对象时,一般要同时校正constructor属性的指向。

// 避免这种写法
C.prototype = {
  method1: function (...) { ... },
  // ...
};

// 较好的写法
C.prototype = {
  constructor: C,
  method1: function (...) { ... },
  // ...
};

// 好的写法
C.prototype.method1 = function (...) { ... };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

上面代码中,避免完全覆盖掉原来的prototype属性,要么将constructor属性重新指向原来的构造函数,要么只在原型对象上添加方法,这样可以保证instanceof运算符不会失真。

此外,通过name属性,可以从实例得到构造函数的名称。

function Foo() {}
var f = new Foo();
f.constructor.name // "Foo"
  • 1
  • 2
  • 3

instanceof运算符

instanceof运算符返回一个布尔值,表示指定对象是否为某个构造函数的实例。

var v = new Vehicle();
v instanceof Vehicle // true
  • 1
  • 2

上面代码中,对象v是构造函数Vehicle的实例,所以返回true

instanceof运算符的左边是实例对象,右边是构造函数。它的运算实质是检查右边构建函数的原型对象,是否在左边对象的原型链上。因此,下面两种写法是等价的。

v instanceof Vehicle
// 等同于
Vehicle.prototype.isPrototypeOf(v)
  • 1
  • 2
  • 3

由于instanceof对整个原型链上的对象都有效,因此同一个实例对象,可能会对多个构造函数都返回true

var d = new Date();
d instanceof Date // true
d instanceof Object // true
  • 1
  • 2
  • 3

上面代码中,d同时是DateObject的实例,因此对这两个构造函数都返回true

instanceof的原理是检查原型链,对于那些不存在原型链的对象,就无法判断。

Object.create(null) instanceof Object // false
  • 1

上面代码中,Object.create(null)返回的新对象的原型是null,即不存在原型,因此instanceof就认为该对象不是Object的实例。

除了上面这种继承null的特殊情况,JavaScript之中,只要是对象,就有对应的构造函数。因此,instanceof运算符的一个用处,是判断值的类型。

var x = [1, 2, 3];
var y = {};
x instanceof Array // true
y instanceof Object // true
  • 1
  • 2
  • 3
  • 4

上面代码中,instanceof运算符判断,变量x是数组,变量y是对象。

注意,instanceof运算符只能用于对象,不适用原始类型的值。

var s = 'hello';
s instanceof String // false
  • 1
  • 2

上面代码中,字符串不是String对象的实例(因为字符串不是对象),所以返回false

此外,undefinednull不是对象,所以instanceOf运算符总是返回false

undefined instanceof Object // false
null instanceof Object // false
  • 1
  • 2

利用instanceof运算符,还可以巧妙地解决,调用构造函数时,忘了加new命令的问题。

function Fubar (foo, bar) {
  if (this instanceof Fubar) {
    this._foo = foo;
    this._bar = bar;
  }
  else {
    return new Fubar(foo, bar);
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上面代码使用instanceof运算符,在函数体内部判断this关键字是否为构造函数Fubar的实例。如果不是,就表明忘了加new命令。

Object.getPrototypeOf()

Object.getPrototypeOf方法返回一个对象的原型。这是获取原型对象的标准方法。

// 空对象的原型是Object.prototype
Object.getPrototypeOf({}) === Object.prototype
// true

// 函数的原型是Function.prototype
function f() {}
Object.getPrototypeOf(f) === Function.prototype
// true

// f 为 F 的实例对象,则 f 的原型是 F.prototype
var f = new F();
Object.getPrototypeOf(f) === F.prototype
// true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Object.setPrototypeOf()

Object.setPrototypeOf方法可以为现有对象设置原型,返回一个新对象。

Object.setPrototypeOf方法接受两个参数,第一个是现有对象,第二个是原型对象。

var a = {x: 1};
var b = Object.setPrototypeOf({}, a);
// 等同于
// var b = {__proto__: a};

b.x // 1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

上面代码中,b对象是Object.setPrototypeOf方法返回的一个新对象。该对象本身为空、原型为a对象,所以b对象可以拿到a对象的所有属性和方法。b对象本身并没有x属性,但是JavaScript引擎找到它的原型对象a,然后读取ax属性。

new命令通过构造函数新建实例对象,实质就是将实例对象的原型,指向构造函数的prototype属性,然后在实例对象上执行构造函数。

var F = function () {
  this.foo = 'bar';
};

var f = new F();

// 等同于
var f = Object.setPrototypeOf({}, F.prototype);
F.call(f);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Object.create()

Object.create方法用于从原型对象生成新的实例对象,可以替代new命令。

它接受一个对象作为参数,返回一个新对象,后者完全继承前者的属性,即原有对象成为新对象的原型。

var A = {
 print: function () {
   console.log('hello');
 }
};

var B = Object.create(A);

B.print() // hello
B.print === A.print // true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

上面代码中,Object.create方法在A的基础上生成了B。此时,A就成了B的原型,B就继承了A的所有属性和方法。这段代码等同于下面的代码。

var A = function () {};
A.prototype = {
 print: function () {
   console.log('hello');
 }
};

var B = new A();

B.print === A.prototype.print // true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

实际上,Object.create方法可以用下面的代码代替。如果老式浏览器不支持Object.create方法,可以就用这段代码自己部署。

if (typeof Object.create !== 'function') {
  Object.create = function (o) {
    function F() {}
    F.prototype = o;
    return new F();
  };
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

上面代码表示,Object.create方法实质是新建一个构造函数F,然后让Fprototype属性指向作为原型的对象o,最后返回一个F的实例,从而实现让实例继承o的属性。

下面三种方式生成的新对象是等价的。

var o1 = Object.create({});
var o2 = Object.create(Object.prototype);
var o3 = new Object();
  • 1
  • 2
  • 3

如果想要生成一个不继承任何属性(比如没有toStringvalueOf方法)的对象,可以将Object.create的参数设为null

var o = Object.create(null);

o.valueOf()
// TypeError: Object [object Object] has no method 'valueOf'
  • 1
  • 2
  • 3
  • 4

上面代码表示,如果对象o的原型是null,它就不具备一些定义在Object.prototype对象上面的属性,比如valueOf方法。

使用Object.create方法的时候,必须提供对象原型,否则会报错。

Object.create()
// TypeError: Object prototype may only be an Object or null
  • 1
  • 2

object.create方法生成的新对象,动态继承了原型。在原型上添加或修改任何方法,会立刻反映在新对象之上。

var o1 = { p: 1 };
var o2 = Object.create(o1);

o1.p = 2;
o2.p
// 2
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

上面代码表示,修改对象原型会影响到新生成的对象。

除了对象的原型,Object.create方法还可以接受第二个参数。该参数是一个属性描述对象,它所描述的对象属性,会添加到新对象。

var o = Object.create({}, {
  p1: { value: 123, enumerable: true },
  p2: { value: 'abc', enumerable: true }
});

// 等同于
var o = Object.create({});
o.p1 = 123;
o.p2 = 'abc';
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Object.create方法生成的对象,继承了它的原型对象的构造函数。

function A() {}
var a = new A();
var b = Object.create(a);

b.constructor === A // true
b instanceof A // true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

上面代码中,b对象的原型是a对象,因此继承了a对象的构造函数A

Object.prototype.isPrototypeOf()

对象实例的isPrototypeOf方法,用来判断一个对象是否是另一个对象的原型。

var o1 = {};
var o2 = Object.create(o1);
var o3 = Object.create(o2);

o2.isPrototypeOf(o3) // true
o1.isPrototypeOf(o3) // true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

上面代码表明,只要某个对象处在原型链上,isPrototypeOf都返回true

Object.prototype.isPrototypeOf({}) // true
Object.prototype.isPrototypeOf([]) // true
Object.prototype.isPrototypeOf(/xyz/) // true
Object.prototype.isPrototypeOf(Object.create(null)) // false
  • 1
  • 2
  • 3
  • 4

上面代码中,由于Object.prototype处于原型链的最顶端,所以对各种实例都返回true,只有继承null的对象除外。

Object.prototype.__proto__

__proto__属性(前后各两个下划线)可以改写某个对象的原型对象。

var obj = {};
var p = {};

obj.__proto__ = p;
Object.getPrototypeOf(obj) === p // true
  • 1
  • 2
  • 3
  • 4
  • 5

上面代码通过__proto__属性,将p对象设为obj对象的原型。

根据语言标准,__proto__属性只有浏览器才需要部署,其他环境可以没有这个属性,而且前后的两根下划线,表示它本质是一个内部属性,不应该对使用者暴露。因此,应该尽量少用这个属性,而是用Object.getPrototypeof()(读取)和Object.setPrototypeOf()(设置),进行原型对象的读写操作。

原型链可以用__proto__很直观地表示。

var A = {
  name: '张三'
};
var B = {
  name: '李四'
};

var proto = {
  print: function () {
    console.log(this.name);
  }
};

A.__proto__ = proto;
B.__proto__ = proto;

A.print() // 张三
B.print() // 李四
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

上面代码中,A对象和B对象的原型都是proto对象,它们都共享proto对象的print方法。也就是说,ABprint方法,都是在调用proto对象的print方法。

A.print === B.print // true
A.print === proto.print // true
B.print === proto.print // true
  • 1
  • 2
  • 3

可以使用Object.getPrototypeOf方法,检查浏览器是否支持__proto__属性,老式浏览器不支持这个属性。

Object.getPrototypeOf({ __proto__: null }) === null
  • 1

上面代码将一个对象的__proto__属性设为null,然后使用Object.getPrototypeOf方法获取这个对象的原型,判断是否等于null。如果当前环境支持__proto__属性,两者的比较结果应该是true

获取原型对象方法的比较

如前所述,__proto__属性指向当前对象的原型对象,即构造函数的prototype属性。

var obj = new Object();

obj.__proto__ === Object.prototype
// true
obj.__proto__ === obj.constructor.prototype
// true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

上面代码首先新建了一个对象obj,它的__proto__属性,指向构造函数(Objectobj.constructor)的prototype属性。所以,两者比较以后,返回true

因此,获取实例对象obj的原型对象,有三种方法。

  • obj.__proto__
  • obj.constructor.prototype
  • Object.getPrototypeOf(obj)

上面三种方法之中,前两种都不是很可靠。最新的ES6标准规定,__proto__属性只有浏览器才需要部署,其他环境可以不部署。而obj.constructor.prototype在手动改变原型对象时,可能会失效。

var P = function () {};
var p = new P();

var C = function () {};
C.prototype = p;
var c = new C();

c.constructor.prototype === p // false
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

上面代码中,C构造函数的原型对象被改成了p,结果c.constructor.prototype就失真了。所以,在改变原型对象时,一般要同时设置constructor属性。

C.prototype = p;
C.prototype.constructor = C;

c.constructor.prototype === p // true
  • 1
  • 2
  • 3
  • 4

所以,推荐使用第三种Object.getPrototypeOf方法,获取原型对象。

var o = new Object();
Object.getPrototypeOf(o) === Object.prototype
// true
  • 1
  • 2
  • 3

编辑:操作系统 本文来源:原型对象,希望能得到大神的指点

关键词:

  • 上一篇:没有了
  • 下一篇:没有了