转载请注明预见才能遇见的博客:http://my.youkuaiyun.com/
原文地址:https://blog.youkuaiyun.com/pcaxb/article/details/100668246
JavaScript原生系列-创建对象、原型、原型链、继承(原型链继承、构造函数继承、组合继承)、插件模拟
目录
JavaScript原生系列-原型、原型链、原型链继承、组合继承
1.Object.create();//参数是对象,或者是null,参数是原型
2.自定义原型:(不是所有的对象都继承Object.prototype)
2.借用构造函数 (解决在创建子类型实例时,不能向超类型的构造函数中传递参数)
文章中涉及到的打印
//打印封装
function L() {
console.log.apply(this,arguments);
}
1.创建对象方式
1.最初创建对象的方式
let obj = new Object();
obj.name = "aa";
obj.getName = function() {
return this.name;
}
L(obj.getName());
2.字面量创建对象
let ps = {
name:"cc",
age:12,
getAge:function(){
return this.age;
},
}
L(ps.getAge());
Object构造函数或对象字面量创建的对象是一样的效果,推荐使用字面量,可以设置默认属性,简单方便。 虽然Object构造函数或对象字面量都可以用来创建单个对象,但是这种创建对象的做法直接导致代码无法复用,所以为了解决这个问题就产生了新的创建对象的方法--工厂模式。
3.工厂模式创建对象
function createPs(name,type){
let obj = new Object();
obj.name = name;
obj.type = type;
obj.getType = function(){
return this.type;
}
return obj;
}
let cp = createPs("dd","5");
这种方式虽然解决了对象实例化代码重复的问题,但这种方式创建的对象无法知道它的类型。公共属性和方法都是属于每个对象的,浪费内存。再看构造函数创建对象。
4.构造函数创建对象
看后面
5.原型模式创建对象
看后面
6.Object.create()创建对象
看后面
2.原型类型
隐式原型:所有引用类型(函数,数组,对象)都拥有__proto__属性(null、undefined、Object.create(null)除外)
显式原型:所有函数拥有prototype属性(Function除外),原型对象在定义函数时就被创建
我们创建的每个函数都有一个prototype原型属性,这个属性是一个指针,指向一个对象, 而这个对象的用途是包含所有实例共享的属性和方法,这个对象就是原型对象。原型对象会在创建一个新函数的时候根据一组特定的规则来生成。
普通对象:只拥有__proto__
函数对象:拥有__proto__和prototype,特殊情况Function.prototype
function person(){};
console.log(typeof person.prototype) //Object
console.log(typeof Object.prototype) // Object
console.log(typeof Function.prototype) // 特殊 Function
console.log(typeof Function.prototype.prototype) //undefined 函数对象却没有prototype属性
function Function(){};
var temp = new Function();
Function.prototype = temp; //由new Function()产生的对象都是函数对象
3.构造函数创建对象
对象会继承构造函数的方法,隐式原型指向构造函数的显示原型。
function Person(name,height,age){
this.name = name;
this.age = age;
this.height = height;
this.getHeight = function(){
return this.height;
}
}
Person.prototype.getAge = function(){
return this.age;
}
let p = new Person("hh",146,20);
p.getName = function() {
console.log(this.name)
};
L(p.getName(),p.getAge());
//getAge 虽然不是在对象p上但是对象会继承构造函数的方法
//对象的隐式原型指向构造函数的显示原型
console.log(p.__proto__ === Person.prototype);//true
当调用某种方法或查找某种属性时,首先会在自身调用和查找,如果自身并没有该属性或方法,则会去它的__proto__属性中调用查找,也就是它构造函数的prototype中调用查找。
每个对象都有一个constructor属性,constructor属性指向了Person这个构造函数。使用构造函数的方式创建的对象下多了一层__proto__,__proto__下有constructor属性。可以通过constructor属性来标识对象的类型,可以使用instanceof判断。
在执行new操作的时候会经历以下4个步骤:
(1)创建一个对象
(2)将构造函数的作用域赋给新对象(因此this指针就指向了新的对象) 【代码:p.__proto__ = Person.prototype】
说明:Person中的prototype是在创建构造函数的时候创建的。其实原型对象就是构造函数的一个实例对象。Person.prototype就是Person的一个实例对象。相当于在Person创建的时候,自动创建了一个它的实例,并且把这个实例赋值给了prototype。
function Person(){};
var temp = new Person();
Person.prototype = temp;
(3)执行构造函数中的代码
(4)返回新对象
使用构造函数的主要问题是每个对象都会把每个函数创建一遍,比如getHeight,每个对象都会创建一遍,非常损耗内存,所以引入了原型模式。
判断属性是实例对象属性还是原型对象属性:isOwnProperty()
获取所有可枚举的属性:Object.keys()
获取所有实例属性:Object.getOwnPropertyNames()
4.原型模式创建对象
一般不要把属性放在prototype中,否则实例会共享一个属性。
function Animal(){}
Animal.prototype.name = 'PIG';
Animal.prototype.type = '111';
Animal.prototype.say = function(){
return this.type;
};
let pig = new Animal();
除了原型使用上面的赋值操作,我们目前更喜欢使用对象字面量或者是new关键词来操作原型。但是使用对象字面量或者new关键词有一个很重要的知识点:无论是使用对象字面量还是new关键词,都是创建一个新的对象来替换掉原来的原型对象。
之前说过原型模式也是有缺点,其最大的缺点便是其共享的特性,随便修改原型对象中的任何一个属性,都会影响到它所实例化的所有对象,这样造成了不能出现“求同存异”的现象。因此我们会更多地使用下面的一种方法。
5.组合使用构造函数模式和原型模式
组合使用当然是将所有共享的属性放在原型对象中,所有独特的属性放在构造函数中
function Person(name,age){//独特的属性
this.name = name;
this.age = age;
}
Person.prototype = {//公共的属性
constructor:Person,
color:"white"
}
let person = new Person("cc",20);
L(person);
L(Person.prototype.constructor,Person,Person.prototype.constructor === Person);
这里如果不把constructor:Person那么Person.prototype.constructor === Object 不是Person
动态原型模式
function Person(name,age){
this.name = name;
this.age = age;
//动态原型模式
if(typeof this.getColor != 'function'){
Person.prototype.color = "white";
Person.prototype.getColor = function(){
return this.color;
}
//这里不能使用对象字面量的形式
// Person.prototype = {
// constructor:Person,
// color:"white",
// getColor:function(){
// return this.color;
// }
// }
}
}
let person = new Person("cc",20);
动态原型模式不能使用字面量的新式,因为在new的时候,是先给__proto__ 赋值,然后再执行构造函数的,所以,通过字面量的方式__proto__不会被赋值到新的prototype。
6.原型和原型链
我们创建的每个函数都有一个prototype原型属性,prototype是function对象的一个属性 ,这个属性是一个指针,指向一个对象, 而这个对象的用途是包含所有实例共享的属性和方法,这个对象就是原型对象。原型对象会在创建一个新函数的时候根据一组特定的规则来生成。
创建构造函数 -- 创建prototype属性 -- 指向原型对象 -- 原型对象有一个constructor属性 -- 指向构造函数
Person.prototype.constructor = Person;
根据ECMA-262第五版中的介绍,实例内部的指针明确称为[[Prototype]],虽然没有标准的方式访问该指针,但常用浏览器每个对象上都支持一个属性__proto__,__proto__这个
是浏览器自己实现的一个访问的接口,不是标准设定的,但是等价于标准定义的[[Prototype]]。
但prototype
属性却是构造函数特有的,它永远指向构造函数的原型对象。
实例虽然无法访问到[[Prototype]],但是可以使用isPrototypeof()方法来确定对象之间是否存在关系,也可以使用getPrototypeOf()来获取[[Prototype]]的值。for-in循环将会遍历所有能够通过对象访问、可枚举的属性,无论该属性位于实例中还是原型中。
* 构造函数--prototype
* 实例对象--[[prototype]]--浏览器自己实现的__proto__,[[prototype]] === __proto__
L(Object.getPrototypeOf(person) === Person.prototype);//true
L(Object.getPrototypeOf(person) === person.__proto__);//true
__proto__是浏览器自己实现的一个接口,constructor需要自己实现
console.log(Person.prototype);
Person.prototype = {
constructor:Person,
//这里是其他的属性
}
console.log(Person.prototype);
这里的两个打印是一样的
console.log(Function)//ƒ Function() { [native code] }
console.log(Object)//ƒ Object() { [native code] }
console.log(new Function())//ƒ anonymous() {}
console.log(new Object())//对象{}
隐式原型指向构造函数的显示原型(图一的绿色和蓝色可以看出prototype和__proto__相等的),所以
console.log(Person.prototype === p.__proto__);//true
对象p拥有一个属性值__proto__,并且__proto__是一个对象,包含两个属性值constructor和__proto__
console.log(p.__proto__);//对象{}
console.log(p.__proto__.constructor);//function Person(){}
console.log(p.__proto__.__proto__);//对象{},拥有很多属性值
console.log(p.__proto__.__proto__.constructor);//function Object(){}
console.log(Person.prototype.__proto__.constructor === Object);//true
console.log(Person.prototype.constructor === Person);//true
console.log(p.__proto__.__proto__ === Object.prototype);//true
console.log(Object.prototype.__proto__);//null
console.log(p.__proto__.__proto__.__proto__);//null
let Person = function(name,age) {
this.name = name;
this.age = age;
}
//prototype只有函数有
Person.prototype.getAge = function(){
console.log(this.age);
}
let p = new Person("cc",20);
p.getName = function() {
console.log(this.name)
};
p.getAge();//20
p.getName();//"cc"
console.log(p);
查找属性,如果本身没有,则会去__proto__中查找,也就是构造函数的显式原型中查找,如果构造函数中也没有该属性,因为构造函数也是对象,也有__proto__,那么会去它的显式原型中查找,一直找到null(Object层没有__proto__),如果没有则返回undefined。
每个对象无论是用什么方法创建的,都有一个__proto__
属性,这个__proto__
属性便是连接原型链的关键地方。通过__proto__形成原型链而非protrotype。
let c = {}
console.log(c.__proto__.constructor);
console.log(c.__proto__);
console.log(c.__proto__.constructor.prototype);
console.log(c.__proto__ === c.__proto__.constructor.prototype);//true
7.原型链(原型链继承)
原型链:原型链的基本思想是利用原型让一个类型继承另一个类型的属性和方法,把子类的原型对象设置为父类的实例,就形成了原型链。__proto__就是一个容器,装prototype的,沿着__proto__往上找,形成原型链。
function Person(name){
this.name = name;
}
Person.prototype.getName = function(){
return this.name;
}
function Gril(beautiful){
this.beautiful = beautiful;
}
//把子类的原型对象设置为父类的实例
Gril.prototype = new Person("cc");//__proto__
Gril.prototype.constructor = Gril;
Gril.prototype.getBeautiful = function(){
return this.beautiful;
};
var gril = new Gril("yes");
L(gril);
原型链也存在两个问题:
(1)原型中包含引用类型值的问题(同5中的原型引用问题)
(2)在创建子类型实例时,不能向超类型的构造函数中传递参数
8.Object.create()创建对象
1.Object.create();//参数是对象,或者是null,参数是原型
function Person(){
}
var p1 = Object.create(Person.prototype);
var p2 = new Person();
console.log(p1,p2)//p1和p2效果一样
2.自定义原型:(不是所有的对象都继承Object.prototype)
var obj = Object.create(null);//一个空对象
//undefined null 没有原型,没有包装类
3.__proto__可以更改,但是不能自己创建
var obj1 = Object.create(null);
obj1.__proto__ = {//新建
num:1
}
console.log(obj1.num);//undefined
var obj2 = new Object();
obj2.__proto__ = {//修改
num:1
}
console.log(obj2.num);//1
9.继承
1.原型链继承
看上面
2.借用构造函数 (解决在创建子类型实例时,不能向超类型的构造函数中传递参数)
借用构造函数(constructor stealing)(有时候也叫作伪造对象或经典继承),在子类型构造函数的内部调用超类型构造函数,函数只不过是在特定环境中执行代码的对象, 因此通过使用 apply()和 call()方法也可以在(将来)新创建的对象上执行构造函数。
function Person(name,age){
this.name = name;
this.age = age;
this.classify = "DEFAULT";
//动态原型模式
if(typeof this.getColor != 'function'){
Person.prototype.color = "white";
Person.prototype.getColor = function(){
return this.color;
}
}
}
function Gril(name,age,clothes){
Person.call(this,name,age);
this.clothes = clothes;
}
let gril = new Gril("cc",20,"beautiful");
gril.classify = "A";
console.log(gril);
let gril1 = new Gril("cc",20,"beautiful");
gril1.classify = "B";
console.log(gril1);
因为使用call函数的方法,让实例化Person超类的时候this指针指向了实例化的子类,相当于classify成了子类Gril的属性,因此每个实例操作的classify都是自己的私有属性。
因为使用call方法,我们还可以传递参数给超类,这样方法无法复用,每个实例都有自己一个实例的方法,所以一般需要要传递参数。
3.组合继承
组合继承(combination inheritance)有时候也叫作伪经典继承,结合了借用构造函数和原型链。使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
function Person(name,age){
this.name = name;
this.age = age;
this.classify = "DEFAULT";
//动态原型模式
if(typeof this.getColor != 'function'){
Person.prototype.color = "white";
Person.prototype.getColor = function(){
return this.color;
}
}
}
function Gril(name,age,clothes){
Person.call(this,name,age);
this.clothes = clothes;
}
Gril.prototype = new Person;
Gril.prototype.constructor = Gril;
Gril.prototype.getAge = function(){
return this.age;
}
let gril = new Gril("cc",20,"beautiful");
L(gril.getColor());//white
L(gril.getAge());//20
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继 承模式。而且, instanceof 和 isPrototypeOf()也能够用于识别基于组合继承创建的对象。
10.扩展
1.原始值是没有属性的
var num = 88;
console.log(num.__proto__)
console.log(num.toString());
2.原型方法的重写 toString 例子
var num = 555;
L(num.__proto__);
L(num.toString());//555 Number的toString
delete num.__proto__.toString;
L(num.__proto__);
L(num.toString());//[object Number] Object的toString
delete num.__proto__.__proto__.toString;
L(num.__proto__.__proto__);
L(num.toString());//报错 Object之上再无toString
3.表达式可以立即执行
;(function(){console.log(111)})()
;+function(){console.log(222)}()
;-function(){console.log(333)}()
;!function(){console.log(444)}()
;var fun = function(){console.log(555)}()
;(function(msg){console.log(msg)})(666)
4.插件模拟
;(function(){
function LogUtil(){
}
LogUtil.prototype = {
}
window.LogUtil = new LogUtil();
})()
JavaScript原生系列-创建对象、原型、原型链、继承(原型链继承、构造函数继承、组合继承)、插件模拟
博客地址:https://blog.youkuaiyun.com/pcaxb/article/details/100668246