Js 面向对象摘要

前序热身

一、js中的数据类型

原始类型

a、 原始类型的变量直接保存原始值,(而不是一个指向对象的指针);

var a =1 
var b =a
说明:两者的存储地址是不一样的,a变量保存的是值1,b变量保存的也是值1,a的值改变了并不会影响到b;

b、如何鉴别原始类型(typeof 是小写)

  console.log(typeof "a"); //"string"
  console.log(typeof 10); //"number"
  console.log(typeof 7.2);//"number"
  console.log(typeof true);//"boolean"
  console.log(typeof undefined);//"undefined"
  console.log(typeof null); //"object"  
引用类型

它其实就是指的是js中的对象,对象是属性的无序列表,属性包含键和值;

  • 鉴别引用类型:(instanceof)
var list =[];            
var object ={};
function obj (val){
    return val
}
 
console.log(list instanceof Array)             //true
console.log(list instanceof Object)            //true
console.log(object instanceof Object)          //true
console.log(Object instanceof Array)           //false
console.log(obj instanceof Function)           //true
console.log(obj instanceof Object )            //true

总结:
	1、每一种引用类型的对象(数组、对象等)都被正确鉴别为Object的实例
    2、如果对象之间存在原型链方式继承关系,比如,对象A继承对象B,那么对象A instanceOf 对象B也会返回true,只要之间存在原型链关联就为true;
  • 鉴别数组:Array.isArray(…)
var arr =[];
console.log(Array.isArray(arr))     //true

总结:虽然instanceof()也可以鉴别数组,但是在不同页面之间传递数组时就会出现问题,因为每个页面有自己上下文,当你把一个数组从一个框架传到另一个框夹时,instanceof就无法识别它,所以此时鉴别数组最好用Array.isArray()

二、对象

1、创建对象
  • 用new 构造函数的方式创建对象(构造函数就是通过new操作符来创建对象的函数,所有函数都可以是构造函数,一般用首字母大写来区别其余函数)
var object = new Object();  //因为引用类型步在变量中直接保存对象,而是保存一个指向内存中实际对象的地址值
。。。。do something

object.name ="haha"     //可以随时给对象添加属性name

object =null       //如果这个变量后面不要用了,最好可以将其置为null,让对象引用解除,如果不写也没大事,因为js有垃圾回收机制
  • 对象字面形式创建对象
var book ={
    name:"三国演义",
    year:2013
}

以上这种字面形式定义对象,其实和const book = new Object(); book.name="三国演义"; book.year=2013是一样的;这是js引擎背后做的事情
2、理解对象
  • 定义属性
    当一个属性第一次被添加给对象时,js为在对象上调用一个叫 [[put]] 的方法,在对象上创建一个新节点来保存属性,调用[[put]]方法时在对象上创建一个自有属性,自有属性表明仅该指定的对象实例才拥有该属性;
  • 属性探测
    最好不要用if(Person.name)这种方式来判断对象是否存在该属性,因为当该属性值为null、underfined、0、false、NaN或者空字符时会返回false,但其实际上属性是存在的,所以最好用in标识符来检测某对象中是否含有该属性,如果含有则返回true,它可以检测任何属性(自有属性和原型属性),只要是对象中能访问的属性,in表示符都会返回true,于是就有一个问题?自由属性和原型属性该怎么判断呢?这时就要用到一个hsOwnProperty属性,每个对象都有该属性(方法),该属性只会是自有属性才会返回true,对于原型属性返回false;
例: var Person={
    name:'haha',
    age:0,
    work:'',
    a:null,
  };
  console.log("name" in Person);       //true
  console.log("age" in Person);        //true
  console.log("work" in Person);       //true
  console.log("a" in Person);          //true
  console.log("c" in Person);          //false
  console.log("toString" in Person);    //true
  console.log(Person.hasOwnProperty("toString"))   //false,因为toString方法是Object原型中的方法,                                                       不是该对象的自有属性(方法),所以返回false
  • 属性删除
    用的了delete操作符进行直接删除,如delete Person.name; js内部可以理解为调用[[delete]]方法;而且要记住,delete操作符只能删除对象自有属性,不能删除原型属性;
  • 读取对象属性过程
    1、首先会在该对象的自有属性中寻找,如果找到则返回;
    2、如果没有,则会通过对象中的—proto—属性所对应的那个对象(该对象的构造函数的原型对象)中去找,如果有则返回,如果没有则继续按原型链往上级寻找,直至到Object.prototype为止,如果还是没有则返回undefined;

