引言
常规情况 , 原型 prototype 本质是一个对象 {} ;
原型链的 链 本质是一个对象引用 , 指向特定的 prototype 对象 ;
prototype 和 __proto__ 都是对象上的一个属性;
也就是说 , 非常规的情况, 是可以手动修改 prototype 和 __proto__ 为任何值的.
为什么需要 prototype ?
就像工厂需要通过对原材料经过特定工序批量生产产品一样 , 程序也需要通过传入不同的参数经过特定的方法处理然后返回一个新的对象.
- 常规创建对象 , 完全模拟工厂的创建
function createObj(name,age){
var obj = {};
obj.name = name ;
obj.age = age ;
obj.sayHello = function(){ alert('hello') };
return obj;
}
var obj1 = createObj('xx',1);
obj1.sayHello();
也就是说 , 完全模拟工厂模式, 每创建一个产品对象, 对象就已经独立于原工厂 , 它始终能
sayName
, 就像你买的工厂生产的收音机 , 即使哪天工厂倒闭, 也不影响你的使用.
- 程序中针对工厂模式的优化.
程序是对现实的世界的抽象 , 但有时从程序使用角度, 会对一些结构做调整以优化程序. 而这也导致了用现实的角度不足以完全理解程序.
上例中的只传入了 name
和 age
两个不同的材料, 而 sayName
的表现基本一致.
也就是可以把要创建的对象简单粗暴分成两部分:
- 依赖传入参数的部分
- 不依赖传入参数的部分
然后 , 上面的 2
抽离出去, 以省成本和时间 .
function createObj(name,age){
var obj = {};
obj.name = name ;
obj.age = age;
return obj;
}
// 工厂偷懒的部分
createObj.lazy = {
sayHello:function(){ alert('hello') }
}
var obj1 = createObj('xx',1);
obj1.sayHello(); // Uncaught TypeError: obj1.sayHello is not a function
分离出去, 却不能调用方法了.
function createObj(){
var obj = {};
obj.name = name ;
obj.age = age;
obj.methods = createObj.lazy;
return obj;
}
createObj.lazy = {
sayHello:function(){ alert('hello') }
}
var obj1 = createObj('xx',1);
obj1.methods.sayHello();
- 程序中 new 的实现
new 则是针对以上做了封装, 一些操作后台操作完成:
- 创建一个新的对象 ;
- 新对象的
__proto__
属性指向构造函数的prototype
- 新对象添加相应属性
- 返回这个新对象
简单模拟
function New(constructorFn,...args){
// 1 + 2
var obj = Object.create(constructorFn.prototype);
// 3
constructorFn.apply(obj,args);
// 4
return obj;
}
function Person(name,age){
this.name = name;
this.age = age ;
}
Person.prototype = {
constructor:Person,
sayHello:function(){ alert('hello') }
}
var p1 = New(Person,'xx',18)
p1.sayHello();
js原生 new
function Person(name,age){
this.name = name;
this.age = age ;
}
Person.prototype = {
constructor:Person,
sayHello:function(){ alert('hello') }
}
var p1 = new Person('xx',18)
p1.sayHello();
prototype
的作用, 就是把一些公用的方法抽离出去, 减少创建新对象的所占空间和所需时间 , 创建实例时并不实际拥有这些方法 , 在访问方法的时候 , 又能通过一些约定
找到对应的公用方法.
通用的方法都挂载在原工厂或构造方法的 prototype
属性上, 而 约定
就是实例通过 __proto__
访问 prototype
;
构造函数与普通函数并无区别, 首字母大写也非必须 . 约定而已.
prototype 的一些特点和坑
- 每个函数都有默认一个 prototype , 箭头函数除外.
function test(){}
typeof test.prototype ; // 'object'
var test2 = ()=>{};
typeof test2.prototype; // undefined
- 每个函数的 prototype 都默认有一个属性 constructor 指向函数本身
function test(){}
test.prototype.constructor === test; // true
- prototype 是可以随意更改的, 虽然不建议
function Test(){};
// 更改为非对象值时, 则 t 的 __proto__ 不再指向 Test.prototype
Test.prototype = 'hello';
var t = new Test();
t.__proto__ === Test.prototype; // false
// 更改为对象值时, 则 t2 的 __proto__ 依然是指向 Test2.prototype 的
function Test2(){};
Test2.prototype = {};
var t2 = new Test2();
t2.__proto__ === Test2.prototype; // true
- prototype 在实例化前后对整个 prototype 的更改的区别
function Test(){}
Test.prototype = {
name :'jack'
}
var t1 = new Test();
t1.name ; // 'jack'
Test.prototype = {
name:'john'
}
var t2 = new Test();
t2.name ; // 'john'
t1.name ; // 'jack'
实例的 __proto__ 指向构造函数的 prototype 对象, __proto__ 值为一个引用值, 实例化之后 , 不再更改 ; 再将改构造函数的 prototype 的引用指向别的对象, 不影响已经实例化的对象.
类似这样:
var a = {
name :'jack'
}
var b = a ;
a = {
name :'john'
}
console.log(a.name); // 'john'
console.log(b.name); // 'jack'
js 是如此的自由, 享受自由带来的好处的时候 , 也就很容易出现一些意想不到的 bug
;
js 语言层面不强制规范 , 则只能人为约定一些规范 . prototype 操作规范:
- 尽量只在原
prototype
上增加或者删除属性, 不将prototype
指向一个新对象. - 尽量不修改
prototype
的constructor
属性 - 不将
prototype
赋值一个基本类型的值 - 将
prototype
赋值给其他的对象时 ,constructor
添加上正确的constructor
属性. - 对
prototype
的更改 , 在离构造函数最近的地方. 在实例化对象之前.
为什么需要 __proto__ ?
实例对象要访问到构造函数中的 prototype , js 内核实现就是通过 __proto__
查找. 而且是在内部优化过的, 即不需要 obj.__proto__.methodName() 才能访问, 中间的 __proto__ 是可以省略的.
- 通过 __proto__ 访问构造函数的 prototype 对象上的方法和属性
function Man(){}
Man.prototype.sayHi = function(){
console.log('hi');
}
var m = new Man();
m.__proto__.sayHi();
// __proto__ 可省, 效果一样
m.sayHi();
m.__proto__.sayHi === m.sayHi ; // true
- __proto__ 链可以很长
function Man(){}
Man.prototype = {}
var m = new Man()
m.toString(); // "[object Object]"
m.toString === m.__proto__.__proto__ ; // true
Man
的 prototype 并没有 toString 方法 , 这里的 toString 而是 Man.__proto__ 上的 , 也就是 Object.prototype 上的.
周边
- instanceof ; 判断一个实例对象是不是另外一个对象的实例.
instanceof 的核心 , 判断的是 , 一个实例对象的 __proto__ 链条中 , 是否存在构造函数的 prototype 属性.
模拟实现
function Instanceof(obj,construtorFn){
if(!obj.__proto__){
return false
}
else{
if(obj.__proto__ === construtorFn.prototype ){
return true;
}
else{
return Instanceof(obj.__proto__,construtorFn)
}
}
}
function Person(){}
var p1 = new Person()
Instanceof(p1,Person) ; // true
Instanceof(p1,Object) ; // true
Instanceof(p1,Function) ; // false
Person.prototype = {};
Instanceof(p1,Person) ; // false
- 实现继承
可以看到, 对象在自身找不到方法和属性的时候 , 会沿着 __proto__ 往上找 , 直到 __proto__ 为null , 即 Object.prototype.proto 为 null 终止.
那么实现继承, 也就是将 __proto__ 链条建立起来即可.
如 m1 是 Man 的实例 , 继承 Person ,
那就需要 m1.__proto__ === Man.prototype ;
然后 m1.__proto__.__proto__ === Person 或 m1.__proto__.__proto__.__proto__ === Person
模拟实现 :
function Person(){}
Person.prototype.eat = function(){}
function Man(){}
Person.prototype.strong = function(){}
function extend(fn,superClass){
fn.prototype.__proto__ = superClass.prototype;
}
extend(Man,Person);
var m1 = new Man();
m1.eat();
m1.strong();
m1 instanceof Man; // true
m1 instanceof Person; // true
以上只简单实现了 __proto__ 的链条的连接. 还需要实现实例化传参 , super 还可以用 Object.create 方法.
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.eat = function () {
console.log('eating')
}
function Man(name, age, power) {
Person.call(this, name, age)
this.power = power;
}
Man.prototype.strong = function () {
console.log('i am strong')
}
function extend(fn, superClass) {
var _fnPrototype = fn.prototype;
fn.prototype = Object.create(superClass.prototype)
fn.prototype.constructor = fn;
Object.assign(fn.prototype, _fnPrototype);
}
extend(Man, Person);
var m1 = new Man('zs', 18, 100);
m1.strong()
m1.eat();
总结
- 构造函数也是函数.
- prototype 和 __proto__ 都是对象
- 函数也是对象, prototype 则是函数对象上的一个属性, 该函数 new 出来的实例, 通过 __proto__ 可以访问到函数的 prototype , 且 __proto__ 可省直接调方法和属性.
- instanceof 验证的是 实例的 __proto__ 链上是否存在构造函数的 prototype 属性.