原型
每一个JS对象(null除外)都和另一个对象相关联,这个“另一个对象”就是原型,每一个对象都是从原型继承属性。
看到这里,不理解,到底怎么理解“继承”这两个字?
继承-个人理解:就是新定义的对象从已存在的对象那里获取了相同的方法和属性,简单的说大概就是将已存在的对象上的属性和方法引用到了一份在自己身上,这样就可以直接使用这些属性和方法了。
原型-个人理解:也就是prototype,实际上说穿了,每一个函数都有一个prototype属性,这个属性比较特殊,本身是一个对象(之前对象那一章学过,对象代表了一个集合,集合了N多属性),这个属性是可以理解成是定义新函数时,系统自动给函数生成的,而它的作用就是一个指针,将添加的内容指向函数,效果相当于就是给构造函数添加了属性或方法,而这个prototype对象,我们称作原型对象。
使用prototype的优点是:所有的对象实例都可以共享它包含的属性和方法,并且可以在任意地方添加,而不是直接写死在构造函数里
示例(注意,如果定义的是构造函数,那么通常首字母大写):
function Person(){}
Person.prototype.name='asd';
console.log(Person.prototype) //{name:"asd"}
var person1=new Person();
扫描二维码关注公众号,回复: 2596622 查看本文章console.log(person1.name) //asd
解释:我们定义了一个构造函数Person(),由上文介绍可知,当我们定义了一个函数后,系统会默认给它加上一个prototype属性,这个属性是一个对象,这个属性也就是对象有个名字叫做原型对象,刚定义是这个原型对象是空的;
而后,我们给Person.prototype增加了一个属性,属性名叫name,属性值叫做asd,这边通过打印可以得到这个这个原型对象的键值对集合,解释时我们也说了,原型对象还是隶属于构造函数的,是函数的属性,相当于给构造函数添加了属性,因此,当我们实例化了一个person1时,这个person1会继承到构造函数的属性,那么自然而然其原型对象中的属性也被继承到了,因此当我们person1.name时,就直接获取到了asd。
let o={} //定义了一个对象,这个对象的属性方法继承自Object.prototype
let a=new Array() //定义了一个数组,继承自Array.prototype
获取对象的原型属性
Object.getPrototypeOf(someObj)
var a={};
console.log(Object.getPrototypeOf(a)===Object.prototype);
对象原型属性:__proto__(不推荐使用)
这个属性因为浏览器的广泛支持,写到了ES的附录中,只有浏览器环境才支持这个属性,其他环境并不需要,不推荐,因为这是一个私有属性,在调试代码的时候这个属性比较有用,这个属性也是可写的,意味这个给对象添加__proto__属性就是在添加原型
var o={};
console.log(o.__proto__) //获取到对象的原型
设置对象的原型属性
Object.setPrototypeOf(obj,prototype)
let a={};
let b={x:1}
Object.setPrototypeOf(a,b)
console.log(Object.getPrototypeOf(a));{x:1}
a.x = 2;
console.log(a.x); //2
注意:添加的原型属性只能是对象,原始值会报错
检测是否为某个对象的原型
someObj.isprototypeOf(anotherObj)
let c={x:1};
let d=Object.create(c);
console.log(c.isPrototypeOf(d));
对象中的super关键字
这个关键字是在ES6中添加的,作用是指向该对象的原型
let obj={
method1(){
return "1a"
}
}
let obj2={
method1(){
return super.method1()
}
}
Object.setPrototypeOf(obj2,obj);
console.log(obj2.method1()); //"1a"
上面这个是类?带学完后补充完整
Object.create(protot[,propertiesObject])
- protot:原型对象
- propertiesObject:这是一个数据描述符,这个参数跟Object.definePrototies(obj,props)第二个参数一样
原型链
在JS中,一般而言对象都是继承自Obejct.prototype,包括数组Array
大致就是:a[]继承自Array.prototype,Array.prototype继承自Object.prototype,这个跟作用域链很相似
自定义构造函数
function Person(){}; //构造函数约定俗成首字母大写
Person.prototype.name="123aaa";
Person.prototype.y=function(){
return this.name;
}
var person1=new Person();
var person2=new Person();
person1.y(); //123aaa
person2.y(); //123aaa
console.log(person1.y === person2.y); //true
console.log(person1.y === Person.prototype.y); //true
这边可以看出实例化的person1和person2都是继承的Person的原型对象,原型对象被所有实例共享,一旦修改原型对象,那么所有的实例都将会修改
操作对象属性
查询属性
查询属性会沿着对象的原型链一级一级往上查询,直到找到目标或者原型是null的对象为止
function Age() {}
Age.prototype={
name:"john",
age:23
}
let a1=new Age();
a1.from="china";
console.log(a1.age); //23
console.log(a1.from); //china
console.log(a1.toString); //ƒ toString() { [native code] }
这边的a1本身没有age这个属性,当没有时,会去它原型链上的上一级查找,上一级是Age.prototype,找到后返回打印,而后a1本身有form这个属性,因此第二个直接打印,第三个发现自身没有,上一级Age.prototype也没有,就再往上一级Object.prototype上查找;
另外,如果找到最后,在顶层原型上也没有,那么会返回undefined;
添加属性
首先检测对象是否允许添加属性,如果允许,则在原始对象上添加属性或者更改已有属性,添加属性操作不会去修改原型链;
function Age() {}
Age.prototype={
name:"john",
age:23
}
let a1=new Age();
a1.from="china"; //这边的from属性只是a1自身的,并不会去修改Age.prototype上的原型
只读属性
function Nav() {}
Nav.prototype={
name:"ulysses",
age:"28"
}
Object.defineProperty(Nav.prototype,"from",{
writable:false,
value:'china'
})
let nav1=new Nav();
nav1.from="America";
console.log(nav1.from); //china
上文说了,添加的时候会去检测属性是否在原型链上已存在,并且检测是否可修改,如果不能修改,那么什么都不会发生,另外检测是检测整条原型链,而不是单一某个原型对象,上面的Nav.prototype换成是Object.prototype也是一样的;
属性已存在的情况
function Person(){}; //构造函数约定俗成首字母大写
Person.prototype.name="123aaa";
Person.prototype.y=function(){
return this.name;
}
var person1=new Person();
var person2=new Person();
person1.name="asd";
console.log(person1.name); //asd
console.log(person2.name); //123aaa
这边可以看出实例化后的属性修改,是相互不影响的,person1的修改没有影响到person2(注意,有一种情况特殊,是数组的push操作,下面会讲)
setter
function Name(){};
Name.prototype={
name1:"aaa",
name2:"bbb",
set fullName(value){
let name=value.split(' ');
this.name1=name[0];
this.name2=name[1];
}
}
let name=new Name();
name.fullName=("ccc ddd");
console.log(name.name1);
这个实例设置fullName时,会调用原型上的set方法,会修改原型上的name1和name2
删除属性
delete运算符只能删除对象的自身属性,不能删除从原型继承而来的属性
function Person(){};
Person.prototype.name="123aaa";
Person.prototype.y=function(){
return this.name;
}
var person1=new Person();
var person2=new Person();
person1.name="asd";
console.log(person1.name); //asd
console.log(person2.name); //123aaa
delete person1.name
console.log(person1.name); //123aaa
console.log(person2.name); //123aaa
检测属性
可以通过in,hasOwnProperty,propertyIsEnumerable等方法来检测属性是否存在
let o={x:1}
in
console.log("x" in o) //true
console.log("y" in o) //false
console.log("toString" in o) //true,会检测整个原型链上的,因此会检测Object.prototype上是否存在
hasOwnProperty
console.log(o.hasOwnProperty("x")) //true
console.log(o.hasOwnProperty("y")) //false
console.log(o.hasOwnProperty("toString")) //false,不会检测整个原型链,只会检测自身是否存在这个属性
propertyIsEnumerable
是hasOwnProperty的增强版,是自身属性,且是可枚举的才返回true
console.log(o.hasOwnProperty("x")) //true
console.log(o.hasOwnProperty("y")) //false
console.log(Object.prototype.hasOwnProperty("toString")) //false,不会检测整个原型链,只会检测自身是否存在这个属性
hasPrototypeproperty
检测原型链是否存在某个属性
console.log(o.hasPrototypeproperty("x")) //true
console.log(o.hasPrototypeproperty("y")) //false
console.log(o.hasPrototypeproperty("toString")) //true,检测整个原型链,是否存在这个属性
注意:
检测属性时,有一个不易察觉的坑
示例:
let o={x:1}
if(o.x){
alert(true)
}
这段代码本身没有问题,检测对象o是否有x属性,但是,这边有一个问题,假如x的值本身就是一个假值(例如:0,false),
就不会执行alert,如果改成
let o={x:1}
if(o.x !== undefined){
alert(true)
}
这么改还是有个问题,假如x的值本身就是undefined,那么又不会执行,因此,还是得使用hasOwnProperty
枚举属性
可以使用 for/in,Object.keys,Object.getOwnPropertyNames来获得的枚举属性;
通过属性描述符设置了enumerable:false,那么这个属性就不能被枚举到了,也就是for/in等查询不到
- for(let a in obj):查询整条原型链上可枚举属性;
- Object.keys(obj):只查找自身可枚举的属性;
- Object.getOwnPropertyNames(obj):查找自身所有属性,可枚举,不可枚举的都查
实践的原型
查询属性会遍历原型链,有一定的性能问题。要注意代码中原型链的长度,并在必要时,将其分解,以避免潜在的性能问题
共享带来的问题
原型上的属性被所有实例共享,如果属性值是对象类型,则某个实例更改后会英雄到其他实例,这旺旺不是实际所期望的效果
function Bbody(){};
Bbody.prototype={
name:"john",
book:[]
}
let b1=new Bbody();
let b2=new Bbody();
b1.book.push("asd"); //通过push操作修改的属性,会影响原型
//b1.book=["asd"] //赋值“=”进去的操作,不会修改原型
console.log(b2.book); //asd
最后,不要去扩展Object.prototype原型,除非你有强大的把控能力