原型和原型链
- 1.理解原型设计模式以及
JavaScript
中的原型规则
原型设计模式
JavaScript是一种基于对象的语言,JavaScript中的所有对象,都具用prototype属性,该属性返回对象的所有属性和方法。
所有JavaScript内部对象都有只读的prototype属性,可以向其原型中动态添加属性方法,但该对象不能被赋予不同的原型,但是自定义的对象可以被赋给新的原型。
对象分为函数对象和普通对象,区分:
凡是通过new function() 创建的对象都是函数对象,其他的都是普通对象。(Object、Function是JS自带的函数对象)1.原型对象:prototype
在JS中,函数对象其中一个属性:原型对象prototype。普通对象是没有prototype属性。但是有 _ proto _ 属性。
原型的作用就是给这个类的每一个对象都添加一个统一的方法,在原型中定义的方法和属性都是被所有实例对象所共享。
var person = function(name){
this.name = name
};
person.prototype.getName =function(){
//通过pserson.prototype设置函数对象属性
return this.name
}
var lzq = new person("李子柒");
lzq.getName();
image.png
2.原型链
_ proto _: js创建对象的内置属性,用于指向创建它的函数对象的原型对象prototype.(是jS内部使用寻找原型链的属性,当实例化一个对象的时候,内部会新建一个 _ proto _ 属性并把prototype赋值给它)
如下:当实例一个对象之后,调用它内部的方法,它的运行顺序是先找到自身有没有该方法,如果没有就会通过 _ proto _ 属性向上层去寻找,一直寻找到Object对象中,如果还没有才会报错null
p._proto_ ----->Person._proto_----->object._proto_----->null
3.设计模式
(1)工厂模式:在函数内创建一个对象,给对象赋予属性及方法再将对象返回。
function Parent(){
var Child = new Object();
Child.name ="李子柒";
Child.age = "24"
Child.sex = function(){
return "女"
}
return Child;
}
// 调用
var x = Parent();
console.log(x.name);//李子柒
console.log(x.sex()) ; // 女
image.png
(2)构造函数模式:无需在函数内部重建对象,而是用this指代
function Parent(){
var Child = new Object();
this.name ="李子柒";
this.age = "24";
this.lev = lev;
Child.sex = function(){
return "女"
}
return Child;
}
// 调用
var x = new Parent();
console.log(x.name); //李子柒
console.log(x.sex()); // 女
(3)原型模式:函数中不对属性进行定义,利用prototype属性对属性进行定义,可以让所有对象实例共享它所包含的属性及方法
function Parent(){};
Parent.prototype.name="李子柒";
Parent.prototype.age ="24";
Parent.prototype.sex = function(){
var s = "女"
console.log(s)
}
//调用
var x = new Parent();
console.log(x.name); //李子柒
console.log(x.sex()); //女
4.混合模式:原型模式+构造函数模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。
function Parent(){
this.name = "李子柒";
this.age = 24;
}
Parent.prototype.sayname = function(){
return this.name
}
// 调用
var x = new Parent();
console.log(x.sayname()); //李子柒
image.png
5.动态原型模式:将所有信息封装在了构造函数中,而通过构造函数中初始化原型,这个可以通过判断该方法是否有效而选择是否需要初始化原型。
function Parent(){
this.name = "李子柒";
this.age = 24;
if(typeof Parent._sayname == "undefined"){
Parent.prototype.sayname = function(){
return this.name;
}
Parent._sayname = true;
}
};
// 调用
var x = new Parent();
console.log(x.sayname())
image.png
原型规则
A.所有的引用类型(数组、对象、函数)都具有对象特性,即可自由扩展属性:
var arr = []; arr.a = 1;
B.所有的引用类型(数组、对象、函数),都有一个 _ proto _ 属性(隐式原型)属性值是一个普通的对象
C.所有的函数、都具有一个prototype(显式原型),属性值也是一个普通对象;
D.所有的引用类型(数组、对象、函数),其隐式原型指向其构造函数的显式原型;(obj._ proto _ === Object.prototype)
E.当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的_ proto _(即它的构造函数的prototype)中寻找。
instanceof的底层实现原理,手动实现一个instanceof
instanceof底层工作:
function instance_of(L,R){ //L左表达式 R右表达式
var O = R.prototype; //取R的显示原型
L = L._proto_; //取L的隐式原型
while(true){
if(L === null){
return false
}
if(O === L){ //当O 显示原型 严格等于L隐式原型时,返回 true
return true;
L = L._proto_
}
}
}
手动实现一个instanceof(用到constructor)
function instance_of(L,R){
var O = R;
L = L._proto_
while(true){
if(L === null){
return false;
if(O === L.constructor){
return true
}
}
}
}
var a = []
var b = {}
function Foo(){}
var c = new Foo()
function child(){}
function father(){}
child.prototype = new father()
var d = new child()
console.log(instance_of(a, Array)) // true
console.log(instance_of(b, Object)) // true
console.log(instance_of(b, Array)) // false
console.log(instance_of(a, Object)) // true
console.log(instance_of(c, Foo)) // true
console.log(instance_of(d, child)) // false 这里就是无法用于判断继承的
console.log(instance_of(d, father)) // true
实现继承的几种方式以及他们的优缺点
继承方式 见:理解原型设计模式以及JavaScript中的原型规则
优缺点
1.原型链继承-->优缺点:构造函数原型上的属性在所有该构造函数的实例上是共享的,即属性没有私有化,原型上属性的改变会作用到所有的实例上。
2 构造函数继承 ---> 优缺点:实现了属性的私有化,但是子类无法访问父类原先上的属性
3组合继承 -->利用 构造函数和原型链方法,可以比较完美的实现继承
function Super(){
this.flag = true
}
Super.prototype.getFlag = function(){
return this.flag; //继承方法
}
function Sub(){
this.subFlag = false
Super.call(this) //继承属性
}
Sub.prototype = new Super;
var obj = new Sub();
Sub.prototype.constructor = Sub;
Super.prototype.getFlag = function(){
return this.flag
}
小问题:Sub.prototype = new Super; 会导致Sub.prototype的constructor指向Super;然而constructor的定义要指向原型属性对应的构造函数,Sub.prototype是Sub构造函数的原型,所以应该加一句纠正:Sub.prototype.constructor = Sub;
4 寄生继承--> 即将sub.prototype = new super改为sub.prototype = Object.creat(supper,prototype),避免了组合继承中构造函数调用了两次的弊端
可以描述new一个对象的详细过程, 手动实现一个new操作符
详细过程:
使用new关键字调用函数(new ClassA(...))的具体步骤
1、创建空对象
var obj = {}
2、设置新对象的constructor属性为构造函数的名称,设置新对象_ proto _属性指向构造函数的prototype对象
3、使用新对象调研函数、函数中的this被指向新实例对象:
ClassA.call(obj); //{}.构造函数()
4、将初始化完毕的新对象地址,保存到等号左边的变量中
/若构造函数中返回this或返回值都是基本类型(number、string、boolean、null、undefined)的值,则返回新实例对象,若返回值是引用类型的值,则实际返回值为这个引用类型/
var foo = "bar";
function test(){
this.foo = "foo"
}
new test(); //test中的this指新对象,并未改变全局的foo属性
console.log(this.foo); // "bar"
console.log(new test().foo); // "foo";
手动实现一个new操作符
第一种方法:
function new1(func){
// 创建一个继承自func.prototype的新对象
var newObj = Object.create(func.prototype);
//截取new1函数第二个以及第二个之后的参数,在newObject作用域内执行改造函数func
var returnObj = func.apply(newObj,Array.prototype.slice.call(arguments,1))
//如果传入参数中的构造函数执行后的returnObj是“对象”类型(比如new1(Object)),那么这个对象会取代newObj作为返回的对象
if ((typeof returnObj === "object" || typeof returnObj === "function") && ret !== null) {
return returnObj;
}
return newObj;
}
2、第二种方法:
function new2(func) {
return function() {
// 新生成一个对象,且新对象的原型对象继承自构造对象的原型对象
let newObj = {
_ proto _: func.prototype
}
// 以第二次执行函数的参数,在obj作用域中执行func
var returnObj = func.apply(obj, arguments)
//同理,returnObj是“对象”类型(比如new1(Object)),那么这个对象会取代newObj作为返回的对象
if ((typeof returnObj === "object" || typeof returnObj === "function") && returnObj !== null) {
return returnObj;
}
return newObj
}
}
理解es6 class构造以及继承的底层实现原理
class构造
Class本质上是一个特别的函数
Class在语法上更贴合面向对象的写法
Class 实现继承更加易懂、易理解
更易于写java等后端语言的使用
本质还是语法糖,使用prototype
// es5
function Fun(a,b){
this.a = a;
this.b = b;
}
Fun.prototype.showA = function(){
console.log(this.a)
}
var fun = new Fun(1,2)
fun.showA(); //1
// es6则引用了class的概念,使得更接近java、C++等语言。更加直观:
class Fun{
constructor(a,b){
this.a = a;
this.b = b;
}
showA(){
console.log(this.a);
}
}
var fun = new Fun(1,2);
fun.showA(); //1
两种写法一样,在es6中,class可以理解为一个语法糖,只是让这种写法更直观
es6中声明新的实例必须要用new。
其中constructor被添加到类里面。constructor方法默认返回的实例对象,即this。也可以返回其他对象,这时,新的实例instanceof当前的class就会报错
class继承的底层实现原理
1.类的实现
转换前:
class Parent(){
constructor(){
this.flag = a
}
filed2 = 2;
func1 = function(){}
}
转换后:
function _classCallCheck(instance,Constructor){
if(!(instance instanceof Constructor)){
throw new TypeError("Cannot call a class as a function")
}
}
var Parent = function Parent(a){
_classCallCheck(this,Parent);
this.filed2 =2
this.func1 = function(){};
this.filed1 = a;
}
解析:
1、调用了_classCallCheck方法判断当前函数调用前是否有new关键字。
构造函数执行前有new关键字,会在构造函数内部创建一个空对象,将构造函数的prototype指向这个空对象的 _ proto _,并将this指向这个空对象。
如上:_classCallCheck中:this instanceof Parent 返回true。
若构造函数前面没有new则构造函数的prototype不会出现在this的原型链上,返回false。
2、将class内部的变量和函数赋给this
3、执行constructor内部逻辑
4、return this(构造函数默认在最后做了)
继承实现
转换前:
class Child extends Parent {
constructor(a, b) {
super(a);
this.filed3 = b;
}
filed4 = 1;
func2 = function(){}
}
转换后
function _inherits(subClass, superClass) {
// (1) 校验父构造函数。
// (2) 典型的寄生继承:用父类构造函数的proptype创建一个空对象,并将这个对象指向子类构造函数的proptype。
// (3) 将父构造函数指向子构造函数的_proto_(这步是做什么的不太明确,感 觉没什么意义。)
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass){
Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}
}
function _possibleConstructorReturn(self, call) {
// 这里的Child.proto || Object.getPrototypeOf(Child)实际上是父构造函数(_inherits最后的操作),
// 然后通过call将其调用方改为当前this, 并传递参数。 (这里感觉可以直接用参数传过来的Parent)
// 校验this是否被初始化, super是否调用, 并返回父类已经赋值完的this。
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
}
var Child = function (_Parent) {
_inherits(Child, _Parent);
function Child(a, b) {
_classCallCheck(this, Child);
var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, a));
_this.filed4 = 1;
_this.func2 = function() {};
_this.filed3 = b;
return _this;
}
return Child;
}(Parent);
1、调用_inherits函数继承父类的prototype
2、用一个闭包保存父类引用,在闭包内部做之类构造逻辑
3、new检查
4、用当前this调用父类构造函数
5、将行之类class内部的变量函数和函数赋给this
6、执行子类constructor内部的逻辑
可见, es6实际上是为我们提供了一个“组合寄生继承”的简单写法。
网友评论