面向对象
1、介绍函数和对象之间的关系
思考?什么是函数?函数有什么作用?
作用1:整合封装代码方法,让调用更加清晰
作用2:用来生成对象
任何对象都是由函数生成的,这样的函数是构造函数
思考? 1、构造函数和普通函数有什么区别?
没什么区别(哈哈哈)只是使用方式的区别
function foo(){
}
foo()//普通函数
new foo()//构造函数
一个函数当正常调用的时候就是普通函数,通过new函数()就是构造函数
但是为了区分构造函数,约定俗成的共识:构造函数 :首字母—>大写
思考?2、构造函数怎么生成对象的?
通过new函数()方式就可以生成对象
let obj = {}也是构造函数生成的对象吗?
是的,因为这种方法叫做包装对象和字面量,{ }其实是一种语法糖,直接简略掉了,相当于new Object()
拓展:
let num=3?不是说万物皆对象吗?3也是一个对象吗?怎么创造出来的?其他的数据类型也是对象吗?
是一个对象,实际上是new number(3)生成的,控制台直接(3).
也就是说万物皆对象:任何数据类型都是由构造函数生成的对象(除了undefined和null之外,这两个是开发js这个语言的人员规定的。)
为什么3.不可以。因为3是一个数字3.也是一个数字。点的含义一个是小数点一个是调用函数,小数点的的优先级更高一点,所以要么加(括号),要么再加一个点(这都是拓展部分,只是更好的理解)
接下来继续
不同构造函数为什么创造的对象不一样?这是为什么?
构造函数(前辈)实例对象(后辈),俗话说龙生龙凤生凤,老鼠的儿子会打洞,也就是说构造函数是有基因的,创造生成对象会把基因带过去
那么这个基因在哪里呢?一般的属性会是基因吗?
function Foo(){}
Foo.a="妈妈染了黄色头发"
let obj =new Foo()//没有a属性
这个时候a属性传给了obj这个对象了吗?显然是没有的,obj对象不具有a属性
比如:一个妈妈在怀着孩子时候染了头发,那孩子出生时候头发和妈妈染的一样吗?显然不一样,因为染头发只是表面,不是妈妈的头发基因就是那个颜色(多么生动的举例子,掌声响起)
构造函数在生成对象的时候可以对对象进行一些设置
在构造函数中设置this就直接影响生成的对象
function Foo(){
this.name="张三"
let b="局部变量"
}
let obj = new Foo()
以上就是this的作用,而b仅仅是函数中的局部变量
思考?相同方式创造的两个对象一样吗?
不一样
只不过基因一样,但是是两个对象。
思考?对象不是引用关系类型吗?为什么还是创造出两个对象
首先你对引用类型理解错了,这种是引用类型的体现,创造了一个对象放在了内存当中,并使得a指向了这个对象(以赋值的思想理解吧),随后b=a使得b也指向了这个对象,所以a 和b指向的是同一个对象,只要任何一个对对象进行操作,都会使得两个同时改变,如图所示
又说多了接下来继续
2、原型和原型链
思考?上边我们总说基因,什么是对象的基因?
任何对象都有一个_proto_ 的属性 ,这个属性就是基因,管这个属性叫做 —> 隐式原型
思考问题?隐式原型(基因)有什么作用?
对象访问属性怎么访问的?
1、首先在内部里边去寻找
2、内部找不到之后去隐式原型里边找…(隐式原型里还有隐式原型,可以一直找。。。。)
(构造函数中的原型===实例对象中的隐式原型)
接着思考?这个隐式原型从哪里来的?
function Foo(){}
let obj = new Foo()
我们可以看到函数在一个constructor的属性中
constructor就是构造者,构造器,把他写到了隐式原型_proto_中,
我们可以看到直接用这个对象调用这个构造器,返回的是我们的构造函数,但是我们想研究构造函数和对象有什么关系,我们就要看看内部结构,怎么办?用console.dir()强行以对象的方式( 函数肯定是对象)进行打开这个函数我们来看一看哈。
里边有prototype
任何函数都有一条属性prototype,他是这个函数在实例对象的时候 传递的基因,prototype就是原型
所以重点来了划重点:
构造函数.prototype===实例对象.proto
什么是原型?什么是隐式原型?那这两个和prototype以及_proto_又是什么关系呢?
prototype是原型,而_proto_是一个隐形原型指向了构造函数中的prtotype,所以这两个一摸一样
思考?直接给构造函数中的prototype添加内容,会发生什么?
function Foo(){}
Foo.prototype.say ="hello"
let obj = new Foo()
我们看到了,直接给原型添加属性,那么我们创造出来的实例对象也会有这个属性。刚刚的例子我们原型添加放在了创建实例之前,那么我们调换顺序呢?
看一下
function Foo(){}
let obj = new Foo()
Foo.prototype.say ="hello"
console.log(obj.say)
还是能打印出来?为什么呢,我们后边才给原型添加的东西呀,不应该呀
我们看看哈
利用构造函数Foo创造了一个实例对象obj,之后执行代码内容为:给构造函数原型添加一个say属性,此时打印obj中的say,从对象身上找,找不到,找到隐形原型指向了原型,原型中有say所以找到打印出来
根据刚刚内容写一个小题目,一个空数组,一个Foo函数,现在在数组里添加100个Foo对象,我们想在每个对象中添加一个属性state=“交流过了”
let arr = []
let Foo = function () {
}
for(let i;i<100;i++){
arr.push(new Foo())
}
现在怎么办?两种方法
第一种逐个遍历,添加
let arr = []
let Foo = function () {
}
for(let i;i<100;i++){
arr.push(new Foo())
}
arr.forEach(obj=>{
obj.state="交流过了"
})
这种方法可以实现没有问题,那如果是3000000个呢,我们一个一个遍历要浪费多少性能?太多了
第二种直接在原型上加
let arr = []
let Foo = function () {
}
for(let i;i<100;i++){
arr.push(new Foo())
}
Foo.prototype.state="交流过了"
简单粗暴
思考?为什么一个是数组而一个不是呢为什么?
<ul class="cc">
<li></li>
<li></li>
<li></li>
</ul>
let arr = [1,2,3]
let arrlist=document.querySelectorAll("li")
看两个东西是由什么构造函数生成的
console.log(arr.constructor);
console.log(arrlist.constructor);
构造者不相同,所以一个是数组一个是类数组NodeList
什么是真正意义上的数组?
由Array构造函数实例出来的才是数组
总结一小下
构造函数:普通函数相似,this执行方法new,生成对象
实例对象:由构造函数生成的对象
原型
(1)函数的原型:任何函数都有prototype属性,将原型添加到对象中。
(2)对象的隐式原型:任何对象都有_proto_属性,对象的类别的信息载体
对象访问的属性:对象身上找,隐式原型找,。。。。(隐式原型有隐式原型一直找直到找到为止)
深造一把
function Foo(){}
1、Foo.prototype存在吗? 答案:存在
因为任何函数都有prototype
2、Foo._proto_存在吗?答案:存在
因为每个函数都是一个对象,任何对象都有_proto_属性
3、Foo.prototype和Foo._proto_的区别? 这是重点内容
Foo.prototype是Foo作为构造函数中的基因传递给实例对象
Foo._proto_是Foo作为对象从她母亲中继承的属性
Foo的构造函数是什么? 函数的构造函数是什么?谁创造了这个函数?
我们直接看下他的构造器/构造者即可
任何函数都是由同一个构造函数(Function)生成出来的
因此我们看Function的prototype,岂不是可以看到所有函数具有的方法或者属性?
天啊这不就是我们所有函数的属性吗,一句卧槽真牛皮,那我们在Function中的prototype中添加东西,岂不是所有的函数都有了这个东西,卧槽真爽哈!
补充:console.dir():按照一个目录结构来展开一个对象
思考?this指向谁?
Array.prototype.foo=function(){
console.log(this);
}
Array.prototype.foo()//指向了Array.prototype
let arr=[]
arr.foo()//指向了arr
根据所学就可以在原型中写自己封装的方法,比如在Array.prototype中写myReduce()方法等等
Array.prototype.myReduce=function(cb,total){
let result = this[0]//指向实例对象
let length = this.length
let start =1
if(arguments.length>=2){
result=total
start =0
}
for(let i=start;i<length;i++){
result =cb(this[i],this[i],this)
}
return result
}
我们后期补一个封装各种数组方法的文章,也算小福利了
接下里聊个小东西
思考?Object Function window之间的关系
(1)window 是最小的,window是不是对象?
(2)windows 和Object的关系
window在控制台上打印一下,一直往下找_proto_…第五代左右会找到Objeci,什么意思,Object是他祖宗,明白了吧
(3)Function 和Object之间关系,
任何对象都是由Object创造出来的
任何函数都是由Function创建出来的
这两个是原始规则
Function又是一个函数,Function创建了自己?判断一下虽然我们下图中看到结果是true,自己创造的自己,但是其实是Object创造出来的,但是为了适应那个法则,把这个功劳给到了Function头上,所以才有了ture。
那句话怎么说来着?一个人扛下来所有
好了我们再来看一个
什么鬼,不是Object生了Function把功劳给了他自己,怎么又出现了Object是Function生的?有病吧?
我们刚刚说的Object负责创造对象,Function负责创造函数,Object又是一个构造函数,是一个函数,所以只能退一步,把这个东西归属到Function头上,也就是说开始定的规则不能破坏,只能是委曲求全
Object.prototype是基因的源泉
Object.prototype.proto=null,也就是说是终点,是原型链的终点(对象访问属性终点)
万事万物皆对象,万事万物访问属性的时候都会访问到Objcet.prototype
原型链:对象访问属性访问__proto__,如果访问不到找__proto__.proto…直到找到Object.prototype.__proto__重点
这也就是说为什么任何对象都有toString方法
他们终究找到的toString方法是Object.protot中的toString方法
但是又有问题了why?
说明数字的toString有自己的构造方法
不光数字有自己的toString方法,字符串也有自己的toString方法
我们根据以上就可以知道怎么鉴别所有的数据类型,有人说typeof,这个只能检测基本的数据类型
Objcet.prototype.toString是不是适合所有的数据类型?
正确,可以将任意的数据类型传进去,内部使用的是this调用的主体
所以是调用统一的toString方法Objcet.prototype.toString(null和undefined),返回的数据类型结构描述一致
console.log(Object.prototype.toString([12,13]));
console.log(Object.prototype.toString(undefined));
console.log(Object.prototype.toString({}));
console.log(Object.prototype.toString(1));
console.log(Object.prototype.toString("2"));
console.log(Object.prototype.toString(null));
???为什么都是Object,因为我们的主体是对Object进行判断的,所以需要把这个方法加载判断的内容上,需要call()
console.log(Object.prototype.toString.call([12,13]));
console.log(Object.prototype.toString.call(undefined));
console.log(Object.prototype.toString.call({}));
console.log(Object.prototype.toString.call(1));
console.log(Object.prototype.toString.call("2"));
console.log(Object.prototype.toString.call(null));
console.log(Object.prototype.toString.call(document.body));
console.log(Object.prototype.toString.call(document.querySelectorAll("li")));
console.log(Object.prototype.toString.call(/asd/));
console.log(Object.prototype.toString.call(true));
掌声响起
但是这样还是有点丑,我把前边object还有大括号去掉自己封装一个type的方法
function type(obj){
return Object.prototype.toString.call(obj).slice(8,-1)
}
扩展
Object.prototype可以用{ }替换
function type(obj){
return ({}).toString.call(obj).slice(8,-1)
}
对象访问toString会直接到达Object.prototype中
类数组都可以直接用prototype直接转接数组方法
NodeLst.prototype.map=Array.prototype.map
NodeLst.prototype.Reduce=Array.prototype.Reduce
又要说再见了,话说张哥下章讲一下面向对象二:对象的继承