summary icon indicating copy to clipboard operation
summary copied to clipboard

JavaScript中原型的使用

Open fred-ye opened this issue 7 years ago • 0 comments

这篇文章来自于我的博客, 后面会慢慢的把东西整理整理移到博客上去。

每个函数都有一个prototype属性,该属性指向一个对象。而该属性只有在该函数做为构造器时才会发生作用,当函数作为构造器时,通过new去创建对象, 对象的原型(内置的prototype属性)会指向构造器(函数)的prototype属性。对象会自动拥有这个对象的构造函数的prototype的成员属性和方法.

先看一个示例:

function P () {
}

typeof P.prototype;
//"object"
//prototype指向的是一个对象

我们来修改P的prototype属性.

var protoObj = {
    date: new Date(),
    say: function() {
        console.log('Hello World');
    }
}
P.prototype = protoObj;

var obj = new P();
obj.date;
obj.say();

当我们将P作为构造器来创建obj时,obj就会拥有对P.prototype的访问权限。obj便可以将P.prototype对应的对象中的属性当作自己的属性来用。

将方法挂到类的原型上

我们定义一个构造函数,包含属性和方法,代码如下:

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.getName = function() {
        return this.name;
    };
}

这种方式,每次生成一个对象时会创建一个函数对象用来获取name属性 再看一种定义类的方式:

function getName() {
    return this.name;
}

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.getName = getName;
}

和上一种写法相比,我们只需要创建一次方法函数,在构造函数中引用它们。更优雅的写法如下,将方法挂到原型上。

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.getName = function() {
    return this.name;
}

Person.prototype可以被Person的所有实例共享。JavaScript 允许在程序中的任何时候修改原型(prototype)中的一些东西,也就是说可以在运行时(runtime)给已存在的对象添加额外的方法。如给String添加一个方法来返回逆序的字符串。

var s = "Simon";
s.reversed(); // TypeError on line 1: s.reversed is not a function

String.prototype.reversed = function() {
    var r = "";
    for (var i = this.length - 1; i >= 0; i--) {
        r += this[i];
    }
    return r;
}
s.reversed(); // nomiS

此部份参看这里

利用原型添加属性

有以下代码:

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

添加原型的两种方式:

  • 属性依次添加
Gadget.prototype.price = 100;
Gadget.prototype.rating = 3;
  • 添加一个对象
Gadget.prototype = {
    price: 100,
    rating: 3
}

原型的使用

javascript中每个对象中并没有属于自己的原型副本,我们可以随时修改prototype属性,并且由同一构造器创建的所有对象的prototype属性都会同时改变

自身属性和原型属性

var newtoy = new Gadget('test', 'red');

当我们在调用对象的某个属性时,javascript引擎会查询该对象的所有属性,如果没有找到。则会去查询创建这个对象的构造器函数的原型newtoy.constructor.prototype,如果在原型中找到了这个属性,就使用该属性。

  • 利用hasOwnProperty(prop)来判断一个属性是自身属性还是原型属性。
  • 获取某个对象的所有属性列表: for-in 循环
    • 并不是所有的属性都会在for-in中显示,如数组的length和constructor, 那些会显示的属性称为可枚举的,可以通过各对象所提供的propertyIsEnumerable()来判断。ES5可以指定哪些属性可枚举。
    • 原型链中的各个属性也会被列出来,前提是它们是可枚举的。
    • 对于所有的原型属性,propertyIsEnumerable()都会返回false,包括那些在for-in循环中可枚举的属性。
function Gadget(name, color) {
    this.name = name;
    this.coloe = color;
    this.getName = function() {
        return this.name;
    }
}
Gadget.prototype.price = 100;
Gadget.prototype.rating = 3;

var newtoy = new Gadget('webcam', 'black');
newtoy.propertyIsEnumerable('name'); //true;
newtoy.propertyIsEnumerable('constructor'); //内建属性和方法通常是不可枚举的
newtoy.propertyIsEnumerable('price'); //false; 原型链中的属性也是不可枚举的

但是,newtoy.constructor.prototype.propertyIsEnumerable('price')返回的是true; 其它很好理解,因为price原本就在newtoy.constructor.prototype这个对象上。

isPrototypeOf()

每一个对象都有一个isPrototypeOf方法,用来判断当前对象是否是另一个对象的原型。

getPrototypeOf()

ES5中提供的获取某个对象的原型的方法。

var mokey = {
    hair: true,
    feeds: 'banana'
}
function Human(name) {
    this.name = name;
}
Human.prototype = mokey;
var george = new Human('George');
Object.getPrototypeOf(george).feeds; // banana

对于另一部份实现了ES5的功能,但没有getPrototypeOf方法的浏览器,我们可以采用__proto__

扩展内建对象

采用原型来扩展内建对象

  • 在Array上添加一个查询数组是否存在某特定值的方法
Array.prototype.inArray = function(data) {
    for (var i = 0, len = this.length; i < len; i ++) {
        if (this[i] == data) {
            return true;
        }
    }
    return false;
}

var colors = ['red', 'green', 'blue'];
colors.inArray('red');
  • 字符串反转
String.prototype.reverse = function() {
    return Array.prototype.reverse.call(this.split('')).join('');
}

__proto__ 链接 (该属性非标准,先不做深入了解)

__proto__prototype不是等价的,__proto__是某个实例对象的属性,而prototype则是构造器的属性。当一个对象被创建时,它的__proto__ 属性和内部属性[[Prototype]]指向了相同的对象 (也就是它的构造函数的prototype属性).

// 声明一个函数作为构造函数
function Employee() {
  /* 初始化实例 */
}

// 创建一个Employee实例
var fred = new Employee();

// 测试相等性
fred.__proto__ === Employee.prototype; // true

原型的陷阱;

当我们对原型对象进行替换时,会触发原型链上的异常

function Dog() {
    this.tail = true;
}
var benji = new Dog();
var rusty = new Dog();
Dog.prototype.say = function() {
    return 'woof';
}

然后我们对Dog的原型进行如下替换:

Dog.prototype = {
    paws: 4,
    hair: true
}

再来看原有对象:

typeof benji.paws; //undefined
benji.say();// woof

之前创建的对象使用的是之前的prototype,我们来新建对象

var lucy = new Dog();
lucy.paws; //4
lucy.say;//undefined
lucy.constructor//function Object() { [native code] }
typeof lucy.__proto__.say; //undefined
typeof lucy.__proto__.paws; //number

会发现,其constructor的指向都不对了,为此,我们需要重写constructor

Dog.prototype.constructor = Dog;

fred-ye avatar Mar 09 '18 14:03 fred-ye