JS Prototype原型链详解

以为理解的很清楚了,遇到问题了,卧槽、把自己搞蒙了。

问题复现(都是模拟、并非源代码):

// 公用模块
var parent = function(option){
	this.option = option;
}
parent.prototype = {
	print:function(){
		console.log(this.option.name);
	}
}

// 功能模块调用
var child = function(option){
	this.option = option;
	return new parent(this.option);
}
child.prototype={
	print:function(){
		console.log(this.option.name);
	}
}

// 功能中可能会有多个相同模块构建对象
var someModel = new child({name:"model_01"});
someModel.print();           // output:"model_01" 

打印出来看起来正常,都怪子模块中有相同的方法而且逻辑输出一致,导致之前没发现。
现在往子模块中追加了一个方法:

child.prototype={
	childPrint:function(){
		console.log(this.option.age);
	}
}

// 调用之后,终于报错了
var someModel = new child({name:"model_01",age:32});
someModel.childPrint();  
// output: someModel.childPrint is not a function

  原意是想在子模块中输出this.option.age,可是报错方法未定义。在方法中打断点根本没有执行,并且查看对象someModel中也没有这个方法,换了几种其他方式定义对象方法;
  最后才想到自己理解的原型链是不是有问题,将方法定义在父模块中,卧槽!执行了!emmmm…

分析:

原本这样做的目的是想child子类继承父类parent方法,且子类有自己的私有方法childPrint。问题就出在了return new parent();上。这个所谓的子模块实际返回的是父模块的一个实例。等于没有了child这一层,也就达不到我们想要自定私有方法的目的。

 打印出对象console.log(someModel),输出的时parent对象。

在这里插入图片描述
故再翻看一遍相关资料,进行总结。

继承与原型链

在JS中做继承只有一种结构对象,每一个实例对象都有一个__proto__属性,这个__proto__属性指向原型对象prototype

someModel.__proto__ == parent.prototype;   // true
someModel.__proto__ == child.prototype ;          // false

// 获取实例原型
Object.getPrototypeOf(someModel) == parent.prototype;       //  true


// 题外记,获取Object的原型对象,
// 会把Object当做构造函数处理。
Object.getPrototypeOf(Object) === Object.getPrototypeOf(Function) ; 
    // ƒ () { [native code] }

这也就是为什么上面的实例中访问不到childPrint方法了。看之前打印的someModel属于parent实例。所以它指向parent的原型对象,也就没有childPrint方法了。

所有实例对象的原型对象都会指向Object,即都是Object的实例。

注意原型链定义方法的写法

// 这样会破坏原型链,这种是重新定义了原型链。相当于重新声明覆盖!
parent.prototype = {
	print:function(){
		console.log(this.option.name);
	}
}
// 这样写就是在原型链上添加定义了一个新的方法。保持了原型链原态
parent.prototype.print = function(){
	console.log(this.option.name);
}

需要注意的几点:

  • 原型链上的this指向问题;
  • 对象上的属性查找;
  • 对象声明方式:
  1. 语法结构声明;引用类型:Array、Object、Function

     let arr = [1,3,5];  // 继承了数组、对象的方法。
     // arr - > Array.prototype -> Object.prototype -> null
     
     let obj = {name:"hello",age:32}; // 继承了对象的方法。
     // obj -> Object.prototype -> null
     
     let fun = function(){}; // 继承了函数、对象的方法。
     // fun -> Function.prototype - > Object.prototype -> null
    
  2. 使用new构造函数创建一个对象;像示例中那样。

  3. Object.create()创建一个新对象。第一个参数作为新对象的__prototype__属性;第二个参数定义新对象的私有属性或修改对应属性、及属性描述符。

  4. ES6的class,原型链上的语法糖。我之前的目的就是想实现继承extends,并且拥有私有方法。

  • 属性的遍历查找;确认自己想要查找的属性需要符合哪些要求,然后采用Object上的一些方法过滤一下,不要直接遍历。

继承示例

call()apply():调用父类,以便继承自身的属性;
Object.create():重写子类的prototype手动继承父类的原型对象

了解更多:call()/apply()部分

修改文章开头的示例:

// 父类
var parent = function(option){
	this.option = option;
	// 增加一个name属性
	this.name = "parent";
}
parent.prototype = {
	print:function(){
		console.log(this.option.name);
	}
}

// 子类实现扩展
var child = function(option){
	this.option = option;
	// return new parent(this.option);
	// call调用后传入的对象this,父类上的自身属性会继承。
	parent.call(this,option);
}

