JS对象模型是个难点,跟其他语言都不一样,是基于对象的,基于原型(prototype)的面向对象语言,而不是基于类的面向对象语言。
C++和JAVA是有类class和实例instance的概念,抽象东西需要具象化,实例是类的实体
JS是基于原型的,只有原型对象的概念,原型对象就是一个个模版,新的对象从这个模版构建从而获得最初的属性。任何对象在运行时都可以动态增加属性(跟python一致),任何一个对象都可以作为另一个对象的原型,这样后者就可以共享前者的属性。
类的申明方式,可以是标识符,数字,字符串,都可以作为key
a是标识符的写法,最后也转换成了string
1也变成了string,属性最后都是string
这种定义的方式,最后都要转换成字符串的方式
还有一种定义方式,是借用函数来实现的,ES6之前的定义方式,这样定义的类,借用函数的方式,这个this跟python里的self是同一个意思,实例自身
这里面是可以写函数的
所有实例的创建要用new,不加new,使用会抛出异常,这叫普通函数调用,用的是函数返回值
这个函数返回值是undefined
A是个对象,这个对象有两个属性,x,y,所以应该加new,没有传值,所以两个都是undefined
给两个值就成了,4,5,这是ES6之前的类定义方式,要当成类来用,就用new
打印a其实和this一样,this就是指向当前实例,this就是self
在C++和JAVA中,称为this指针
去掉new试试,a打印之前,先要经过第16行,就需要先做函数调用,就需要先执行
后面有xy,也就是4,5真的动态注入到某个对象上了
普通函数调用,这个this就是全局对象
一旦new,this就不是全局对象,用了new,指向的是当前实例本身,用new相当于创建新的实例,(普通函数调用,this指向的是全局对象)
称这个是构造器,是用来构造的,用new就可以构造一个实例出来,所以习惯上称为构造器,或是构造函数
也可以有方法,这个属性只要对应到函数上就可以了
在18行打印了,才会有show属性
1的时候show还没有动态加入
传统方式就是用构造器,但是不能使用普通函数调用,虽然可以执行,但是会报问题,如果要作为类来用,必须使用new关键字来获得一个实例,这时候在函数体里出现的this就指向当前实例本身,就动态增加了,x,y,show这样的属性即可
试试能不能继承
这样执行看看,这些都是全局的。this出问题了
缺一个函数,call,(将python的super之前,类名有,要传self)需要借助call方法,call代表用这个this解决问题,这就是继承
这是P3D对应的对象信息
这是ES6之前的写法,目前还有很多人写
因为function是没有继承和扩展的关键字的,也不能加列表,就只能用这种方式来调用,在ES6之前需要用这种方法
有了call,this指定就绑定在当前实例point3D(python也是借助父类的构造器构造自己),子类该覆盖覆盖,父类就可以被子类的同名覆盖
虽然是基于原型的,但是面向对象还是基本保留的
使用new之后,就可以用function来构造对象,这些function称为构造函数或者构造器,每个函数,都是函数对象,都可以调用内建的call方法,将内建的这些方法传入进去
new关键字不用,就是一次普通的函数调用,this代表全局
能否改成新的方式
ES6开始提供了class,构造器关键字constructor,相当于python中的init,对照着改
可以看看Point的真面目,class只不过是一个语法糖,本质上还是以前的function,只不过新语法内部也需要使用新语法
对于类来讲没有什么私有属性,不跟python一样,python里的私有属性就是改名,实际上就是公开的,只不过改了名
看下下面如何写继承,新语法更像面向对象
point 3D里面 ,xyz都有
新语法可能有些浏览器跑不起来
这只是一种语法糖,本质上还是function,建议写ES6,constructor类似init
不用new也是不行的
使用new,就阻止你发生错误
ES6中的class,没有私有的属性,继承是哟个extends关键字,
一个类只能拥有一个名为constructor的构造器方法。如果没有显示的定义一个构造方法,则会添加一个默认的constructor方法。
一个构造器可以使用super关键字来调用一个弗雷的构造函数。
本质上是语法糖,可以创建起来更加简单,清晰,还是一个本质的特殊的函数。
构造器只能有一个
继承用extends,调用的时候用super
子类中直接重写父类的方法可以,可以当方法用
现在打印的是第6行,就是show的时候到底用了谁
下面这种是不同的定义方式
现在调用就出了问题,但是可以看到属性里的变化,show:1100,属性是占优的,所以只是借了父类的构造器来构造你自己,this还是你自己,属性还是你自己的,属性优先
属性优先,这样下面调用起来就是上面的
把下面的去掉,去调用,就还是1100
属性是优先的
同样的方法,子类就可以覆盖父类的
但是交叉的方式就出问题了,用箭头的方式也是可以的
是优先使用子类的属性,如果同名的属性和方法都在,优先使用属性,子类属性覆盖父类属性
父类,子类使用同一种方式类定义属性或者方法,子类覆盖父类。访问同名属性或方法时,优先使用属性。
对于类来讲,是有静态属性的,静态属性还处于实验性的阶段
所谓的静态其实就是类的类属性,类的就属于全部实例的,类的需要static,实例的就是this
这种写法在这种语言里是错误的写法,有类不一定有实例
静态就是类的,拿类来调用没有问题
new是实例化,不放心可以在外面套一层括号,告诉你不能调用,类的就是类的,不能用实例来调用
所以静态的只能用类来调用,不能用实例来调用
跟java一样,静态就是类,类的就是类的,实例不能用,跟python的(static method不一样)
有些版本不支持静态属性
除了python,其他C++,java,JS面向对象的语言里,静态就是类的,用类来调用即可
这个this肯定不是你的实例,add.print,实例不能直接访问静态方法,跟C++,JAVA一致
实例可以通过构造器的方式访问静态方法,构造器里面其实还有很多东西
没有实例,看看打印的ths是什么,this指的是类本身,因为这是新语法,this现在不是实例
思考的方式是,在调用静态的时候,有可能一个实例都没有
静态的就是类的,类的就是类的。
注意:
静态的概念和Python的静态不同,相当于Python中的类变量。classmethod
this的问题
有两个属性,name和getNameFunc,这个函数就相当于一个方法,调用整个方法,会return一个function函数,就可以在这个函数后面调用这个函数
定义出来就直接是一个实例,这个实例有自己的属性和方法
这里return是一个匿名函数
this.name=magedu,this=school本身
第一个括号代表的是方法调用,第二个括号代表返回的函数调用
方法调用会this.name,在这里测试叫global全局,网页测试叫全局window,浏览器对象全局叫window
如果是普通函数调用,this指的是全局的,
这个返回的是一个普通函数,紧接着调用普通函数,如果普通函数用this,指向的就是全局,this。name,全局对象里现在就没有name,相当于在问全局对象有没有name属性,因为现在name是定义在school对象上的
这个this纯粹跟当下用什么函数有关,如果用普通函数调用,非得用this,这里就是,如果是构造器,没有用new,这时候用this也不对,所以不用new就是普通函数调用,跟这上面一个道理
这里普通函数调用,this就已经变成全局对象 了
如果修改,可以使用that,school传进去,跑到that里
但是传错地方了,上面方法调用是没用
所以this解决不来,可以用that,解决所有隐式传参都可以这么做
可以用一些高级的语法,ES6开始引入了call方法
刚才用传统的继承point,是把this传入进去
现在这一段调用返回的是函数,JS会帮你把函数组装成方法,call方法就是其中一个,call方法对任何一个函数对象都提供,只要肯定前面是一个函数就可以调用,call方法就可以将丢失的this强行捆进去
这样就相当于把函数的this,强行改成school,可以认为一个函数内部有一个this(早期兼容,如果是函数调用,this指向函数对象,如果new当实例来用,this就指向实例本身,)
现在this可以改,通过外部调用call方法传进去,等于把内部的this替换掉,call(school)不但调用call方法,还把this修改了
都修改了,this就不等于global了,但是this.name照样 把值拿回来了
函数要是有多参,除了有call方法还有另外的东西,apply
点开看一下
可变参数
第二参数,在call方法是剩余参数,对于apply方法,第二个参数开始必须是一个数组
介绍apply,call方法
5拿给x,y丢了,4不知道去哪里了
它把this进行修改了,对于普通函数调用,会修改this
第一个给了this,普通函数调用
如果使用apply调用,现在效果一样,不但效果一样,还会把函数里的this指针重置
apply区别在于需要一个数组,call方法是把后面的参数往后列即可
apply,需要传数组,call就直接继续写参数即可
call方法和apply方法会在源码中大量出现,是以前写的,现在还有一种写法,是以后用框架来写的时候,最要用的方式,这个方法就叫bind,所有的函数都可以call,apply,bind
如果是apply就可以直接得到想要的结果
直接用bind不能得到想要的结果
bind并不直接执行,不像apply和call方法直接执行就帮你执行,顺带就把你要传入的对象传进去,立刻执行得到结果。bind方法是把你传入的参数,只是做this的修改,作为绑定,绑定到这个对象上并没直接执行,只是给你返回一个新的函数,然后需要再次调用。这样也可以解决this的问题
所以,先bind,绑定return一个新的函数回来,然后在后面加括号调用,缺什么参数补什么参数,原来几个参数,现在就是几个参数,绑定以后新的function可以覆盖原来的function。
bind是return一个新的函数回来,这个函数里面把this重新绑定你指定的对象,原理上this应该指向实例,但是this任由你修改
在网页里面this指针经常会变,网页里面函数调用,this是谁,是你不能确定的,所以需要强行做绑定
显式传入就是that方法
aplly和call的定义参数是你指定的,要修改或覆盖this的
2009年ES5提供了bind
bind最后绑定,是返回一个新的函数
只要是函数调用,this是谁就需要看情况了,class里,this是指向类的,this其实是在一直变化的,不可能在写代码的时候让this变来变去,就应该提前用call,apply,bind方法,把this固定下来,改成你明确知道的实例,这样减少错误
ES6引入了箭头函数
改成这样,能不能得到this.name
现在改的是,getnamefunc,不是改的里面的匿名函数
现在把里面的改成 了匿名的箭头函数
this现在不等于,查看this是空对象
现在是老语法和新语法混着写,混着写就容易出问题
刚才是在这里出的问题,修改一下就搞定了,老的语法不建议加载新的箭头函数语法,箭头函数按照新的语法,解决了this绑定的问题,新的应该用class定义方式,用老语法就不要混着箭头函数了
建议使用call,apply,bind的方式来解决,不然一会老语法,一会新语法,别人无法修改,箭头函数已经不管原来的this绑定问题了,只要在谁的作用域,就把this绑定好了,不需要你关心了
需要改造下,比如用全新的方式来写
getnamefunc()会返回一个箭头函数
这样就有了
bind方法也是常用的方式