三、函数

函数其实也是对象,只不过函数区别于其他对象的一点就是它内部有一个内部属性叫[[call]],该属性是函数独有的,表明该对象可以被执行,

typeof操作符会对任何具有[[call]]属性的对象返回function,所以函数的类型返回就是function;

函数有2种字面形式(函数声明和函数表达式)

  • 函数声明:
function add(a,b){
    return a+b;
}
  • 函数表达式:
const add =function(a,b){
    return a+b;
}

注意:函数声明会被js引擎提升至当前上下文(执行环境)的顶部,这个上下文有可能是该函数被声明时所在的函数或者是全局范围,这就意味着你可以提前使用该函数,如下:
const result =add(1,2); //3,如果下面用函数表达式的就会报错
function add(a,b){
return a+b;
}

  • 函数的参数问题

1、arguments:它是一个不是数组,但类似于数组的对象,函数的实参都会传入保存在它里面,所以它的内部值就是调用函数时所传入的实参值;每个实参都用索引值对应存储,就和数组的形式一样,它一般在函数内部使用;

2、length:它表明了此时你定义的函数所期望的参数个数,说白了就是你定义的函数它()中所填写的形参的个数,函数声明形式的length=0,具体以上两个属性的举例见下:

<script>
  function add(){
    return arguments[2];                         //函数返回传入的第3个实参值
  }
  console.log(add(1, 2, 3,56565),'??????');       //调用add函数,返回值为第三个实参值3
  console.log(add.length,'>>>>>>');                //此时add为函数声明形式,没传形参,所以此时													//长度为0
</script>
  • 重载
    js语言本身没有重载,如果你定义了两个名字相同的函数,后面的会覆盖掉前面的,这样就意味着同一个函数只能做相同的一件事,但是你可以通过arguments属性来内部做逻辑处理,虚假地搞成重载的样子(同一个函数,输出不一样的内容,当两个不同函数使,利用的就是arguments属性),举个例子:
old:

function sayMessage(message){                //定义一个方法,输出传入的内容
    console.log(message);
}

now:

function sayMessage(message){                //还是原先的方法,只不过通过arguments属性做了入参											格式的逻辑判断处理,如果啥也不传,则输出默认信息,如											果有入参,则输出传入的信息
	if(arguments.length===0){
        message= "Default message"
	}
    console.log(message);
}
  • this对象:每个函数作用域中都存在一个this对象,它代表当前调用该函数的对象;
  • 改变this指向(call()、bind()、apply())

1、call():

A.Call(B,x,y)   //改变函数A的this指向,将其指向B对象,后面参数x,y也都是需要传入A函数的参数

2、apply():

A.apply(B,x,y)   //用法和call完全一样,只不过是入参有一点不一样,接收两个参数,第一个同样是指向当前调用函数内部的this的对象,第二个是一个数组或者一个类似于数组的对象,其内部就是还是那些需要被传入函数的参数,它和call不同的是call都是分散着传入,而他是包在一个数组或伪数组中;

3、bind():

bind()方法会创建一个新的函数,称为绑定函数 ,该方法可传入两个参数,第一个参数作为this,第二个及以后的参数则作为函数的参数调用 

例子1:
var test=function(){
    console.log(this.x);
}
var o={
    x:1
}
test();  //undefined
test.bind(o)(); //1

第一次调用this指向global,第二次test.bind(o)() 这个this指向o,通过观察可以知道test.bind(o)此时是一个函数,说明bind返回的是一个函数。

例子2:
 const a = {
    b:function(){
      var func =function(){
        console.log(this.c)
      }.bind(this);     //将func函数内部的this绑定给()中的这个this,这个this是在b函数中的,那它又指向调用b函数的那个对象,也就是a对象,所以func函数的this最终指向是a对象,所以最终结果是haha
      func()
    },
    c:"haha"
  };
  a.b();    //"haha"
  