call()、apply()会决定该方法执行的上下文环境,也就是定义了this的指向;方法中自身拥有的属性则会被继承。

还需要继承它的原型链。

// 实现父类上的原型对象上的方法继承。
child.prototype=Object.create(parent.prototype,{
	age:{
		value:21, 
	    enumerable: true, 
	    configurable: true, 
	    writable: true 
	},
	print:{         // override parent method
		value:function(){ 
			parent.prototype.print.apply(this,arguments);
			// do more something...
			console.log(this.age);
		},
		enumerable: true,
		configurable: true, 
		writable: true
	},
	childPrint:{           // 定义自己的私有方法
		value:function(){
			console.log(this.option.age);
		}	
	}
})

// 实例化一个对象,查看打印结果正常。
var someModel = new child({name:"model_01",age:32});
someModel.print();           
// output:"model_01" 21
someModel.childPrint();
// output:32

Object.create()第二个参数新增或修改属性时,属性的描述符中value或writableget或set不能同时存在。

补一点属性描述符:
Object.defineProperties(obj, props) 多个属性的定义;
还有它的单数版:
Object.defineProperty(obj,prop,descripor) 定义单个属性的描述。

  1. 拥有valuewritable属于数据描述符;
  2. 拥有getset属于存取描述符;
  3. 默认是一个数据描述符。

有一个操作,可能需要在深入一点:

// 先输出一下child的原型对象构造函数
child.prototype.constructor; //  ƒ Object() { [native code] }
// 感觉好像不对,类的构造函数不应该是它自身吗?

child.prototype.constructor = child;
// 现在输出一下,构造函数是它自身
child.prototype.constructor; 
//  ƒ (option){
	this.option = option;
	// return new parent(this.option);
    parent.call(this,option);
}

疑问:在JS中的概念很弱,为什么还要讲究这个,构造函数,是因为ES6的class

2019-03-31 17:21补充

扩展实践(模拟ES 6 class模式)

可以先简单了解一下class语法

  1. 不使用prototype,只使用call/apply;将所有的属性全部加载到this

     var parent = function(option){
     	this.name="parent";
     	this.print=function(){
     		console.log("parent:",this.name);	
     	}
     }
     
     var child = function(option){
     	// 先调用父类的构造函数、类似ES6 super();
     	// 这样基本接合 ES6 类的概念 
     	parent.call(this,option);
     	this.name="child";
     	
     }
    
     // 实例化
     var model = new child({name:"model"});
    

      同class继承、子类的的构造函数继承父类构造函数;子类没有自己的this,我想应该是语法屏蔽,在这里我们可以在后面父类调用,只是name属性被覆盖了。

     // 后调用父类方法
     this.name="child";
     parent.call(this,option);
    

    类的构造函数是它自身:

     child.prototype.constructor = child;
    
  2. 除了构造函数的继承,当然还需要原型链的继承了。使用Object.create()上面已经写过了,就不写了。

    讨论一些情况
    如果我们不想父类,我们可能会直接继承父类的原型链:

     var parent = function(option){
     	this.name="parent";
     }
     parent.prototype.print =function(){
     	console.log(this.name);	
     }
     
     var child = function(){};
     child.prototype = Object.create(parent.prototype);
     
     // 然后实例化
     var model  = new child({});
     model.print();
     // output:undefined
    

      这个应该很清楚了,this指向的child就是没有name,我们再手动创建?
    疯了吗,我为的就是不想继承构造函数,还想让我声明!!!!

    怎么办?可以再设计时可以在父类原型上定义属性,两次定义?怕什么,又不会覆盖。。。

     parent.prototype = {
     	name:"parent",
     	print:function(){
     		console.log(this.name);	
     	}
     }
    

    父类的有些属性,我们需要在原型链中定义,因为在构造函数中可能会被子类继承并覆盖掉。如果写到原型链中,按照原型链特性属性查找方式;可以确保我们特意想要从父类传给子类的属性。

  3. 可以进行多个类的继承,使用Object.assign()mixin模式

     var anotherParent = function(){};
     
     Object.assign(child.prototype,anotherParent.prototype);
    

JS 原生对象原型不应该被扩展。尽可能的嵌套少一点,过于复杂导致性能问题。

参考:

1、MDN原型链与继承

属性的定义说的很楚。

2、《你不知道的JavaScript(上)》

