Function 原型 原型链 继承的实现
Function
每一个 function xxx(){} 都是一个 Function 对象
例如:
- 第一种
function add(v1, v2){return v1 + v2;}
- 第二种
let add = new Function("v1","v2","return v1+v2")
完全一致, 因此, add 指向一个函数对象
this问题
- 函数内部的this:
- 当调用函数前,会把调用对象的地址 0xXXXX 当成参数入栈 : push 0xXXX
- 函数内部的this 特指这个对象地址 0xXXXX
- 例如: o.call(), 当 调用 call 前 首先 push o,o 当成参数入栈,在call 函数中 this === o;
对原型以及原型链有信心的可拉到最下面,有小测试看看能否看懂
- Function.prototype 中添加的函数, 谁能调用
- Object.prototype 中添加的函数, 谁能调用
每个Function对象都有一个 prototype 属性 也有 proto, 下面函数对象中会说
-
- 函数对象.prototype 就是一个普通对象, 用于存放自定义函数
-
- 函数对象.__proto__ 不仅用于寻根(找祖宗), 也是继承
-
- 这部分在下面函数对象中会用代码演示 , 目前先别管
- 任意一个函数调用 , xxx() 相当于 xxx.call()/xxx.apply()
- 汇编指令不用看懂,能理解为什么函数内会有this即可
- 当一个函数被调用时 : call 函数 , 首先会把 调用对象当成参数传入函数
- 函数内 this 关键字 特指此 传入的对象, 因此能在此对象上添加属性
//不使用 严格模式var who = 'window';let obj = {who:'obj'};function callme(){console.log(this.who);}//也可写成 callme.call() / callme.call(window)callme() // 'window' , window.callme() (push window call callme)obj.callme = callme; // 相当于 callme.call(obj)obj.callme(); // "obj" ,(push obj call callme )callme.call(obj) // obj (push obj call callme)
构造函数
构造函数也是函数
//构造函数也是函数 , 既可以 haha() , 也可以 new hahafunction haha(name,age){this.name = name;this.age = age;// this.func = new Function("") 等价, 是否应该写在prototype 中?this.func = function(){console.log()}; }// 使用 new 操作符 来创建一个实例let o = new haha(1,2); // let o = this , 返回新创建的对象(实例)// 直接调用// window.name = 1, window.age = 2, window.func = ...haha(1,2); //使用call来调用//newobj.name = 1, newobj.age = 1let newobj = {};haha.call(newobj,1,1)
- haha 本身是指向一个函数对象, 或者把haha当成一个函数指针也行(不知道也没关系,只要知道 function === new Function)
- 现在使用 new 操作符 创建了一个实例 (一个对象)
- 只要使用 new 操作符, 就把 此函数当成一个构造函数来使用,就是一个初始化过程别想复杂了
- 不使用 new 操作符 , 直接调用 haha() 就是一个普通函数
- 构造函数也是一个函数,使用new:创建一个对象,直接调用:普通函数
- 使用new的过程:
-
- 申请内存, 为新实例创建空间
-
- 申请成功后, 0xXXX指向这块区域首地址(实例),调用haha ( push 0xXXX ,call haha )
-
- this 特指 0xXXX
-
- 执行函数内的代码
-
- 返回 this (默认情况构造函数里没有return , 那么就返回新对象; 如果有return 则返回return的内容)
-
- let o = this
原型prototype, prototype的来历
上面原来的函数
function haha(name,age){this.name = name;this.age = age;// this.func = new Function("") 等价this.func = function (){console.log()}; }let o1 = new haha(1,1);let o2 = new haha(2,2);// 有没有必要产生2个 函数 ?// o1.func === o2.func => false
对函数进行修改
function haha(name,age){this.name = name;this.age = age;//对上面的代码修改this.func = inner_func;}//全局作用域function inner_func(){ console.log(this.name) }let o1 = new haha(1,1);let o2 = new haha(2,2);// o1.func === o2.func true
- 如此便解决了一个问题 => 重复的函数
- 但是带来了一个新问题, 如果你有10个类似 haha的构造函数, 每个构造函数内有10个不同的函数
- 那么在全局作用域中 , 有100个函数
- 那么是否毫无封装性可言 ,全在全局作用域中, 无法把函数进行归类
- 因此有了原型, 主要为了解决 js 中函数分类以及继承的问题
每个Function都有一个 prototype 属性
// let haha = new Function("")// 见到 function XXX(){} => 立马想到是 new Function()function haha(){ }console.log(haha.prototype) //prototype也是一个对象/*里面有2个属性constructor : 看命名是 构造器的意思,先别管__proto__ : 每个JS对象都有那么个属性, 也先别管constructor: ƒ haha( ) __proto__: Object*/haha.prototype.func1 = function(){} haha.prototype.name = 'haha'let o1 = new haha();let o2 = new haha();// o1.func1 === o2.func2 true// o2.name === o1.name true
- 每一个Function 对象 / 函数 都有一个 prototype 的属性
- 由于 每一个函数都是一个 new Function
-
- 因此 函数名 = new Function
-
- 可通过 函数名.prototype 访问到其原型
-
- 原型也是一个对象, 只是比自己 (new Object() / {} ) 多了1个constructor属性
- prototype 能让 新的实例对象共享一个空间
- 所有通过 new haha 的新对象都能使用在 haha.prototype 定义的属性/函数
- 为什么新对象 (o1,o2) 能直接访问到原型中的属性/函数? 这涉及到原型链,查找属性和函数
- 查看下是否每一个js对象中都有一个属性叫 proto
prototype 是一个普通对象 , 一点都不神奇
prototype : 每个函数对象都有 , __proto__ : 每个对象都有
* 每一个新对象的__proto__ = 创建他的 函数对象.prototype
constructor 属性并非一种功能上的作用, 只是一种惯例,标识构造函数是谁
function haha( ){}haha.prototype.func1=function(){}haha.prototype.name = 'haha';console.log(haha.prototype)
/*{func1: ƒ () //自己增加的name: "haha" //自己增加的constructor: ƒ haha( ) // 默认就有的__proto__: Object // 默认就有的}haha.prototype 指向上面这个对象 , 这个原型对象中有刚刚赋值的 func1 ,name 属性, 如果不赋值,则只有2两个默认属性 constructor, __proto__
*//*true原型中的属性constructor:谁来初始化(实例化)对象的 , 是haha
*/console.log(haha.prototype.constructor == haha)/*
o1.__proto__ 指向 haha的原型对象
o1.__proto = haha.prototype
*/let o1 = new haha();//true , 每一个通过 new haha()实例化的对象, 其 __proto__ 被赋值为 haha.prototypeconsole.log(o1.__proto__ === haha.prototype) //有没有发现js中的每个对象都有个__proto__属性 console.log({})
- prototype (原型对象)也是一个对象, 默认只有 constructor, (proto 每个对象都有 )两个属性
- 原型中的 constructor 指向 haha ,意思是通过这个函数来 初始化(实例化) 对象的
- js中的每个对象都有一个__proto__属性
- 每个 new haha 的实例化对象 其 __proto__属性被赋值为haha.prototype
- __proto__属性是原型链的基础, 若对象本身没有的属性/函数从这个地方开始找(原型链中说明)
自己构建prototype
- prototype : 本身就是一个对象, 只是多增加了1个constructor属性,因此可构建自己的prototype
- constructor : 指向构造函数
function haha( ){}// haha.prototype = new Object()haha.prototype = {name:"haha",func1:function(){console.log(this.name)}}//默认情况下, 一个对象的constructor指向Object ( constructor : Object)console.log(haha.prototype.constructor) //ƒ Object() { [native code] }console.log(haha.prototype) //{name: "haha", func1: ƒ}// o1对象在创建时, o1.__proto__ = haha.prototypelet o1 = new haha()console.log(o1.__proto__ === haha.prototype) // true
添加constructor
haha.prototype = {constructor:haha, //添加一个constructor属性,让其指回haha构造函数name:"haha",func1:function(){console.log(this.name)}}
- 如此就跟默认情况下的原型没区别了
Function 函数对象
- 根据上面所有的例子可知 prototype 一点都不神秘,就一个普通对象罢了
-
- 一个函数被构建时相当于 new Function, 同时此函数.prototype = {constructor:构造函数}
-
- 此函数对象的.__proto__ = Function.prototype
- 函数对象既有 prototype 也有 proto
/*可知, function A 实际就是let A = new Function()A.prototype : { constructor : A,__proto__ : Object}那么 A.__proto__ 是什么 ?根据上面可知 : 一个实例对象在构造函数中 this.__proto__ = Function.prototype => A.__proto__ = Function.prototype
*/function A(){}// trueconsole.log(A.__proto__ === Function.prototype) //ƒ Function() { [native code] }console.log(A.__proto__.constructor)
Function.__proto__ Object.__proto__
Function 由谁创建, Object 由谁创建
/*在创建一个函数实例对象时, this.__proto__ = Function.prototype那么Function.__proto__ 是什么 ?Function 由谁创建的 ?function Function(){}function Object(){} 由谁创建*///true 说明 Object 是 Function原型 创造的console.log(Object.__proto__ === Function.prototype)//true 说明 Function 由自己的原型创造console.log(Function.__proto__ === Function.prototype)// true Function原型 由 Object原型 创建console.log(Function.prototype.__proto__ === Object.prototype)
- Function 由自己原型创建, Object 也由 Function的原型 创造
- 因此 Function 为环形结构, Function.prototype是所有函数对象的老祖宗
- Function.prototype 又由 Object.prototype 所创建
- Function → Function.prototype → Object.prototype → null
-
Object.prototype 一切的祖宗,有物混成先天地生
总结: 每一个函数对象在创建时有2个属性
prototype : 创建一个对象, 给你自定义函数使用
__proto__ : 寻根之旅
function A(){}//老祖宗let object_prototype = Object.prototype;// 由老祖宗生成 , 用于生成函数对象的老祖宗let function_prototype = Function.prototype;/*当创建一个函数对象时, 此处为A先在Function的构造函数中告诉这个函数对象 谁是你祖宗A.__proto__ = Function.prototype 为A构建一个原型对象,用于存放自定义函数此对象的 __proto__ 指向老祖宗A.prototype = {constructor:A};*/console.log(A.prototype.__proto__ === object_prototype) //trueconsole.log(A.__proto__ === Function.prototype) //true
原型链(继承的实现)
- 继承的目的是什么 ? 省的自己写了, 用别人写好的
- 如果想用别人已经实现好的函数,自己不想实现怎么办 ?
- 原型本身就是一个对象, 让自己的原型指向别人已经实现好的原型是否可行
- 当调用的函数/属性在自身对象中找不到时 , 到__proto__中查找
- (重复一次,别嫌啰嗦)根据上面对于原型的例子
-
- 一个函数对象的原型对象就是一普通对象,就是为了让你自定义函数使用的
/* 回顾:看过上面例子的,应该知道生成一个函数对象的流程如下let A = new Function("");A.__proto__ = Function.prototype;A.prototype = {constructor:A};let o = new A() 会发生的事:o.__proto__ = A.prototype*/function A(){}A.prototype.a_func = function(){ console.log('A')}// 想用 A 中的函数, 直接修改 prototypefunction B(){}B.prototype = new A() // 相当于继承了 A /*let b = new B() 在创建对象时: b.__proto__ = B.prototypeb对象中没有a_func函数, 到b.__proto__中查找b.__proto__ 是一个A的实例对象(new A()), 也没有a_func, 继续到b.__proto__.__proto__找可知 b.__proto__.__proto__ === A.prototype , 因此找到*/let b = new B(); b.a_func(); // 也可以这样B.prototype = A.prototype;b.a_func()
B.prototype = new A(); //方式一B.prototype = A.prototype; //方式二/*两种方式的区别是什么 ? 方式1 : 由于是 new A(),那么 B.prototype.b_func = ..B.prototype.c_func = ..可以随意添加 , 而不会影响到 A.prototype方式2 : 由于两个prototype都指向同一个 原型若 B.prototype.b_func = .. 则 A.prototype 也会多出一个 b_func */
再次注意: 一般情况下使用 B.prototype = new A() 为的是不影响 A.prototype , 在 new A()对象中可添加任意函数let c = new B() let d = new B()所有 B的实例对象.__proto__ 都将被赋予 B.prototypec.__proto__ = B.prototype;d.__proto__ = B.prototype;可能有仔细的人发现了 , 之前不是有个 constructor 属性吗一般来说不是 B.prototype.constructor === B 吗如果你觉得很重要那么可以添加:B.prototype = new A() ; B.prototype.constructor = B;
JS中所有引用类型的对象都继承了Object(重复的部分,上面都看过的可省略)
- Object.prototype(Object的原型对象) 到底了,Object.prototype.__proto__ === null
- 一切对象寻找到根上就是 Object.prototype
let o = {}// true console.log(o.__proto__ === Object.prototype)//toString() 函数 在这里console.log( o.__proto__.toString() )
function A(){}let a = new A()let o = {};// A.prototype === a.__proto__// trueconsole.log(a.__proto__.__proto__ === o.__proto__) //trueconsole.log(a.__proto__.__proto__ === Object.prototype) /*a.__proto__ => A的原型对象根据前面所说, 原型对象就是一个普通的Object对象 :new Object() 额外多了1个属性constructor而每一个对象都有一个 __proto__ 属性 由创建时被 构造函数赋值:a.__proto__ = A.prototypea.__proto__.__proto__ => 最终的Object原型对象*/
逐层递进关系
函数对象 | 原型 | 最终:Object.prototype |
---|---|---|
A | A.prototype | A.prototype.__proto__ |
同名函数重写 (不能说重写,而是在原型链的前端找到以此命名的函数)
function A(){}A.prototype.func_a = function(){console.log('AAAAA')}function B(){}// 继承A , 注意使用new A(). 如果B.prototype = A.prototype,会发生什么?B.prototype = new A(); //新添加B.prototype.func_b = function(){console.log('func_b')}//重写A中的同名函数B.prototype.func_a = function(){ console.log("重写了")}let b = new B()/*首先在b自身对象中找,再去b.__proto__中找, 正好能找到*/b.func_a() //"重写了"
//修改上面代码B.prototype = A.prototype;// 那么这里将 覆盖 A.prototype.func_aB.prototype.func_a = function(){ console.log("重写了")}
继承问题, 别的语言中有父类 - 子类, 即 new 子类构造函数 将调用 父类构造
- 用js模拟此行为
//父类function A( arg ){this.arg = arg}A.prototype.a_func = function(){ console.log('A')}//子类function B(name, age , arg){// 使用A.call(), 并把this传递进去// 这里 this 是 new B() 实例对象的引用A.call(this,arg) this.name = name;this.age = age;}let b = new B(1,1,2);console.log(b)/*age: 1arg: 2name: 1*/
- 注意, A() 就是一个普通函数调用
- 使用A.call(this),会把新对象的地址(引用)传递进去
接下来,组合上面已经完成的问题 : new 子类调用父类, 同时修改子类的原型以达到 真正的继承
function A( arg ){this.arg = arg}A.prototype.a_func1 = function(){ console.log('A')}function B(name, age , arg){A.call(this,arg) //注意别直接使用A()this.name = name;this.age = age;}B.prototype = new A(); //修改原型B.prototype.constructor = Blet b = new B(1,2,3);b.a_func1()
- 注意, 以上 A 的构造函数被调用了两次
-
- 第一次是 B.prototype = new A();
-
- 第二次是 let b = new B(1,2,3); B的构造函数 A.call(this,arg);
最终的继承解决方式
- 上面, A构造函数调用了两次 ,并且由于B.prototype = new A(), 原型中多了一个无效的属性
- 如果父类的构造函数中属性越多, 意味着B.prototype中的无效属性越多
- 那么只要减少构造函数的调用即可, 毕竟我们只要一个 对象 并且此对象.__proto__指向A原型
//创建一个 新对象, 设置 __proto__ = 父类的 prototype//如此 与 new A() 一模一样function copy_prototype(superType){let o = {};o.__proto__ = superType.prototype;return o} function A( arg ){this.arg = arg}A.prototype.a_func1 = function(){ console.log('A')}function B(name, age , arg){A.call(this,arg) //注意别直接使用A()this.name = name;this.age = age;}B.prototype = copy_prototype(A) // 修改的地方B.prototype.constructor = B
- 现实情况就是非常残酷, __proto__ 属性可能因为各种兼容性原因而无法直接使用
- 例如在一些浏览器中 __proto__ 被改成其他名字
- 因此有了版本2
/* 版本2放弃直接使用 __proto__ 改成 Function 对象当 new O() 时 , O.__proto__ = O.prototype如此 可保证兼容性问题*/function copy_prototype(superType){function O(){}O.prototype = superType.prototype;return new O();}
原型链最后的重点, 检测看看自己是否真的明白了
- Function.prototype 中添加的函数, 谁能调用
- Object.prototype 中添加的函数, 谁能调用
function A(){}Function.prototype.test_func = function(){ console.log('test_func')}Object.prototype.test_object = function(){{ console.log('test_object')}}A.test_func(); //可调用A.test_object(); //可调用let a = new A();a.test_object(); //可调用a.test_func(); //不可调用 为什么?/*A.test_func() 为什么能调用 ?a.test_func() 为什么不能调用 ?A的创建流程:let A = new Function("");A.__proto__ = Function.prototype;A.prototype = {constructor:A};因此, A能通过 __proto__ 找到 Function.prototype 中的test_func由于 Function.prototype.__proto__ === Object.prototype (上面演示过)因此 A也能找到Object.prototype中的test_object
-------------------------------------------------------------a的创建流程 new A():a.__proto__ = A.prototype ( 即{constructor:A, __proto__:Object} )由于 a.__proto__ 在整个原型链中 与 Function.prototype 无关只能找到 Object.prototype 因此, 能找到 test_object*/
instanceof 的实现
- 根据上面可知, __proto__是寻根的关键
- 实现过程
-
- 在构造函数中 this.__proto__ = 构造函数对象.prototype
-
- 例如 b.__proto__ = B.prototype
-
- 因此可根据 b.__proto__ 是否与某个原型相等来判断
function check_instance(child , parent) {//获取父类的原型对象let parent_prototype = parent.prototype;// 子类原型对象let child_proto = child.__proto__;// 检查两个对象是否相等 , 若相等说明子类是通过此父类构造的// 若不相等, 再去获取 __proto__ 来判断while(child_proto != null){if( child_proto === parent_prototype )return true;// 接着往原型链的下一个找child_proto = child_proto.__proto__; }return false;}
添加一个计数器, 看看找了几层才找到
function A(){}function B(){}B.prototype = new A(); // B 继承 Alet b = new B();/*[0, true][1, true]*/console.log(check_instance(b, B))console.log(check_instance(b, A))function check_instance(child , parent) {let count = 0; //一个计数器 , 记录找了几次let parent_prototype = parent.prototype;let child_proto = child.__proto__;while(child_proto != null){if( child_proto === parent_prototype )return [count,true];child_proto = child_proto.__proto__;++count;}return [count,false];}
原文地址:https://blog.csdn.net/dashoumeixi/article/details/146273977
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mrgr.cn/news/94754.html 如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mrgr.cn/news/94754.html 如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!