例子3:
const a = {
    b:function(){
      var func =function(){
        console.log(this.c)
      };
      func.bind(this)();   //将func函数中的this指向绑定给this,这个this是在函数b中的,也同样指向调用函数b的对象(a)
    },
    c:"haha"
  };
  a.b()   //“haha”

四、构造函数和原型对象

1、每一个对象在被创建时,都会用一个构造器属性(constructor),指向其构造函数的引用,如果是通过构造函数方式创建的对象,则constructor指向创建它的构造函数,如果是Object构造函数创建或者对象字面量形式创建的对象,该属性指向Object;原型对象的contructor属性也指向其构造函数

 const Person =function(name){
    this.name=name;
  };
  const person1 = new Person() ;  //通过构造函数Person创建一个对象person1
  const person2 ={                   //用对象字面量形式创建一个对象person2
    name:"123"
  };

  console.log(person1.constructor ===Person)    //true
  console.log(person2.constructor ===Object)     //true

2、 对象实例与构造函数之间通过原型对象连接:
在这里插入图片描述

图解:构造函数中的prototype属性指向其原型对象(Person.prototype),原型对象中的constructor属性指向其构造函数(Person),实例对象中的[[prototype]]属性(_ proto_ )指向其构造函数的原型对象,实例对象能共享器构造函数的原型对象中的所有属性(主要就是一些公用的方法)

3、new 构造函数时内部原理

  • new的时候,会立即去执行构造函数的代码,然后在内存中开辟一块新的空间(所以每个实例对象占用的空间是不一样的)
  • 在构造函数中创建一个空对象(相当于var kong ={})
  • 把构造函数内部的this指向这个空对象(内部相当于this=kong,构造函数中例如this.age=age等语句相当于给new出来的实例对象添加属性或方法)
  • 把空对象的内部原型(属性)指向构造函数的原型对象(kong. _ proto_ ==指向构造函数的prototype),这就同等于如果构造函数没有返回值,则new出来的实例对象内部的proto属性就指向了构造函数的原型对象;
  • 原型对象中有一个constructor属性回指向构造函数;
  • 当构造函数执行完成后,如果有返回值则返回那个return的对象给实例,如果没有,则把当前的空对象返回给实例对象(也就是构造函数内部的this对象就可以理解为有一句默认的影藏代码就是:return this)
    示意图:
    在这里插入图片描述

4、–proto-- 和prototype有什么区别

_proto_是任何对象都有的内部原型属性;而prototype只有函数才有的属性,该属性是在函数创建的时候产生的;

5、其他知识点:

一般在构造函数中添加属性,在原型对象中添加方法,如果有一些常量或者公共的属性也可以设置在原型对象中,这样有助于减少内存,但是说回来还是最好不要设置属性;

原型对象上的属性只能通过原型自己去设置,即通过Person.prototype.name=123,如果实例对象中设置与原型对象中相同的属性,那么他会自动为这个实例对象设置一个属性,不会去改变原型中的值;

6、函数的4种调用模式
①函数执行模式
在这里插入图片描述
②对象调用模式
在这里插入图片描述
3、构造器调用模式
在这里插入图片描述
4、call和apply调用模式
在这里插入图片描述

五、继承

1、前言:在其他语言中,继承是通过类来实现的,然而在js中没有类的概念,所以它的继承是发生在没有类继承关系的对象之间,这种继承机制叫做原型对象链,又称原型对象继承;

2、什么叫原型对象链:对象实例继承其原型对象中的属性方法,原型对象继承它的原型对象的属性方法,一次类推就是原型对象链;所以所有对象都继承Object.prototype

3、Object.prototype中的方法

  • hasOwnProperty()检查对象是否存在一个给定名字的自有属性
  • propertyIsEnumerable()检查一个自有属性是否可枚举
  • isPrototypeOf()检查一个对象是否是另一个对象的原型对象
  • valueOf()返回一个对象的值表达(每当一个操作符用于对象上时,此时对象就会调用该方法)
  • toString()返回一个对象的字符串表达,js引擎在大部分情况下默认返回[object,Object],如果你对该返回值不满意,可以在对象中自定义重写toString方法