使用过程的流程;涵盖的很清楚。
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: prototype是JavaScript中的一个关键属性,每一个函数对象都有一个prototype属性。在JavaScript中,对象是通过构造函数创建的,而prototype属性就是用来指向该构造函数的原型对象。 原型对象是一个普通的对象,它包含了可以被实例对象共享的属性和方法。当我们创建一个实例对象时,实例对象会通过__proto__属性链接到构造函数的原型对象上,从而实现了属性和方法的继承。 通过原型对象,我们可以为实例对象添加公共的属性和方法。当实例对象需要调用某个属性或方法时,如果实例对象自身不存在这个属性或方法,它会自动在原型对象中查找。这样就实现了属性和方法的共享和复用,提高了代码的效率。 可以通过调用构造函数的prototype属性来访问原型对象,我们可以在原型对象上添加属性和方法。例如,可以通过给Array.prototype添加方法来扩展数组对象的功能,也可以给Object.prototype添加方法来扩展所有对象的功能。 需要注意的是,原型对象是共享的,当我们修改原型对象上的属性或方法时,所有的实例对象都会受到影响。因此,在修改原型对象时需要谨慎,避免对已有的对象造成不必要的影响。 总结来说,prototype属性是JavaScript中用于实现继承和共享属性的关键属性,它定义了构造函数的原型对象,使得所有通过该构造函数创建的实例对象都能够共享原型对象上的属性和方法。通过原型对象,我们可以为实例对象添加公共的属性和方法,提高代码的复用性和效率。 ### 回答2: prototype属性是JavaScript中每个对象都拥有的一个属性,它是一个指针,指向该对象的原型原型是一个普通的对象,它包含了一些可被该对象实例共享的属性和方法。 通过使用prototype属性,我们可以为对象定义共享的属性和方法。当我们创建一个对象的实例时,该对象实例将会从其原型中继承这些共享的属性和方法。 在JavaScript中,对象的原型是通过构造函数来创建的。在创建构造函数时,会自动为该构造函数创建一个prototype属性,并将其指向一个空对象。我们可以通过向原型对象中添加属性和方法,从而为实例对象提供共享的属性和方法。 原型对象中的属性和方法,可以被该构造函数创建的所有实例对象公用。当我们访问实例对象中的属性或方法时,如果实例对象本身没有该属性或方法,就会通过原型链的查找,去原型对象中寻找。 原型属性的优势在于节省内存。因为所有实例对象共享同一个原型对象,而不是每个实例对象都拥有一份相同的属性和方法。这样就可以大大减少内存占用。 另外,通过在原型对象中定义属性和方法,还可以实现动态的属性和方法。即使在对象创建之后,我们仍然可以通过修改原型对象来为其新增属性和方法,而所有实例对象都会即刻生效。 总之,prototype属性是JavaScript中用于实现对象间继承和共享属性的重要机制,它可以让我们更高效地创建对象,并且提供了灵活的方式来定义和修改对象的共享属性和方法。 ### 回答3: prototype属性是JavaScript中的一个特殊属性,它是由每个函数创建的对象所共享的。每个构造函数都有一个prototype属性,它指向一个对象。 使用prototype属性可以给对象添加方法和属性,这些方法和属性可以被该构造函数创建的所有对象访问和共享。当我们创建一个新对象时,JavaScript会自动为该对象创建一个__proto__属性,它指向构造函数的prototype属性。 通过给prototype属性添加方法,我们可以在构造函数创建的每个对象中共享这些方法。这么做的好处是避免了在每个对象中重复创建方法的内存消耗,同时也提高了代码的可维护性。 原型继承是JavaScript的一种核心概念,它基于对象的原型链,在对象的原型链中查找属性和方法。当我们访问一个对象的属性或方法时,JavaScript会先在对象本身查找,如果找不到,就会去原型链上的原型对象中查找。这种属性和方法的继承关系使得JavaScript的对象可以“继承”来自原型对象的属性和方法。 通过使用构造函数和对象的原型链,JavaScript实现了类似于面向对象编程语言的继承概念。我们可以创建一个构造函数,通过它创建的对象会自动继承该构造函数的原型属性和方法。这样可以实现代码的复用和灵活性,减少重复的代码。 总结来说,prototype属性是通过构造函数创建的对象所共享的一个属性。通过给prototype属性添加方法和属性,可以实现这些方法和属性在所有对象中的共享。原型继承是JavaScript中实现代码复用和灵活性的一种机制,通过原型链的查找,对象可以继承来自原型对象的属性和方法。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

heroboyluck

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值