若是没有面向对象这种抽象概念的小伙伴,建议先看一下我写的
JS基础入门篇(三十四)—面向对象(一)👏👏👏👏程序员
想要如下为 f 添加一个say方法,有三种方法。segmentfault
<script> function Fn(){}; var f = new Fn(); </script>
方法一:至关于添加一个自定义属性,此属性是一个方法。
数组
<script> function Fn(){}; var f = new Fn(); f.say = function(){ console.log(1); } f.say(); //打印 1 </script>
方法二:为构造函数.prototype添加一个say方法。
app
<script> function Fn(){}; var f = new Fn(); Fn.prototype.say = function(){ console.log(2); } f.say(); //打印 2 </script>
方法三:为Object.prototype添加一个say方法。
函数
<script> function Fn(){}; var f = new Fn(); Object.prototype.say = function(){ console.log(3); } f.say(); //打印 3 </script>
疑问🤔️:
方法二中挂在构造函数的方法,和方法三中挂在Object的方法, f 为何能查找的到???post
解析(此解析必定要看懂,没有看懂多看几遍或者百度下):
是原型链的概念。就是js内部的查找机制。首先要明白:this
1.prototype 原型spa
当一个函数被申明的时候,该函数下默认有一个属性:prototype,该属性的值是一个对象。
举例说明:prototype
<script> function Fn(){} var f = new Fn(); console.log( Fn.prototype ); </script>
结果如图所示:3d
2.__proto__
当一个对象被建立的时候,该对象会自动被添加上一个属性:__proto__,他的值也是一个对象,而且该属性 就是 当前这个对象的构造函数的prototype
举例说明:
<script> function Fn(){} var f = new Fn(); console.log( f.__proto__ ); </script>
结果如图所示:
3.对象.__proto__ === 构造函数.prototype
举例说明
<script> function Fn(){}; Fn.prototype.say=function () { console.log(1); }; var f = new CreatePreson(); f.say=function () { console.log(2); }; console.log( f.__proto__ ); console.log( Fn.prototype ); console.log( Fn.prototype === f.__proto__ ); </script>
结果如图所示:
因此查找机制为:
调用f.say( );时
if( 对象 f上面是否say方法 ){//为真,执行if内部的代码 则调用f上面的say方法 }else if(Fn.prototype是否有say方法){//为真,执行else if内部的代码 第一步:f.__proto__ === Fn.prototype 由这个查找到f对应的构造函数的原型,即为 Fn.prototype。 第二步:查看Fn.prototype是否有say方法,有的话,则调用Fn.prototype是上面的say方法。 }else if( Object.prototype是否有say方法 ){ 第一步:Fn.prototype.__proto__ === Object.prototype 由这个查找到Fn.prototype对应的构造函数的原型,即为 Object.prototype。 第二步:Object.prototype是否有say方法,有的话,则调用Object.prototype是上面的say方法。 }else{//若是以上都没有say方法 会报错。 }
举例说明
<script> function Fn() { } var f = new Fn(); f.say = function () { console.log(1); }; Fn.prototype.say = function () { console.log(2); }; Object.prototype.say = function () { console.log(3); }; f.say();//打印1。由于在f上面找到了,就不会往下继续找了。 </script>
1.hasOwnPropert
🌹🌹🌹
做用 用来判断某个对象是否含有 指定的 自身属性 语法 boolean object.hasOwnProperty(prop) 参数 object 要检测的对象 prop 要检测的属性名称。 注意:不会沿着原型链查找属性,只查找自身属性
若是以上文字都看不懂,能够先看例子,再看文字。
<script> //建立构造函数 function CreatPerson(name, age) { this.name = name; this.age = age; } CreatPerson.prototype.kind = "人类"; CreatPerson.prototype.say = function () { console.log("我会说话 "); }; //生成对象,实例化的过程 var p = new CreatPerson("Lily",28); //调用hasOwnProperty方法,查看是不是自身的属性,再也不在原型链上面找。 console.log(p.hasOwnProperty("name"));//true console.log(p.hasOwnProperty("age"));//true console.log(p.hasOwnProperty("kind"));//false console.log(p.hasOwnProperty("say"));//false </script>
2.constructor
🌹🌹🌹
函数的原型prototype的值是一个对象,初始化会有一个属性为constructor, 对应的值为拥有这个原型的函数 注意:prototype的值是能够修改的,修改了prototype的值, 要手动将constructor指向函数
<script> function Fn() { console.log("构造函数"); } console.log(Fn.prototype.constructor); // Fn(){console.log("构造函数"); //由于arr 是经过字面量的方式生成一个数组,可是函数内部仍是会经过new Array 生成arr对象 //因此Array是arr的构造函数 //arr没有constructor,会根据原型链查找,找到JS内部的Array.prototype上的constructor方法。 //Array.prototype.constructor指向Array var arr = [1, 2, 3]; console.log(arr.constructor); // Array() { [native code] } //由于obj 是经过字面量的方式生成一个对象,可是函数内部仍是会经过new Object 生成obj对象 //因此Object是obj的构造函数 //obj没有constructor,会根据原型链查找,找到JS内部的Object.prototype上的constructor方法。 //Object.prototype.constructor指向Object var obj = {}; console.log(obj.constructor); //Object() { [native code] } </script>
3.instanceof
🌹🌹🌹
instanceof 是一个二元运算符,返回布尔值 运算检测 一个 对象原型 是否 在要检测的对象的原型链上 使用:object instanceof constructor
<script> var arr = []; console.log( typeof arr );//"object" console.log( arr instanceof Array);//true console.log( arr instanceof Object);//true //str是字面量生成的,是由JS内部的String构造函数new出来的。 //可是str会马上"压扁"本身,让本身不是对象。 //因此str都不是对象了,天然instanceof String 的获得的值为fasle //但str.indexOf(),str仍是能够调用indexOf()方法的缘由是,当它调用方法的时候,会从新将本身包装成对象。 //使用结束后会从新"压扁"本身,让本身不是对象。 var str = "123"; console.log( str instanceof Array );//false console.log( str instanceof String);//false console.log( str instanceof Object);//false var obj = {}; console.log( obj instanceof Array );//false console.log( obj instanceof Object);//true // Array.prototype -> Object.prototype </script>
1.谁调用就指向谁。
2.谁触发就指向谁。
举例说明1
<script> function fn() { console.log(this); } fn();//打印结果:Window 解析:至关于 window.fn(); 因此指向window document.onclick=fn;//打印结果:document 解析: 由document的触发,因此指向document </script>
举例说明2
<script> var obj={ n:"k", foo:function () { console.log(this); console.log(this.n); } }; obj.foo(); //运行结果为: //{n: "k", foo: ƒ} //k //解析:obj.foo();是obj调用foo对应的函数。因此this指向obj。 var b = obj.foo; b(); //运行结果为: //Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …} //undefined //解析:var b = obj.foo; ==== var b=function () { console.log(this); console.log(this.n);} //变量b是Window 的自定义属性,因此b(); === window.b(); //因此其中的this指向window,this.n === window.n //因为window上面没有n这个自定义属性,则打印出来为 undefined </script>
1.call
1. 函数**`会`**马上执行 2. 函数执行时候,函数**`第一个参数`**是内部的**`this指向`** 3. **`第一个参数以后的参数,都是指 函数执行时候 ,内部的实参`**
直接撸代码,举例说明
<script> function Fn() { console.log(this); } Fn.call(); //结果为:Window // 1.函数会当即执行 // 2.不传入任何参数,this的指向不变,仍是指向window。 Fn.call(document); //结果为:#document // 1.函数会当即执行 // 2.括号中的内容 第一个参数 就是 函数执行时候 ,内部的this指向 function Go(a,b) { console.log(this); console.log(a,b); } Go.call(document,2,3); //结果为: // #document // 2 3 // 1.函数会当即执行 // 2.括号中的内容 第一个参数 就是 函数执行时候 ,内部的this指向 // 3.第一个参数以后的参数,都是指 函数执行时候 ,内部的实参 </script>
2.bind
1. 函数**`不会`**马上执行 2. 函数执行时候,**函数第一个参数是内部的this指向** 3. **第一个参数以后的参数,都是指 函数执行时候 ,内部的实参** 4. **`返回的是 修改了 this指向的新函数`** 与call的区别就是函数不会马上执行。
举例说明
<script> function foo (a,b) { console.log( this ); console.log( a,b ); } var fn = foo.bind( document,2,3);// 函数 不会 马上执行,返回的是 修改了 this指向的新函数 fn();//调用以后才会执行 this指向的新函数 //运行结果: //#document //2 3 </script>
3.apply
与call很类似,只是第二个参数值接受数组
举例说明
<script> function foo (a,b) { console.log( this ); console.log( a,b ); } foo.apply( document,[2,3] ); // 和call 类似 直接调用 , 不过第二个参数接受数组 //运行结果: //#document //2 3 </script>
由于由typeof打印出来,数组和对象的结果都是object。有时候咱们须要判断变量是不是数组
。
方法一:
var arr = [1,2,3]; console.log( arr.toString() );//1,2,3 Array.prototype.toString = Object.prototype.toString;//从新赋值Array.prototype.toString的方法。可是下次在别的状况调用Array.prototype.toString,此方法已被从新覆盖。因此不太好 console.log( arr.toString() );//[object Array]
var arr = [1,2,3];
console.log( Object.prototype.toString.call(arr) );
// 使用 Object.prototype.toString
// 同时 修改内部的this指向 arr
console.log( arr );//[object Array]
方法二:
继承
在JavaScript中,继承就是让一个对象(子类)拥有另外一个对象(父类)的属性/方法(还有原型上的属性和方法)。其中原则就是:
1.子类的修改不能影响父类
2.子类能够 在 父类 基础上 添加本身的属性 和 方法
1.经过prototype 赋值 (行不通,可是仍是要看行不通的缘由)
举例说明:上代码
<script> function CreatPerson() {} CreatPerson.prototype.say = function () { console.log("我会说汉语"); }; function Coder(){} // 此处 子类 的 prototype和父类的 prototype 指的是 同一个对象。 // 的确是继承CreatPerson的原型上面的方法,可是当Coder.prototype重写say方法 // CreatPerson.prototype中的say方法也会被改写 Coder.prototype = CreatPerson.prototype; Coder.prototype.say=function () {// console.log("我会说汉语,还会码代码"); }; var person = new CreatPerson(); person.say(); var coder = new Coder(); coder.say(); </script>
2.原型链继承
子类的原型 = 父类的实例 注意 : 在为 子类 原型 赋值的时候去修正 constructor 弊端 : 子类构造函数内的地址的修改会修改其余子类。 由于全部子类构造函数的原型共享一个实例。
举例说明
<script> function CreatPerson() { this.age=18; this.arr=[1,2,3]; } CreatPerson.prototype.say=function () { console.log("我会说汉语"); }; CreatPerson.prototype.eat=function () { console.log("我想吃饭"); }; function Coder() {} Coder.prototype = new CreatPerson();//子类构造函数内的地址的修改会修改其余子类。由于全部子类构造函数的原型共享一个实例 Coder.prototype.constructor=Coder;//在为 子类 原型 赋值的时候去修正 constructor Coder.prototype.say=function () { console.log("我会说汉语,还会码代码"); }; var person=new CreatPerson(); var coder1=new Coder(); person.say();//我会说汉语 coder1.say();//我会说汉语,还会码代码 coder1.eat();//我想吃饭 //----------能够继承父类,修改子类也不会影响到父类。可是子类修改会影响到子类------------------- var coder2=new Coder(); coder2.age=10; //coder2.age -> Coder.prototype.age === new CreatPerson().age // 存储的是值,Coder.prototype.age的改变,只会影响当前对象的age // 别的子类影响不到 coder2.arr.push(4); //coder2.arr -> Coder.prototype.arr === new CreatPerson().arr // 存储的是地址,Coder.prototype.arr 修改,new CreatPerson().arr 取到的内容就是修改后的内容 console.log(coder1.age);//18 console.log(coder1.arr);//[1, 2, 3, 4] console.log(coder2.age);//10 console.log(coder2.arr);//[1, 2, 3, 4] console.log(person.age);//18 console.log(person.arr);//[1, 2, 3] </script>
对原型链继承遇到问题的解决的方案一:
改成:
function Coder() { this.arr=[1,2,3];//这样查找的时候,对象上面就有了,不会查找到上一层,既不会修改到。 }
解析:此方法麻烦,若是父类有不少自定义属性都是对象或者方法,那么子类都要从新复制一遍。
对原型链继承遇到问题的解决的方案二:
借用构造函数 在子类中执行父类的构造函数 修改子类构造函数中的 this指向 只能继承父类构造函数中的方法和属性 继承不到父类构造函数原型链中的方法和属性
改成:
function Coder() { CreatPerson.call(this);// // 此处的 this 指的 是 Coder 的 实例 }
总结:经过原型链继承的正确写法。
<script> //父类构造函数 function CreatPerson( name ) { this.age = 18; this.arr = [123]; this.name = name; } CreatPerson.prototype.say = function () { console.log("我会说汉语"); }; //子类构造函数 function Corder(name,job) { CreatPerson.call(this,name);//继承父类上非原型上的属性和方法。 this.job=job;//子类扩展的自定义属性 } Corder.prototype=new CreatPerson();//继承父类上原型上的属性和方法。 Coder.prototype.constructor=Coder;//在为 子类 原型 赋值的时候去修正 constructor Corder.prototype.say=function () {//重写父类上面的say方法,并不修改父类的say方法 console.log("我会说汉语,我是程序员!!!"); }; //父类的对象实例化 console.log("------------ 父类1 -----------"); var person=new CreatPerson("jack"); console.log(person.age);//18 console.log(person.name);//jack //子类1的对象实例化 console.log("------------ 子类1 -----------"); var corder1=new Corder("rose","worker"); corder1.arr=[234]; corder1.say();//我会说汉语,我是程序员!!! console.log(corder1.age);//18 console.log(corder1.arr);//[234] console.log(corder1.name);//rose console.log(corder1.job);//worker //子类2的对象实例化 console.log("------------ 子类2 -----------"); var corder2=new Corder("Mary","corder"); console.log(corder2.age);//18 console.log(corder2.arr);//[123]子类与子类之间的 自定义属性 没有受到影响 console.log(corder2.name);//Mary console.log(corder2.job);//corder corder2.say();//我会说汉语,我是程序员!!! console.log("------------ 父类1 -----------"); person.say();///我会说汉语 父类原型上面的方法 没有受到影响 console.log(person.arr);//[123] 父类自定义属性 没有受到影响 </script>
提醒本身: Coder.prototype.constructor=Coder;//在为 子类 原型 赋值的时候去修正 constructor。 不要忘记修正子类的constructor。
3.拷贝式继承
1. 完成拷贝式继承首先要知道如何拷贝对象。因此先来拷贝对象
<script> //拷贝对象的内容 function cloneFn( sourse ) { var obj= (Object.prototype.toString.call(sourse). indexOf("Array")!==-1)?[]:{};//若是对象是数组,就应该建立数组。若是是非数组的对象,就应该建立对象。 for(var attr in sourse){ if(typeof sourse[attr] ==="object"&& sourse[attr] !==null){//若是对象内的键值仍是对象,进行更深一步的拷贝 obj[attr]=cloneFn( sourse[attr] ); }else{ obj[attr]=sourse[attr]; } } return obj; } var a={ abc:1, abc2:2, arr:[1,23,4] }; var clone=cloneFn( a ); clone.abc=9; clone.arr.push(5);//不会影响a中的arr console.log(clone);//{abc: 9, abc2: 2, arr: Array(4)} console.log(a.abc);//1 console.log(a.arr);//[1, 23, 4] </script>
2.拷贝继承
<script> //拷贝对象的内容 function cloneFn( sourse ) { var obj= (Object.prototype.toString.call(sourse). indexOf("Array")!==-1)?[]:{};//若是对象是数组,就应该建立数组。若是是非数组的对象,就应该建立对象。 for(var attr in sourse){ if(typeof sourse[attr] ==="object"&& sourse[attr] !==null){//若是对象内的键值仍是对象,进行更深一步的拷贝 obj[attr]=cloneFn( sourse[attr] ); }else{ obj[attr]=sourse[attr]; } } return obj; } //父类构造函数 function CreatPerson( name ) { this.age = 18; this.arr = [123]; this.name = name; } CreatPerson.prototype.say = function () { console.log("我会说汉语"); }; //子类构造函数 function Corder(name,job) { CreatPerson.call(this,name);//继承父类上非原型上的属性和方法。 this.job=job;//子类扩展的自定义属性 } Corder.prototype=cloneFn(CreatPerson.prototype);//拷贝父类上原型上的属性和方法。 Coder.prototype.constructor=Coder;//在为 子类 原型 赋值的时候去修正 constructor Corder.prototype.say=function () {//重写父类上面的say方法,并不修改父类的say方法 console.log("我会说汉语,我是程序员!!!"); }; //父类的对象实例化 console.log("------------ 父类1 -----------"); var person=new CreatPerson("jack"); console.log(person.age);//18 console.log(person.name);//jack //子类1的对象实例化 console.log("------------ 子类1 -----------"); var corder1=new Corder("rose","worker"); corder1.arr=[234]; corder1.say();//我会说汉语,我是程序员!!! console.log(corder1.age);//18 console.log(corder1.arr);//[234] console.log(corder1.name);//rose console.log(corder1.job);//worker //子类2的对象实例化 console.log("------------ 子类2 -----------"); var corder2=new Corder("Mary","corder"); console.log(corder2.age);//18 console.log(corder2.arr);//[123]子类与子类之间的 自定义属性 没有受到影响 console.log(corder2.name);//Mary console.log(corder2.job);//corder corder2.say();//我会说汉语,我是程序员!!! console.log("------------ 父类1 -----------"); person.say();///我会说汉语 父类原型上面的方法 没有受到影响 console.log(person.arr);//[123] 父类自定义属性 没有受到影响 </script>