叉烧店并不卖叉烧

javascript-original-object-prototype

javascript TTT 145℃ 0评论

参考

我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法

引用类型才具有prototype属性,包含:

  • 1.Object
  • 2.Function
  • 3.Array
  • 4.Date
  • 5.String
  • 6.RegExp

例如:

var fn = new String("text");
console.log(fn); //[object String]: {0: "t", 1: "e", 2: "x", 3: "t", length: 4}
console.log(fn.value); //undefined
String.prototype.value="val"; 
console.log(fn.value); //val
function Person(name){
    this.name = name;
}
Person.prototype.getName = function () {
    return this.name;
};
var p1 = new Person("Evan"); 
console.log(p1.name); // Evan
console.log("next line");
console.log(p1.getName()); // Evan

这个prototype属性也就相当于动态添加了属性或者方法

例子:

function Person2 () {
}
Person2.prototype.name = "Nicholas";
Person2.prototype.age = 29;
Person2.prototype.job = "Software Engineer";
Person2.prototype.sayName = function () {
    console.log(this.name)
};
var p2 = new Person2();
var p3 = new Person2();
p2.sayName(); // Nicholas
console.log(p2.sayName == p3.sayName); // true

一张图看各个对象的关系:

图中的Person为代码的Person2

Person2里面只有一个prototype属性,指向原型对象(原型对象顾名思义就是每次用Person2去实例化一个对象的时候,都是有基于原型对象,使用prototype为Person2添加属性和方法就是为Person2的原型对象去添加属性和方法)。原型对象中的constructor指向它的构造函数(它的来源,也就是Person2),和其他原型属性和方法。

Person2.prototype就是原型,isPrototypeOf确定二者是否有关系,Object.getPrototypeOf获取原型值

console.log(Person2.prototype); // {age: 29, constructor: function Person2() { }, job: "Software Engineer", name: "Nicholas", sayName: function () { console.log(this.name) }}
console.log(Person2.prototype.isPrototypeOf(p2)); // true
console.log(Object.getPrototypeOf(p2).name); // Nicholas
console.log(Person2.prototype.constructor == Person2); // true

将上例稍微改一下,给实例化的P2添加name属性并赋值:name: me

p2.name = "me";
// 现在实例中找,没有再到原型中找
console.log(p2.name); // 
console.log(p2.constructor == Person2); // true
// 用hasOwnProperty()检测属性或者方法是否在实例中(也就是当前对象Person2所拥有,而非原型对象Person2.prototype)
console.log(p2.hasOwnProperty("name")); // true
console.log(p3.hasOwnProperty("name")); // false
// 用一个in,检测是否有次属性(也就是查找自身对象,原型对象和原型对象中的原型对象),无论在实例还是原型对象中
console.log("name" in p2); // true
console.log("name" in p3); // true

若改整个Person2.prototype:

// 这是在Chrome浏览器实现的,在IE和edge表现不同
Person2.prototype = {xx: "xx"};
console.log(p2.xx); // undefined
var ppp = new Person2();
console.log(ppp.xx); // xx

只会改变后面的实例的对象,之前的不会改变。之前引用的不会被垃圾清理。(这是在Chrome浏览器钟进行的试验,IE和Edge有不同的表现)

p2和p3是实例化的Person2,也能访问Person2的原型对象。Person2用指针[[Prototype]]来实现访问原型对象。p2和p3不能操作[[Prototype]],但可以用__proto__来访问

console.log("========");
console.log(p1.prototype); // undefined
console.log(Person2.prototype); // {name: "Nicholas", age: 29, job: "Software Engineer", sayName: ƒ, constructor: ƒ}
console.log(p2.__proto__); // {name: "Nicholas", age: 29, job: "Software Engineer", sayName: ƒ, constructor: ƒ}
console.log(Person2.__proto__); // ƒ () { [native code] }

实例化对象调用原型对象是使用__proto__指针,而不是prototype指针。对象本身没有原型对象,是去调用构造函数的原型对象

当构造函数Person2调用__proto__,返回它本身

__proto__prototype的区别:(IE不支持__proto__)

  • __proto__:对象的内部原型的引用
  • prototype:返回类的原型

当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就回去__proto__里找这个属性,这个__proto__又会有自己的__proto__,于是就会这个一直找下去,也就说我们平时所说的原型链的概念。所以__proto__是联系各个对象的桥梁

var Person3 = function () {};
var p4 = new Person3();
console.log(p4.__proto__ == Person3.prototype); // true