4、常见的几种继承方式:

  • 对象继承

    ①、自定义的字面量形式对象,会默认继承Object.prototype;这是隐式继承;

    ②、但是你也可以显式地继承,通过用Object.create(),该方法接收两个参数,第一个是你要该创建的新对象继承的原型对象,第二个是这个新对象的一些原生属性表达;

    理解

    说白了什么叫对象继承,const A =Object.create(B),就是说将一个对象A的_proto_属性设置为另一个对象B,这样对象A就相当于是某个构造函数创建出来的实例对象,对象B就相当与是某个构造函数的原型对象,对象A就继承了对象B中的属性和方法,这样就相当于实现了继承,这种继承就叫做对象继承;那这是通过什么来实现的呢,就是通过Object.create()方法;

    var person1 ={                                           //定义一个对象person1
     	name:"haha",
    	sayName:function(){
        	...
    	}
    }
    
    var person2 =Object.create(person1,{                  //通过该方法将person2的原型对象设置为person1即(person2._proto_指向person1)					  
    	name:{                                            //在继承person1的同时还定义了一个自己的属性name,值为Greg                                                            			
        	configurable:true,
        	enumerable:true,
        	value:Greg,
        	writable:true
    	}
    })
    person1.sayName();               //"haha"
    person2.sayName();               // "Greg"                  //自己有name属性就用自己的
    console.log(person1.hsaOwnProperty('sayName'))              //true
    console.log(person1.isPrototypeOf(person2))                 //true
    console.log(person2.hsaOwnProperty('sayName'))              //false,这个方法不是person2的                                                                   自有属性,是继承person1的,所以为false
    
  • 类式继承

    function A (name){
    	this.name=name;
    	this.list= [1,2,3]
    }
    
    A.prototype.getName=function(){
    	console.log(this.name)
    }
    
    function SubA (name){
    	this.subName = 'sub' + this.name
    }
    
    SubA.prototype = new A();
    const sa1 = new SubA('sa1')
    console.log(sa1.name,sa1.list) //undefined [1,2,3]
    

    代码解析:首先执行的是new A(),这个上面讲过了会创建一个新对象,然后执行A构造,将A构造函数中的this指向它,所以这个新对象中就有name属性和list属性{name:undefined,list:[1,2,3]}这里name为undefined是因为在new的时候没传name值,然后该对象的_proto_属性指向A.prototype,所以此时该对象中也同时拥有了getName方法,{name:undefined,list:[1,2,3],getName:function(){this.name}},然后将这个对象赋值给了SubA.prototype,所以SubA.prototype ={name:undefined,list:[1,2,3],getName:function(){this.name}},然后代码继续执行,执行new SubA(‘sa1’),此时会执行SubA构造函数,同样又会创建一个空对象obj2,该对象拥有subName属性,然后该对象obj2.proto_又同样会指向SubA构造函数的prototype,所以该对象obj2={ subName:‘sub sa1’, _ proto :{name:undefined,list:[1,2,3],getName:function(){this.name}}},执行结束后,返回该对象obj2,赋值给了实例对象sa1,所以sa1= { subName:‘sub sa1’, _ proto _:{name:undefined,list:[1,2,3],getName:function(){this.name}}},所以此时打印sa1.name,sa1.list就是undefined [1,2,3]

    类式继承的缺点:这种方法不支持父构造函数带参数,还有就是父构造函数中属性和方法都会变成共有属性,可能原先设计的父构造函数中的属性和方法并不想共有,所以这种方式有些不太合适

  • 构造函数继承

    function A (name){
    	this.name=name;
    	this.list= [1,2,3]
    }
    
    A.prototype.getName=function(){
    	console.log(this.name)
    }
    
    function SubA (name){
    	A.call(this,name);
    	this.subName = 'sub' + this.name;
    }
    const sa1 = new SubA('sa1')
    console.log(sa1.name,sa1.subName) //sa1 subsa1
    console.log(sa1.getName()) //sa1.getName is not a function
    

    代码解析 :首先执行new SubA(‘sa1’),它会去执行SubA构造函数,构造函数内部执行了A.call(this,name);又会去执行A构造函数,它A构造函数中的this指向SubA构造函数中创建出来的对象objSubA,所以此时objSubA={name:“sa1”,list:[1,2,3]},执行完A构造函数后,继续执行this.subName = ‘sub’ + this.name;往objSubA对象上继续赋对象,所以此时objSubA={name:“sa1”,list:[1,2,3],subName:‘sub sa1’},然后执行完SubA构造函数后,将objSubA赋值给了sa1实例对象,所以此时打印name和subName分别为sa1 和subsa1,由于该对象上没有getName方法,所以执行sa1.getName()会报错

    构造函数继承问题:它不能继承父构造函数原型对象上的方法和属性

  • 组合继承

    function A (name){
    	this.name=name;
    	this.list= [1,2,3]
    }
    
    A.prototype.getName=function(){
    	console.log(this.name)
    }
    
    function SubA (name){
    	A.call(this,name);
    	this.subName = 'sub' + this.name;
    }
    SubA.prototype = new A();
    
    const sa1 = new SubA('sa1')
    console.log(sa1.name,sa1.subName) //sa1,sub sa1
    console.log(sa1.getName()) //sa1
    

    代码解析:这种方式其实就是结合了类式继承和构造函数继承,首先执行SubA.prototype = new A();此时会执行A构造函数,创建一个对象objA, _ proto_属性指向A.prototype,所以该对象就有了name:undefined和list:[1,2,3],_ proto_:{getName:function(){console.log(this.name)}} 然后将该对象赋值给了SubA.prototype,然后执行new SubA(‘sa1’),同样的方式执行SubA构造函数,创建一个新的对象objSubA,然后执行A.call(this,name);将name和list属性赋值在了objSubA对象上,此时objSubA={ name: ‘sa1’,list:[1,2,3]},然后继续执行this.subName = ‘sub’ + this.name;,此时objSubA={ name: ‘sa1’,list:[1,2,3]’‘,subName:’sub sa1‘}然后objSubA对象内部属性_proto_ 同样会指向SubA.prototype,所以此时sa1=objSubA={ name: ‘sa1’,list:[1,2,3]’‘,subName:’sub sa1‘ ,_ proto_:name:undefined和list:[1,2,3],_ proto_:{getName:function(){console.log(this.name)}} },所以此时打印sa1.name和subName,由于sa1实例对象本身有name和subName,所以就输出sa1和sub sa1,getName()自身没有,要通过_ proto_属性向上找,最后输出sa1

    组合继承的问题:这种方式也是有两个小问题,第一就是_proto_中的属性没有被用到,第二就是父构造函数被调用了2次

  • 寄生式组合继承

    	function A (name){
    	this.name=name;
    	this.list= [1,2,3]
    }
    
    A.prototype.getName=function(){
    	console.log(this.name)
    }
    
    function SubA (name){
    	A.call(this,name);
    	this.subName = 'sub' + this.name;
    }
    // SubA.prototype = new A();要改善的就是这一句,这一句太过于强硬
    function inheritPrototype(subClass,superClass){
    	function F(){}
    	F.prototype=superClass.prototype;
    	subClass.prototype= new F();
    }
    inheritPrototype(SubA,A);
    
    const sa1 = new SubA('sa1')
    console.log(sa1.name,sa1.subName) //sa1,sub sa1
    console.log(sa1.getName()) //sa1
    

    代码解析:执行inheritPrototype(),内部创建一个F函数,F.prototype = A.prototype, 然后执行new F(),又创建了一对象,其_proto_ =F.prototype,所以该对象为{proto:A.prototype},然后将该对象赋值给了SubA.prototype,然后执行new SubA(‘sa1’),又创建了一个新对象objSubA,然后该对象就拥有了name,subName,list属性,然后该对象的_proto_指向SubA.prototype,所以最后sa1实例对象最后={name:“sa1”,subName:“sub sa1”,list:[1,2,3],proto :{proto:A.prototype}}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Ronychen’s blog

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

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

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

打赏作者

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

抵扣说明:

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

余额充值