如果没有面向对象这种抽象概念的小伙伴,建议先看一下我写的
JS基础入门篇(三十四)—面向对象(一)????
1.非常非常重要而又简单的概念—原型链
想要以下为 f 添加一个say方法,有三种方法。
<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方法。
<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 为什么能查找的到???
解析(此解析一定要看懂,没有看懂多看几遍或者百度下):
是原型链的概念。就是js内部的查找机制。首先要明白:
1.prototype 原型
当一个函数被申明的时候,该函数下默认有一个属性:prototype,该属性的值是一个对象。
举例说明:
<script>
function Fn(){}
var f = new Fn();
console.log( Fn.prototype );
</script>
结果如图所示:
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>
2.hasOwnProperty, constructor, instanceof
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>
3.this的指向
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>
4.修改this指向的三种方式
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>
5.数组的检测
因为由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]
方法二:
6.继承
继承
在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>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。