上面的例子,实例p4的来源(__proto__)就是Person3的原型(prototype)

复杂例子:

var Person4 = function () {};
Person4.prototype.say = function () {
    alert("Person say");
}
Person4.prototype.salary = 50000;
var Programmer = function () {};
Programmer.prototype = new Person4();
Programmer.prototype.writeCode = function () {
    alert("Programmer writes code");
};
Programmer.prototype.salary = 500;
var p5 = new Programmer();
p5.say();
p5.writeCode();
alert(p5.salary); // 500

来做一个推导:
1.var p5 = new Programmer()可以得出p5.__proto__ = Programmer.prototype
2.而在上面我们指定了Programmer.prototype = new Person4(),我们来这样拆分,假设var p1 = new Person4(),那么:p1.__proto__ = Person4.prototypeProgrammer.prototype = p1。因此:Programmer.prototype.__proto__ = Person4.prototype
3.因为var p5 = new Programmer(),所以:p5.__proto__ = Programmer.prototype。因为Programmer.prototype.__proto__ = Person4.prototype,所以:p5.__proto__.__proto__ = Person4.prototype

根据上面的推导,运行p5.say(),由于p5没有say这个属性,于是去p5.__proto__也就是Programmer.prototype也就是p1中去找,由于p1也没有say,那就去p5.__proto__.__proto__也就是Person4.prototype中去找,于是找到了 say()的方法

console.log("======");
var student = { name: 'aaron' };
console.log(student.__proto__); // Object{}

JavaScript中的所有对象都是继承自Object对象,所以这里来源是Object {}

关于继承

function Animal() {}
Animal.prototype.say = function () {
    return "www";
}
function Person5() {}
Person5.prototype.say = function () {
    return "hello";
}
Person5.prototype = new Animal();
var p6 = new Person5();
console.log(p6.say()); // www

当一个类的prototype指向另一个对象(说对象而不说类是因为用new的方式赋值,如果不用new的方式可以用Animal.prototype的方式赋值。此时需要弄明白用new赋值的含义,比如var p = new Person()此时系统会自动赋值p.__proto__ = Person.prototype,而系统在查找属性的时候,如果在对象中没有找到对应属性的时候,就会去查找__proto__prototype属性里面的内容)时,就实现了继承。如例子中Person5继承了Animal

这里要注意的是p6.say()调用的是Animal中的say是因为Person中的sayPerson5.prototype = new Animal();中被覆盖了,所以如果需要修改继承后的属性,则需要在继承之后重写即可

console.log(p6.constructor); // Animal() {}
// Object.getPrototypeOf() 返回对象的原型
console.log(Object.getPrototypeOf(Person5.prototype).constructor); // Animal() {}

Person5.prototype等于new Animal()是一个对象

引用类型问题

function SuperType () { this.color = ["red", "blue", "green"]};
function SubType () {};
SubType.prototype = new SuperType();
var s1 = new SubType();
s1.color.push("black");
console.log(s1.color); // ["red", "blue", "green", "black"]
var s2 = new SubType();
console.log(s2.color); // ["red", "blue", "green", "black"]

s1和s2是子类SubType的实例化,s1把继承父类SuperType的color属性进行添加,按理说只能s1自己添加,而不影响其他的子类SubType实例化的对象,但是结果把其他所有子类SubType实例化的对象的color属性一起改了

原因是SubType.prototype = new SuperType()相当于:把父类SuperType实例化赋值给子类SubType.prototype,实例化之后内存地址就是固定了(相当于静态变量),因此由子类SubType实例化的对象的__proto__属性指向同一个地方,因此共享color属性

但是这样不合理,可以进行一下更改:

function SuperType2 () { this.color = ["red", "blue", "green"] };
function SubType2 () {
    SuperType2.call(this);
}
// SubType2.prototype = new SuperType2();
var s3 = new SubType2();
s3.color.push("black");
console.log(s3.color); // ["red", "blue", "green", "black"]
var s4 = new SubType2();
console.log(s4.color); // ["red", "blue", "green"]

call()函数用于调用一个对象的一个方法,以另一个对象替换当前对象,也就是将SuperType类中的内容在SubType类中运行一遍,此时的this是SubType的this

所以每次实例化子类SubType,都要调用子类重写的函数SuperType.call(this),进行一次分配,每个实例拥有自己的color属性。互不干扰

call()

转载请注明:叉烧店 » javascript-original-object-prototype

喜欢 (0)
发表我的评论
取消评论

CAPTCHA Image
Reload Image
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址