一,给一个对象增加原型的方式:
- 通过new (js中所有的东西都是可以new出来的,new出来的对象为了实现继承,才必须有一个隐式原型的指针属性__proto__)
- 通过Object.create(原型)
- 字面量的原型链(和new的一样)
下面我们分析这两种方式形成的原型链:
二,通过new一个构造函数->创建对象时形成的原型链:
下面一个构造函数的例子和new的实现,通过看new操作符的实现可以更好的理解原型链上的
__proto__和prototype
function Person(name,age){
this.name = name;
this.age= age;
this.fn=()=>{ console.log('方法')}
}
Person.prototype={
fn1:function(){console.log("fn1")}
}
var p=new Person('andy');//可以不加小括号,不传参的话
p.name//"andy"
p.fn()//"方法"复制代码@实现一个构造函数(入参是构造函数,与其他参数,返回一个对象)function _new(/*constr,params*/){//...arg更方便
let args=[...arguments];
let constructor=args.shift();
var newObj={};
newObj.__proto__=constructor.prototype;
//let newObj = Object.create(constructor);//issue
let result = constructor.apply(newObj ,args);
console.log("result",result,"newObj ",newObj )
return typeof result === "object" ? result: newObj
}
var p = _new(Person,'andy',22);//new总结四步:提取参数,第一个参数为构造函数,剩余的为其他参数(构造函数私有属性,方法);创建对象,并把其原型指向构造函数的原型;调用构造函数,并把this指向新创建的对象;返回此对象,如果构造函数返回一个对象,则返回此对象(不建议构造函数有返回值),否则返回新创建的对象;以上代码我们只需要关注这一句代码即可:
newObj.__proto__=constructor.prototype;
我们都知道new一个构造函数,返回的是一个对象;
那么,在new在创建的这个对象,通过以上代码很直观的可以看出,
这个对象有一个__proto__的属性,指向了构造函数的prototype
得出结论,只有对象才有隐式原型,只有函数才有显示原型,下方会继续推导正确的结论。
Number.prototype.__proto__===Object.__proto__; //false
Number.prototype.__proto__===Object.prototype; //true
Object.prototype.__proto__; //null@继续思考: Number既是函数,又是对象。所以,他同时拥有两种原型属性。
Number.__proto__; //ƒ () { [native code] }
Number.__proto__===Function.prototype; //true
Number.prototype;//Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ, …}
@看一下constructor
构造函数的prototype上都有一个construtor指向他自己
Number.prototype.constructor===Number //true发现一个很诡异的问题:
Object.__proto__.__proto__.__proto__; //null这是什么情况??~~
@包装对象1的原型链
new Number(1).__proto__.__proto__.__proto__===null //true
(1).__proto__.__proto__.__proto__===null //truenew Object({a:1}).__proto__.__proto__===null //true@图解原型链
- JS中万物皆是对象,对象上都会有__proto__属性。
- 而Number,Object属于构造函数,所以,都有prototype原型对象。此原型对象上,有属性__proto__和constructor属性等。(见下方图一)
- 构造函数上不止有prototype对象,而且还有另外的一个__proto__属性。(见下方第二图)
@从构造函数上出发的原型链
- Number构造函数上的__proto__,(并非Number.prototype原型对象的上__proto__)
Number.__proto__===Function.prototype; // true
Function.prototype.__proto__===Object.prototype; // true
Object.prototype.__proto__===null; // true
Number.__proto__.__proto__.__proto__ === null; //true
- Object构造函数上的__proto__, (并非Object.prototype原型对象的上__proto__)
Object.__proto__; // ƒ () { [native code] }
Object.__proto__ ===Function.prototype; // true
Function.prototype.__proto__===Object.prototype; // true
Object.prototype.__proto__===null; // true
Object.__proto__.__proto__.__proto__===null; // true
- Function构造函数上的__proto__,(并非Function.prototype原型对象的上__proto__)
此链比较特别。Function上的__proto__指向了自己的prototype原型对象。
Function.__proto__; // ƒ () { [native code] }
Function.__proto__===Function.prototype; // trueFunction.prototype.__proto__ === Object.prototype; // trueObject.prototype.__proto__ === null; // true
Function.__proto__.__proto__.__proto__===null; // true
- Number原型对象上__proto__属性出发的原型链条
Number.prototype.__proto__===Object.prototype; // true
Object.prototype.__proto__ ===null; // true
- Object构造函数上的__proto__属性,指向Function.prototype,(证明Object是函数的本质。)(Object有__proto__属性,因为只有对象才有__proto__属性,证明了Object是对象。)=》说明了所有构造函数既有prototype,又有__proto__。近一步证明了,JS中万物皆是对象。{数字1,字符串,布尔值(是包裹对象)、正则表达式、Function、Number、Object都是对象}。
- Object
Function.prototype原型对象上的__proto__又指向Object.prototype
Object.__proto__===Function.prototype; // true
Function.prototype.__proto__===Object.prototype; // true
Object.prototype.__proto__ ===null; // true
只有函数有prototype,只有这种对象不是函数,固没有prototype。
new Number(1).prototype; //undefinedJS中一切皆是对象。都有隐式原型__protot__
new Number(1).__proto__; //Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ, …}
(1).__proto__; //Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ, …}
Number.__proto__; // ƒ () { [native code] }; //ƒ () { [native code] }
Array.__proto__; // ƒ () { [native code] }; //ƒ () { [native code] }
RegExp.__proto__; // ƒ () { [native code] }; //ƒ () { [native code] }
String.__proto__; // ƒ () { [native code] }; //ƒ () { [native code] }
Object.__proto__; // ƒ () { [native code] }; //ƒ () { [native code] }
Symbol.__proto__; // ƒ () { [native code] }; //ƒ () { [native code] }
Date.__proto__; // ƒ () { [native code] }; //ƒ () { [native code] }
undefined.__proto__; //VM3065:1 Uncaught TypeError: Cannot read property '__proto__' of undefined
null.__proto__;//VM3065:1 Uncaught TypeError: Cannot read property '__proto__' of undefined
三、通过Object.create(原型对象),创建的原型链
Object.create(proto [,propertiesObject]) 功能:创建一个对象,其原型为prototype,同时可添加多个属性。
参数:
- proto(必须):原型对象,可以为null表示没有原型。
- descriptors(可选):包含一个或多个属性描述符的对象。
propertiesObject参数详解:
数据属性
- value:值
- writable:是否可修改属性的值
- configurable:是否可通过
- delete:删除属性
- enumerable:是否可for-in枚举
- 访问属性 get():访问 set():设置
示例:
function Person(name){
this.name = name;
}
Person.prototype.say = function(){console.log('my name is ' + this.name +',my age is ' + this.age);}
var person = new Person('andy');
var p = Object.create(person,{
age:{
value: 23,
writable: true,
configurable: true
},
sex:{
configurable: true,
get:function(){return sex + '士';},
set:function(value){sex = value;}
}
});
p.sex = '男';
p.say(); //'my name is andy,my age is 23'
console.log(p.sex); //'男士'
p.sex = '女';
console.log(p.sex);//女士@Object.create的原型链
p.__proto__.__proto__.__proto__.__proto__;//null@Object.create的原型链2
var a1={}
a1.prototype; //undefined
a2.prototype; //undefined
var a2=Object.create(a1)
a2.__proto__===a1 ; // true
a1.__proto__===Object.prototype ; // true
a2.__proto__.__proto__.__proto__===null; // true
@注意:
a1 没有原型对象prototype; a2指向的原型对象就是a1本身!
@Object.create 的基本实现原理
入参为原型对象,出参为一个new之后的构造函数。此构造函数的原型指向入参Obj.
function create(obj) {
function F() {}
F.prototype = obj;
return new F();
}内部实现就是通过创建一个构造函数,然后new一个实例出来,此构造函数的原型指向我们指定的原型。最终返回一个对象。指定原型的对象创建完成。
四、原型链使用注意事项:
- 不要太长的原型链,影响查询效率!
- 构造函数上不仅仅有prototype原型对象(此原型对象上有__proto__),此构造函数也有自己的__proto__属性。可以理解为一个构造函数上有两个__proto__。但是,一个在他的原型对象上,一个在他自己身上。
- 只有函数有prototype,(有一些对象是函数(new出来的对象就一定只是对象),但是所有的函数都是对象)
- JS中一切皆是对象。都有隐式原型__proto__
- (1)、[] 、{} 、 true、“abc” 、Object 、 Function 、Array、String都有__proto__,试想如果没有这个隐式原型的指针,就没有办法实现继承。
五、疑问推导:
Object的隐式原型指向的是Function原型对象。只有这个指向是逆向的回指,先逆向指向Function,然后在顺指回来。然后在指向null
Object.__proto__.__proto__.__proto__; //nullObject.__proto__ ===Function.prototype; //true
//Function.__proto__ === Function.prototype; //trueFunction.prototype.__proto__===Object.prototype; //true
Object.prototype.__proto__===null; //true
六、instanceof原理与实现
说原型链,就不得不说一下instansof,那么,先看一下instansof是如何实现的
instanceof的作用是用来做检测类型
instanceof 检测一个对象A是不是另一个对象B的实例的原理:
查看对象B的prototype指向的对象是否在对象A的[[prototype]]链上。如果在,则返回true,如果不在则返回false。不过有一个特殊的情况,当对象B的prototype为null将会报错(类似于空指针异常)。
函数模拟A instanceof B
function instans_of(obj,func){
var obj = obj.__proto__;//取对象obj的隐式原型
var func = func.prototype;//取构造函数的显示原型
while(true){
if(obj===null){return false};
if(obj===func){return true};//这里重点:当 obj 严格等于 func 时,返回 true
obj=obj.__proto__;//
}
}规则简单来说就是 obj的 __proto__ 是不是强等于 func.prototype,不等于再找 obj.__proto__ .__proto__ 直到 __proto__ 为 null
@instanceof对整个原型链上的对象都有效,因此同一个实例对象,可能会对多个构造函数都返回true
七、类型判断方法总结
- typeof
判断基本类型的,比如:Number, Boolean, Undefined, String, Null, 其他的引用类型和Null会返回object
- instanceof
检测一个对象A是不是另一个对象B的实例
- Object.prototype.toString
Object.prototype.toString.call('');//"[object String]"
Object.prototype.toString.call(Symbol);//"[object Function]"
Object.prototype.toString.call(Symbol());//"[object Symbol]"Object.prototype.toString.call([]);//"[object Array]"Object.prototype.toString.call(RegExp());//"[object RegExp]"Object.prototype.toString.call({});//"[object Object]"Object.prototype.toString.call(Symbol());//"[object Function]"七、学生的原型链
function Person(name, age){
this.name = name;
this.age = age;
}
function Student(score){
this.score = score;
}
Student.prototype = new Person('李明',22);
var s = new Student(99);
console.log(s instanceof Student); //true
console.log(s instanceof Person); //true
console.log(s instanceof Object); //true
s.__proto__ === Student.prototype; // true
Student.prototype; //Person new之后返回的对象{name: "李明", age: 22}
Student.prototype.__proto__===Person.prototype; // true
Person.prototype.__proto__===Object.prototype; // true
Object.prototype;//{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
Object.prototype.__proto__===null; // true
s.__proto__.__proto__.__proto__.__proto__===null; // true
//Object的隐式原型与Function原型对象上的隐式原型互相指向对方的原型对象。(互指向)
Object.__proto__===Function.prototype; // true
Function.prototype.__proto__===Object.prototype; // true
@小例子总结:
- Object的隐式原型与Function原型对象上的隐式原型互相指向对方的原型对象。(互指向)
- s只有__proto__没有prototype。其__proto__指向Student的prototype原型对象。
- Student.prototype上的__proto__又指向Person的prototype的原型对象。
- Person.prototype原型对象上只有__proto__属性和constructor属性
- __proto__是属性,prototype是对象,除了JS自己的构造函数默认有prototype对象上的很多方法属性外,其他自己创建的构造函数,默认prototype对象上只有两个属性(__proto__和constructor),如果需要,需要自己去增加原型对象上的属性和方法。
- Student.prototype = new Person('李明',22); 这行代码的作用实现了Student.prototype 原型对象上的__proto__指向了Person的prototype。所以student是有prototype对象的。并且二者都为{name: "李明", age: 22}这个对象。 也就是new Person的时候 this指向了Student.prototype
- Student.prototype.name可以 取到对象中的值。Person构造函数中this下的属性,就是给new出来的实例用的,并非是给自己用的。他自己也用不到。
- 还注意一点:Student原型对象上的constructor指向的是他的构造函数Person,Person原型对象上的constructor指向的是他自己Person,
Person.prototype.constructor===Person; // true
Student.prototype.constructor===Person; // trueObject.prototype.constructor===Object; // true所有构造函数原型对象中都有一个constructor指向的是构造函数自己。因为他没有自己的构造函数。
八、constructor 我是谁的实例对象
function F(){};
var f = new F();
f.__proto__.constructor===F; // true
F.__proto__.constructor===Function; // true
F.prototype.constructor===F; // true
Function.__proto__.constructor===Function; // true
Number.prototype.constructor===Number; // true
Number.__proto__.constructor===Function; // true
Object.prototype.constructor===Object; // true
__________________________________________________________________
@看图加深印象
var a=new Number(1);
a.prototype; // undefined
a.__proto__.constructor===Number; // true
Number.__proto__.constructor===Function; // true
Number.prototype.constructor===Number; // trueFunction.prototype.constructor===Function; // true
Function.__proto__.constructor===Function; // true
//都是对象
typeof a.__proto__; //"object"
typeof Number.prototype; //"object"//默认优先使用__proto__属性对象上的constructor
a.constructor===Number; // true
Number.constructor===Function; // true//
a.constructor===Function; // false
@constructor总结:
- a.constructor===Number; 或者a.__proto__.constructor===Number;默认使用隐式原型上的constranctor。
- 不建议使用prototype下的constranctor。
- 除了(1)这种对象外,其他的构造函数(也就是其他的所有对象)都有两个constructor。
- 我们只需要记住直接使用constanctor即可。
- 关键:我是谁构造出来的实例对象。
九、字面量的原型链(字面量其实也是new出来的,原型链无异)
__proto__永远指向prototype
prototype下的__proto__永远指向prototype。除了null
var o={};
o.__proto__===Object.prototype;
Object.prototype.__proto__===null
({}).__proto__.__proto__===null
var arr=[1]
arr.__proto__===Array.prototype
Array.prototype.__proto__===Object.prototype
Object.prototype.__proto__===null
[1].__proto__.__proto__.__proto__===null
十、总结
- __proto__和prototype都是对象。
- 只有函数有prototype。函数同时也有两个__proto__.一个在函数本身上,一个在他的原型对象上。
- 只有对象有__proto__。回想new的实现中 obj.__proto__=courstranctor.prototype.
- __proto__永远指向prototype
- prototype下的__proto__永远指向prototype。除了null
- JS中万物皆是对象,对象上都会有__proto__属性。
- 只有函数有prototype。(1),“ab”,[],{}等实例是没有prototype.因为他们是数据对象(实例对象),不是函数。
- 函数也有__proto__,而且有两个
————————————————————————————————————————
@待补充与完善:
isPrototypeOf()方法允许你检查一个对象是否存在于另一个对象的原型链上。- __proto__属性,用来读取或设置当前对象的prototype对象
- Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)
@__proto__属性,多角度参考思考
@它本质上是一个内部属性,而不是一个正式的对外的 API ,只是由于浏览器广泛支持,才被加入了 ES6 。标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的。因此,无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,而是使用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替
@__proto__
主要用来回溯对象,即用来继承的,@__proto__的实现 (从网上粘贴了一段如下)未考证
Object.defineProperty(Object.prototype, '__proto__', {
get() {
let _thisObj = Object (this);
return Object.getPrototypeOf(_thisObj);
},
set(proto) {
if (this === undefined || this === null) {
throw new TypeError();
}
if (!isObject(this)) {
return undefined;
}
if (!isObject(proto)) {
return undefined;
}
let status = Reflect.setPrototypeOf(this, proto);
if(!status) {
throw new TypeError();
}
},
});
function isObject(value) {
return Object(value) === value;
}