基础知识
原型
创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向了一个对象。所有由此函数用new操作符创建出的对象都共享此原型(属性、方法)。
原型对象
任何一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象
。默认情况下,所有的原型对象都会自动获得一个constructor
属性,这个属性指向的是prototype属性所在函数的指针。
1 2 3 4 5
| function Person(name) { this.name = name; }
console.log(Person.prototype.constructor)
|
例如上述代码中,Person.prototype.constructor指向的是Person。
输出应该为
1 2 3
| function Person(name) { this.name = name; }
|
对于对象来说,浏览器(FireBox、Chrome)都会默认添加一个属性__proto__
,这个指向的是什么?
1 2 3 4 5 6 7
| function Person(name) { this.name = name; }
let p = new Person('Bob');
console.log(p.__proto__.constructor)
|
console输出为
1 2 3
| function Person(name) { this.name = name; }
|
所以说,这个__proto__
指向的是Person.prototype
判断原型
1 2 3 4 5 6 7 8 9 10 11 12 13
| function Person(name) { this.name = name; }
function SPerson(name) { this.name = name; }
let p1 = new Person('Bob'); let p2 = new Person('Tom');
Person.prototype.isPrototypeOf(p1); Person.prototype.isPrototypeOf(p2);
|
获取原型
1 2 3 4 5 6 7
| function Person(name) { this.name = name; }
let p = new Person('Bob');
Object.getPrototypeOf(p) == Person.prototype;
|
原型和属性的优先级
1 2 3 4 5 6 7 8 9 10
| function Person() { }
Person.prototype.name = "Tom"; let p1 = new Person(); let p2 = new Person(); p1.name = 'Bob'
p1.name; p2.name;
|
当对象属性存在和原型属性同时存在时,顺序应当为 对象属性 -> 原型属性进行读取。
判断属性是来自对象还是原型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function Person(){ } Person.prototype.name = 'Cos';
let p1 = new Person(); let p2 = new Person();
p1.hasOwnProperty("name");
p1.name = 'Bob'; p1.name; p1.hasOwnProperty("name")
delete p1.name; p1.hasOwnProperty("name")
|
in操作符
1 2 3 4 5 6 7 8 9
| function Person() { }
Person.prototype.name = 'Bob';
let p1 = new Person();
console.log("name" in p1);
|
使用in操作符可以判断一个属性是否存在于对象或者原型中。
扩展:如果想要判断一个属性是否存在并且是否为对象所属的,我们应该用什么进行判断?
1 2 3 4 5 6 7 8
| function Person() { }
Person.prototype.name = 'Bob';
let p1 = new Person();
!p1.hasOwnProperty("name") && ("name" in p1);
|
继承和原型链
继承
在OO(面向对象)语言中,一般都支持两种继承方式:接口继承和实现继承,而在JS中,仅仅支持实现继承这种方式。
原型链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function SuperType() { this.property = true; }
SuperType.prototype.getSuperValue = function() { return this.property; }
function SubType() { this.subproperty = false; }
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() { return this.subproperty; }
let instance = new SubType(); instance.getSuperValue();
|
由上面的代码可以看出instance指向了SubType的原型,SubType的原型又指向了SuperType的原型。有一个特殊说明:如果调用instance.prototype.constructor,则返回的是SuperType,这是因为被重写了。
其他说明
- 属性和方法先会检索对象本身是否存在,如果不存在则沿着原型链向上寻找。
- 所有默认的原型都是Object的实例
- 确定原型和实例的关系可以用instanceof操作符
some instanceof Object
- 原型是引用类型,共享原型中的属性,用一个例子说明
1 2 3 4 5 6 7 8 9 10 11 12 13
| function SuperType() { this.colors = ['red', 'blue', 'yello']; }
function SubType() { }
SubType.prototype = new SuperType(); let ins1 = new SubType(); ins1.colors.push('white');
let ins2 = new SubType(); ins1.colors;
|
常用的构造模式
组合使用构造模式和原型模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ["Tom", "Court"]; }
Person.prototype = { constructor: Person, sayName: function () { alert(this.name); } };
let person1 = new Person("Nicholas", 27, "Software Engineer"); let person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van"); console.log(person1.friends); console.log(person2.friends); console.log(person1.friends === person2.friends); console.log(person1.sayName === person2.sayName);
|
运行结果
1 2 3 4
| [ 'Tom', 'Court', 'Van' ] [ 'Tom', 'Court' ] false true
|
特点总结
- 原型可以复用代码,person1.sayName === person2.sayName可以看出指向的同一个内存地址。
- 使用构造函数构造的对象,即使有有相同的方法(不放在原型中)也不指向同一内存空间。
动态原型模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function Person(name, age, job) { this.name = name; this.age = age; this.job = job;
if (typeof this.sayName !== 'function') { Person.prototype.sayName = function() { console.log(this.name); } } }
let person1 = new Person('Bob', 24, 'Teacher'); person1.sayName();
|
运行结果
特点总结
- 当使用动态原型模式的时候,不能够以对象字面量的形式进行重写,否则则会切断所有以创建对象和原型之间的联系,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function Person(name, age, job) { this.name = name; this.age = age; this.job = job; }
Person.prototype.test = function() { console.log('I\'m test'); }
let p1 = new Person('Bom', 20, 'Teacher');
Person.prototype = { sayName: function() { console.log(this.name); } }
|
从上述代码中,如果我们在Location C的地方用字面量的形式重写prototype,则相当于让prototype指向了一块新的内存地址,而在LocationB已经创建好的p1的__proto__
,则会于新的原型切断联系。
- 通过修改原型的方式,可以让已经创建好对象的
__proto__
拥有原型中的方法。例如:
1 2 3 4 5 6 7 8 9 10 11
| function Person(name, age, job) { this.name = name; this.age = age; this.job = job; }
let p1 = new Person('bob', 21, 'Doctor');
Person.prototype.sayName = function () { console.log(this.name); }
|
则p1也具有了sayName这个属性,不过是使用prototype中的。
寄生构造函数模式
1 2 3 4 5 6 7 8 9 10 11 12 13
| function SpecialArray() { let values = new Array(); values.push.apply(values, arguments);
values.toPipedString = function() { return this.join('|'); };
return values; }
let colors = new SpecialArray('red', 'blue', 'green'); console.log(colors.toPipedString());
|
运行结果
特点总结
- 使用寄生构造模式对于Array这种系统内置类型,想要进行二次修改可以通过使用该模式。
- 只是包装了一下扩展Array类,本质上与在函数体外面写相同,返回的对象与构造函数或与构造函数的原型属性之间并无关系。
稳妥构造函数模式
1 2 3 4 5 6 7 8 9 10
| function Person(name) { let o = new Object(); o.sayName = function() { console.log('AAA I\'m big Big ' + name); }; return o; }
let p = Person('Bob'); p.sayName();
|
运行结果
特点总结
- 在该模式下,创建Person对象的时候,没有使用new操作符。
- 一般在安全环境下(禁止this等操作符),适合使用稳妥模式。
借用构造函数
为了解决原型中包含引用类型值所带来的问题,开始出现了借用构造函数。即在子类型构造函数的内部调用超类构造函数。
1 2 3 4 5 6 7 8 9 10 11 12
| function SuperType() { this.colors = ['red', 'blue', 'yellow']; } function SubType() { SuperType.call(this); }
let ins1 = new SubType(); ins1.colors.push('green');
let ins2 = new SubType(); ins2.colors;
|
所以说,这是在即将创建的那个对象的环境下调用了SuperType构造函数,这样就会在SubType对象上执行所有SuperType的初始化代码。
组合继承
这种技术是通过将原型链和借用构造函数结束组合使用,这样既可以通过在原型上定义方法实现了函数的复用,又能够保证每个实例都有它自己的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function SuperType(name) { this.name = name; this.colors = ['red', 'blue', 'yellow']; }
function SubType(name, age) { SuperType.call(this, name); this.age = age; }
SubType.prototype = new SuperType(); SubType.prototype.contructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age); }
|
原型式继承
借助已有的对象创建新对象。
1 2 3 4 5 6
| function object(o) { function F(){} F.prototype = o; return new F() }
|
现在object函数内部,创建了一个临时的构造函数F,并且将传入的对象作为该构造函数的原型,最后返回这个临时类型的新实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| let person = { name: "Nicholas", friends: ['Shelby', 'Court', 'Van'] }
let anotherPerson = object(person); anotherPerson.name = 'Greg'; anotherPerson.friends.push('Rob');
let yetAnotherPerson = object(person); yetAnotherPerson.name = 'Linda'; yetAnotherPerson.friends.push('Barbie');
person.friends;
|
上述代码中,我们将person对象使用object函数作为参数传入,新创建的xanotherPerson对象将作为person对象的原型。
在ES5中,新增了Object.create() 方法规范化了原型式继承。
1 2 3 4 5 6 7 8 9 10 11 12 13
| let person = { name: "Nicholas", friends: ['Shelby', 'Court', 'Van'] }; let p1 = Object.create(person); p1.name = 'Greg'; p1.friends.push('Hans');
let p2 = Object.create(person); p2.name = "Shely"; p2.friends.push('Bob');
console.log(person.friends);
|
对于Objet.create()方法还存在第二个参数(选填),写法与defineProperties()一致。
1 2 3 4 5 6 7 8 9 10
| let person = { name: "Nicholas", friends: ['Shelby', 'Court', 'Van'] };
let anotherPerson = object.create(person, { name: { value: "Greg" } });
|
寄生式继承
该模型与原型式是紧紧相连的一种模式,他的实现类似于与寄生构造函数和工厂模式类似的。
1 2 3 4 5 6 7
| function createAnother(original) { let clone = object(original); clone.sayHi = function() { console.log('Hi'); } return clone; }
|