文章目录
- 函数
- 重载(overload)
- 匿名函数
- 回调函数
- 匿名函数自调
- 作用域(scope)和作用域链
- 闭包(closure)
- 面向对象
- 封装
- 赋值的按值传递和按地址传递
- 构造函数
- this
- 继承
- 原型对象
- prototype , _ _ proto _ _
- 构造函数自有属性和共有属性的修改
- for , for in
- 原型链
- 多态
- 自定义继承
- 总结
函数
什么是函数: 保存一段可重用的代码段的程序结构,再起一个名字
为什么用函数: 重用,避免重复编码
何时使用函数: 只要一段代码,可能被反复使用时,都必须先封装在函数中,再反复调用函数——DRY:Don’t repeat yourself
如何创建函数: 3种
①用声明方式创建:
//function声明提前function(){}整体都会提前,因为变量提升`=`后面的留在原地,此处无`=`
function 函数名(形参列表){
函数体;
return 返回值
}
存在的问题: 会被声明提前——破坏程序原有的执行顺序
声明提前: hoist
什么是: 在程序开始执行前,
Js引擎会先将var
声明(创建一个新变量)的变量和function
声明的函数,提前到当前作用域的顶部集中创建=
(赋值)留在原地!
function fun(){ console.log(1) }//window对象中存储变量fun指向函数对象
fun(); //2
//创建一个新的变量fun,window已存在fun不会创建fun,新的值传给原fun,原fun值释放
function fun(){ console.log(2) }
fun(); //2
var fun=100;//新声明的fun提前,值(原始类型值)留在原地,fun=100代替原函数地址
fun(); //报错:fun不是一个函数(不能调用)
*②用赋值方式创建:
var函数名=function (形参列表){
函数体;
return 返回值
}
优点: 仅=
前的声明部分被提前,而函数定义留在原地, 避免函数声明提前
×③用new方式创建:
var 函数名=new Function("形参1","形参2","函数体; return 返回值")
揭示了:
函数的本质其实也是一个对象,只不过内部存的不是数据,而是代码段而已。
函数名的本质仅仅是一个普通的变量(内存里存储一个数据的存储空间,一个变量只能存一个值,所以函数存不进来所以放外面,形成一个对象地址值引用关系)。函数名通过保存函数对象的地址,来引用这函数对象。
当我们调用函数名时,其实是让引擎找到函数名变量引用的函数对象,按照函数对象中保存的代码段,执行程序。
function其实是new Function的简写!所以程序每指定一次function,都等效于一个new Function,都会创建一个新函数对象!
形参
什么是: 专门接受函数执行时必须的参数值得变量
为什么: 有些函数必须提供某些数据才能正常执行
何时: 只要一个函数,自己缺少一些数据,需要外部提供数据,才能正常执行时!就要定义形参!
形参虽然没有var, 但是也属于局部变量!
返回值
什么是: 一个函数的执行结果
为什么: 调用者需要获得函数的执行结果
何时: 如果一个函数的调用者希望获得函数的执行结果,进行后续操作时,函数中就必须定义返回值
如何: return 返回值;
return 其实可以单用!表示退出函数的意思!
break:只退出循环
return后不要随意换行,一旦return后换行,则js引擎会自动在return后加; 换行之后的代码不再执行!
function add(a,b){
return a+b;
} //正确
function add(a,b){
return ; //js引擎会自动在return后加`;`
a+b;
} //错误: 返回undefined
重载(overload)
什么是: 多个同名的函数,但是形参列表不同,在调用时,可自动根据传入实参列表的不同,选择匹配的函数执行。
为什么: 减少函数的个数,减轻调用者的负担。
何时: 今后只要一件事!传入不同的实参列表,希望执行不同的操作时,就必须用重载!
如何:
问题: js不支持其他语言那种重载的语法: 因为js不允许多个同名函数同时存在!如果同时定义多个同名函数,则最后一个同名函数,会覆盖之前所有同名函数!
解决: 借助于arguments
对象 来变通实现, 只定义一个函数! 且不要定义形参!
每个函数内都有一个arguments对象:
什么是arguments: 专门接受传入函数的所有实参值的类数组对象(长得像数组的对象)
相同: 下标, length
不同: 不是数组类型,而是对象类型,所以类数组对象无法使用数组类型的函数!
何时使用: 今后在js中,如果一个函数的实参值个数不确定,就必须用arguments代替形参列表!
如何使用:
① 不用自己创建!可直接使用!
② 获得实参值: arguments[i]
③获得实参个数: arguments.length
在要实现重载的函数内,判断arguments中的实参值个数或实参内容不同,来动态选择不同的逻辑执行!
//定义付款函数,支持手机支付,现金支付,刷卡支付
function pay( ){
// arguments{0: , 1: , ...}.length=2
//如果没有传入实参值
if(arguments.length==0){
console.log("手机支付...")
}else if(arguments.length==1){//否则如果只传入一个实参值
console.log(`现金支付,收您${arguments[0]}元`)
}else{//否则
console.log(`刷卡支付,从您卡号${arguments[0]}中扣款成功!`)
}
}
//想手机支付:
pay();
//想现金支付:
pay(100);
//想刷卡支付:
pay("6553 1234","123456");
原生函数中重载的例子: arr.splice()
- 删除元素: arr.splice(i, 几个)
- 插入新元素: arr.splice(i, 0, 新值1, 新值2,…)
- 替换现有元素: arr.splice(i, 几个, 新值1, 新值2,…)
匿名函数
什么是: 创建函数时,不指定函数名的函数
为什么: 节约内存!因为用完就自动释放!
何时: 今后只要一个函数只使用一次时,都必须用匿名函数
如何:
回调函数
回调函数几乎都是匿名函数
因为回调函数几乎都是为主函数量身定制的,别的函数也用不了。所以,希望回调函数用完,和主函数一起释放!
ex:
1). arr.sort(function(a,b){return a-b})
2). str=str.replace(/正则/ig, function(kw){ return xxxx })
匿名函数自调
创建一个匿名函数后,立刻调用自己!
何时: 今后所有js代码都应该包裹在一个匿名函数自调内!
为什么: 避免使用全局变量 避免全局污染 释放已调用函数
如何:
标准:
//"函数名()" 原理是 函数名声明变量指向的“地址()” 等于函数直接调用
(function(形参列表){
函数体;
return 返回值
})(实参值列表)
//除了不能有函数名
×特殊:
+function(){…}()
!function(){…}()
坑! 匿名函数前后的分号不要省略!
错误: (function(){ … })()
(function(){ … })()
报错: (intermediate value)(intermediate value)(...) is not a function
正确: (function(){ … })();
(function(){ … })();
(function(){//创建函数=new Function()
var start=new Date();
alert(`开始加载页面内容,at:${start.toLocaleString()}`);
})();//立刻调用!
</script>
<h1>Welcome</h1>
<script>
(function(){
var end=new Date();
alert(`页面内容加载完成,at:${end.toLocaleString()}`);
})();
作用域(scope)和作用域链
什么是作用域:
作用: 一个变量的可用范围
本质: 一个保存变量的对象
为什么: 为了避免不同范围内的数据之间互相干扰!
Ⅰ 全局作用域: window对象
专门保存所有全局变量的作用域
优点: 随处可用,可反复使用!
缺点: 极易被污染
Ⅱ 函数作用域: ?对象
专门保存仅函数内可用的局部变量的作用域
优点: 因为仅函数内可用,所以不会被污染
缺点: 不可重用!
函数的生命周期: 一个函数从创建,到调用,最后到调用完所经历的阶段
定义函数时:
Ⅰ. 创建函数对象
Ⅱ . 创建函数名变量,保存函数对象的地址
Ⅲ . 每个函数其实都有一个"好友列表"。一般函数的好友列表中只包含两个格:
①离函数近的这个格: 暂时空着
②离函数稍远的格: 存window的引用
为什么: 因为每个函数都害怕将来执行时,自己少东西,不知道该找谁帮忙!好友列表中保存window,是为了,万一将来执行时,自己缺少变量,还可找window要!
调用函数时:
Ⅰ . 临时创建本次函数调用的函数作用域对象
Ⅱ . 在函数作用域对象中创建本次函数所需的所有局部变量
包含两种情况:
①在函数内var出的变量
② 形参变量虽然没有var,但是也属于局部变量!
Ⅲ . 函数要有列表中离函数近的一个格,会立刻引用函数作用域对象
Ⅳ . 函数执行过程中,变量的使用顺序:
①先局部
②局部没有,才全局
函数调用后:
①释放函数作用域对象
②导致函数作用域对象中的局部变量也跟着释放!
③所以,所有局部变量都不可重用!
var a=10;
function fun(){
var a=100;
a++;
console.log(a)
}
fun(); //101
console.log(a) //10
作用域链:
①什么是: 由多级作用域串联形成的链式结构
②每个函数在创建时,就有了自己的作用域链。普通函数作用域链里包含2个格:
离自己近的格,暂时为空,调用函数时,用来临时引用函数作用域对象(函数作用域对象调用后就释放又空了)
离自己远的格,始终保存着全局作用域对象window
③保存着一个函数可用的所有变量
④控制着变量的使用顺序: 先局部,局部没有,才全局!
js中没有块级作用域:
(1). 程序块: 除函数以外的循环和分支的{},称为程序块
比如: for(var i=0;i<arr.length;i++){... }
, if(xxx){...}else{...}
(2). 在Java语言中循环和分支结构的{}也是一级作用域,循环和分支结构内var的变量,出了{},不能使用!
比如: if(xxx){
var a=10;
}else{
var a=100;
}
console.log(a) //错误!
改为:
var a;
if(xxx){
a=10;
}else{
a=100;
}
console.log(a) //正确!
(3). 但是,js中循环和分支的{}不是作用域,即使在循环内和分支内声明的变量,出了循环和分支,照样使用!
比如: if(xxx){
var a=10;
}else{
var a=100;
}
console.log(a) //正确!
闭包(closure)
如果在一个内部函数里,对在外部作用域(但不是全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。
闭包的特点:
- 可以读取自身函数外部的变量(沿着作用域链寻找)先从自身开始查找,如果自身没有才会继续往上级查找,自身如果拥有将直接调用。(哪个离的最近就用哪一个)
- 延长内部变量的生命周期
- 函数b嵌套在函数a内部
- 函数a返回函数b
闭包的作用:
在函数a执行完并返回后,闭包使得JavaScript的垃圾回收机制不会收回a所占用的资源,因为a的内部函数b的执行需要依赖a中的变量,闭包需要循序渐进的过程。
闭包的构成:
闭包由俩个部分构成:
- 函数
- 以及创建该函数的环境
应用场景:
保护函数内的变量安全。函数a中只有函数b才能访问,而无法通过其他途径访问到,因此保护了a的安全性。
在内存中维持一个变量
什么是闭包:
使用: 既重用一个变量,又保护变量不被篡改的一种编程方法
本质: 外层函数的函数作用域对象,因为被内层函数引用无法释放,就成了闭包!
为什么: 全局变量和局部变量都有不可兼得的优缺点:
全局变量 | 局部变量 |
不会污染 | 极易被污染 |
可重用 | 不可重用 |
何时: 只要希望反复使用一个变量,又保护这个变量不被别人篡改时
如何: 3步,三特点:
①用外层函数包裹要保护的变量和内层函数对象
②外层函数必须返回内层函数对象到外部
③使用者必须调用外层函数才能获得返回的内层函数
结果: 反复调用返回的内层函数,可以反复使用外层函数的局部变量,且不会被篡改!
闭包形成的原因: 外层函数调用后,外层函数的作用域对象无法释放,形成了闭包!(因为被内层函数对象的作用域链引用着)
闭包的缺点: 比一般的函数多占用内存!多占用的是外层函数的函数作用域对象
如何解决: 主动释放!
保存内层函数的全局变量=null
结果: 先释放内层函数对象,再释放外层函数的函数作用域对象
//闭包三步: 三大特点:
//1. 用外层函数包裹要保护的变量和内层函数
function parent(){
var total=1000;
//2. 将内层函数对象返回到外层函数外部
return function(money){
total-=money;//total(函数作用域对象)就是闭包
console.log(`还剩:${total}元`)
}
}
//3. 外部要想使用外层函数返回的结果,必须调用外层函数,用变量接住返回的内层函数对象
var pay=parent();
//问题: 按理说: parent()调用后,局部变量total应该释放才对?!
/*pay:function(money){
total-=money;
console.log(`还剩:${total}元`)
}*/
pay(100); //900
total=0;
pay(100); //800
pay(100); //700
笔试时: 画简图:
(1). 找受保护的变量是谁?——通常是妈妈的局部变量
(2). 找妈妈生出了几个内层函数孩子:
a. 孩子可以通过return返回到外部!
b. 强行给全局变量赋值一个内层函数
c. 包裹在一个对象或数组中同时返回出多个内层函数.
结论: 妈妈一次生出的多个孩子,共用同一个变量!
比如:
function fun(){//妈妈
var n=999;
//js中有个规定: 强行给一个未声明过的变量赋值,会自动在全局!创建该变量!
//剖腹产
nAdd=function(){n++};
//顺产
return function(){
console.log(n)
}
}
var getN=fun();
//getN:function(){console.log(n)}
getN();//999
nAdd();
getN();//1000
短路逻辑
//凡是从页面上输入的,或获得的一切都只能是字符串
//所以想获得数字,必须在使用之前强制类型转换
//var price=parseFloat(prompt("输入金额"))
//如果金额>=500,就打八折
//if(price>=500){//一个条件
//price*=0.8 //一件事,只有满足条件才执行
//}
//price>=500&&(price*=0.8);
//console.log(`金额: ¥${price.toFixed(2)}`)
//alert(0&&2)//0->false 不用2,用0
//alert(1&&2)//1->true 所以继续使用2,不用1
var msg=prompt("输入自我介绍")
//如果msg经过隐式转换,可转为true,则后续值不再使用。
//如果msg经过隐式转换,被转为false("", null, NaN, undefined, 0),则采用后续值代替前一个值
msg=msg||"主人很懒,什么也没留下"
console.log(`自我介绍: ${msg}`);
alert(0||2)//0->false, 所以才会启用备胎2
alert(1||2)//1->true,所以不再用2,而用1
面向对象
什么是面向对象: 程序中,都是先用对象结构,集中保存一个事物的属性和功能,然后再按需使用事物的属性和功能
什么是对象: 程序中,集中存储现实中一个事物的属性和功能的程序结构/存储空间
为什么: 便于大量数据的管理和维护
何时: 今后,所有项目都是用面向对象思想做的
如何: 面向对象三大特点: 封装,继承,多态
封装
什么是: 创建一个对象,集中保存一个事物的属性和功能
为什么: 为了便于大量数据的管理和使用
何时: 今后只要使用面向对象方式编程,都必须先创建所需的对象,再按需调用对象中的属性和方法
如何创建对象: 3种: 1.用{}创建一个对象 2.用new Object()创建 3.用构造函数
用{}创建一个对象:
var 对象名={
属性名: 属性值,
… : … ,
方法名: function(){
… this.属性名 …
},
方法名: function(){
… this.属性名 …
}
}
何时: 如果在创建对象时,已经知道对象的成员内容
用new Object()创建:
① 先创建一个空对象: var 对象名=new Object()
其中: new Object()可简写为{}
② 向空对象中强行添加新属性和方法:
对象名.属性名=值
对象名.方法名=function(){
… this.属性名 …
}
何时: 当创建对象时,暂时不知道对象中的成员,就可先创建空对象,稍后得知对象的成员内容后,再强行添加进对象中。
揭示: js中一切对象低层都是关联数组:
①长得像: 都是名值对儿结构!
②都可用[“属性名”]和.属性名两种方式访问成员!
对象.属性名是对象[“属性名”]的简写
对象.属性名也会被自动翻译为对象[“属性名”]
a. 如果属性名是已知的固定的名字,两者都可用!
b. 但是如果属性名来自于一个可能变化的变量!则只能用[变量],且不能加"",因为变量一旦放入""中,就成了写死的字符串,无法改变了。
访问不存在的属性,都不会报错!而是返回undefined!
用途: 判断对象中是否已经包含某个属性!对象.属性名===undefined
说明对象中不存在该属性对象.属性名!==undefined
说明对象中存在该属性
强行给不存在的属性或位置赋值,也不会报错!而是在该新位置自动添加新属性。
用途: 今后向对象或关联数组中添加新成员,唯一的办法只有强行赋值!
都可被for in遍历!
<script>
//浅克隆: 只能克隆一级属性。如果属性值又是一个内嵌的子对象,则无法克隆子对象
function clone(oldObj){
//1. 创建一个空的新对象
var newObj={}; //new Object()
//2. 遍历旧对象中每个属性
for(var key in oldObj){
// "sname"
// "sage"
//3. 获取旧对象中当前属性值,强行赋值给新对象中同名属性,结果为新对象添加了同名的新属性
newObj[key]=oldObj[key];
// newObj["sname"]=oldObj["sname"]
// "Li Lei"
// newObj["sage"]=oldObj["sage"]
// 11
}
//结果: {
//"sname":"Li Lei",
//"sage":11
//}
return newObj;
}
var lilei={
sname:"Li Lei",
sage:11,
address:{
city:"北京",
street:"万寿路"
}
}
var lilei2=clone(lilei);
console.log(lilei);
console.log(lilei2);
console.log(lilei==lilei2);//false
// 地址 地址
//给新lilei2找一个女朋友:小丽
lilei2.gf="小丽";
console.log(lilei2);
console.log(lilei.gf);//undefined!
//把lilei2搬家到西安
lilei2.address.city="西安";
//lilei的地址会不会变?
console.log(lilei.address.city);//西安
//如果lilei地址还是北京,说明克隆成功
//如果lilei地址跟着变为西安,说明仅克隆了第一级属性,内嵌的子对象依然是共用的!
//深克隆: 每一级内嵌的对象,都可被克隆一份副本,对新对象做任何修改,都不应该影响原对象。
赋值的按值传递和按地址传递
原始类型(number boolean string null undefined)修改新变量不改变原变量
引用类型修改新变量会改变原变量,传递的是地址(在内存中以地址的方式存储),两个地址指向的是同一个对象
构造函数
用构造函数反复创建多个相同结构的对象:
问题: 以上两种创建对象的方式,一次只能创建一个对象,如果反复创建多个相同结构的对象时,重复代码会极其多!
何时: 今后,只要反复创建同一类型的相同结构的多个对象时,都要用构造函数!
什么是: 专门描述同一类型的所有对象的相同结构的函数
为什么: 重用结构代码!
如何: 2步:
①定义构造函数描述同一类型所有对象的同一结构
function 类型名(形参列表){
this.属性名=形参;
this.属性名=形参;
this.方法名=function(){//浪费内存,构造函数不能有方法定义,向原型对象强行添加
... this.属性名 ...
};
}
② 调用构造函数反复创建相同结构的的多个对象
var 对象名=new 构造函数名(实参值列表)
原理: new共做了几件事: 4件
① 创建新的空对象
② 设置新的子对象继承父对象(构造函数的原型对象)
③ 用新对象替换构造函数中的this,并调用构造函数。通过强行赋值的方式,为新对象添加新属性
④ 返回新对象的地址保存在变量中
function Student(sname,sage){
this.sname=sname;
this.sage=sage;
this.intr=function(){
console.log(`I'm ${this.sname},I'm ${this.sage}`)
}
}
//要创建一个李磊
var lilei=new Student("Li Lei",11);
//再创建一个韩梅梅
var hmm=new Student("Han Meimei",12);
console.log(lilei);
console.log(hmm);
问题1: 构造函数虽然可以重用代码,但是浪费内存
所以今后在构造函数中就不应该包含方法定义
问题2: 方法定义放在哪里?
如何访问对象中的成员:
对象.属性名
对象.方法名()
this
问题: 对象自己的方法中想使用对象自己的属性
错误: 直接在对象自己的方法中写对象自己的属性名!
报错: xxx未定义!
因为: 前边不带.的普通变量,默认只能在函数作用域和全局作用域window中查找,无权擅闯某个对象内部,获取对象中的属性值。
不好的解决: 在对象自己的方法中,用"对象名.属性名"方式来访问对象自己的属性,告诉引擎准许进入某个指定的对象内获取属性值。
缺点: 紧耦合: 外部对象名改变,方法内写死的对象名也必须跟着手动修改!如果忘记修改方法内写死的对象名,程序一定会出错!
好的解决: 用关键字this代替函数内写死的对象名:
什么是this: 自动指向正在调用当前函数的.前的对象 的关键词
何时: 今后只要对象自己的方法,要使用对象自己的另一个属性名或另一个方法时,都必须用"this.属性名"方式来访问!
原理:
①当调用函数时,在临时创建的函数作用域对象中,临时创建this关键词!临时指向正在调用当前函数的.前的对象。
②在函数内使用this,等效于直接使用调用函数时.前的对象。
③如果将来调用函数时,.前的主语对象发生了变化,则this可自动跟着.前的对象一起变化,而不用修改原代码!
强调: 要判断this指谁!一定不要看定义在哪儿!只看在哪里被谁调用!
总结:
obj.fun() this
->obj
new Fun() this
->new新对象
fun() this
->window
构造函数.prototype.fun=function(){ this
->?}
<script>
//为数组加添加一个新函数sum,可对当前任意数组中的元素求和!
Array.prototype.sum=function(){
var sum=0;
for(var i=0;i<this.length;i++){
//不要用!for(var i in this){
sum+=this[i]
}
return sum;
}
</script>
</head>
<body>
<script>
var arr1=[1,2,3];
console.log(arr1.sum());//6
var arr2=[1,2,3,4,5];
console.log(arr2.sum());//15
</script>
</body>
继承
问题: 构造函数虽然可以重用代码,但是却浪费了内存!定义在构造函数中的方法,每创建一个对象,都执行一次function,都会创建该方法的一个完全相同的副本给每个对象。——没必要的!
结论: 今后在构造函数内,不要包含方法定义!
解决: 继承:
什么是继承: 父对象的成员,子对象无需重复创建,就可直接使用!
为什么: 代码重用,节约内存!
何时: 如果多个子对象,都要使用相同的功能时,就要用继承来实现
如何:
原型对象
Ⅰ Js中的继承都是通过继承原型对象来实现的(只继承原型对象,不能继承构造函数)
Ⅱ 什么是原型对象: 为该类型下所有子对象集中保存共有成员方法或属性的一个父级对象
Ⅲ 如何使用:
创建:
① 不用自己创建
② 买一赠一送的: 当我们定义构造函数时,就附赠了一个空的原型对象
何时继承: 当用new创建子对象时,new的第2步是设置当前新创建的子对象继承构造函数的原型对象。
①每个对象上都有一个 _ _ proto _ _ 属性,用来标记当前对象的父对象是谁
②New的第二步,将当前新对象的_ _ proto_ _属性,指向当前构造函数的原型对象
结果: 父对象(构造函数的原型对象)中的成员,所有子对象无需重复创建,就可直接使用!
如何向原型对象中添加共有方法和属性: 强行赋值:
① 找到当前构造函数的原型对象:
构造函数.prototype
② 如何向原型对象中添加共有方法和属性: 强行赋值:
构造函数.prototype.方法名=function(){ … }
prototype , _ _ proto _ _
为所有子对象保存共有方法的父对象,称为原型对象
一个类型中,prototype
和 _ _ proto _ _
其实指向的是同一个原型对象
① prototype
属于构造函数对象,是站在和原型对象平级的位置,查找构造函数: 构造函数.prototype
② _ _ proto _ _
属于每个子对象中,是站在子级角度称呼父对象 , 所有对象都有 _ _ proto _ _属性
想访问一个类型中的原型对象,都用: 构造函数.prototype
想访问某一个子对象的父对象,都用子对象. _ _ proto _ _
script>
//妈妈
function Student(sname,sage){
this.sname=sname;
this.sage=sage;
}//.prototype->Student的原型对象
//向Student的原型对象中强行添加共有方法intr()
Student.prototype.intr=function(){
console.log(`I'm ${this.sname},I'm ${this.sage}`)
}
//要创建一个李磊
var lilei=new Student("Li Lei",11);
//再创建一个韩梅梅
var hmm=new Student("Han Meimei",12);
console.log(lilei);
console.log(hmm);
//李磊想自我介绍
lilei.intr();
//韩梅梅也想自我介绍
hmm.intr();
//李磊的爹和韩梅梅的爹是不是同一个人!
console.log(lilei.__proto__==hmm.__proto__);//true
//李磊的爹是构造函数的老公吗?
console.log(lilei.__proto__==Student.prototype)
//true
</script>
构造函数自有属性和共有属性的修改
自有属性:保存在子对象中,仅归子对象自己所有的属性
共有属性: 保存在原型对象中,归多个子对象共有的属性
获取属性值: 二者没差别!都可用子对象.属性名
修改属性值:
自有属性 才可用"子对象.属性名=值
“方式修改
共有属性:
① 不可用"子对象.属性名=值"方式修改
② 只能用”构造函数.prototype.共有属性名=值
"方式修改
③ 如果硬用 "子对象.共有属性名=值"方式修改,不报错,而是自动给当前这一个子对象,添加同名自有属性,并保存新属性值。结果: 从此在这个属性的使用上,这个子对象和其他子对象再无关系分道扬镳!
function Student(sname,sage){
this.sname=sname;
this.sage=sage;
}
Student.prototype.className="初一2班"
var lilei=new Student("Li Lei",11);
var hmm=new Student("Han Meimei",12);
console.log(lilei);
console.log(hmm);
//获取lilei的sname和className
console.log(lilei.sname,lilei.className)
console.log(lilei.className, hmm.className)
//修改className
//正确:
Student.prototype.className="初二2班"
console.log(lilei.className, hmm.className)
//错误:
lilei.className="六年级2班"
console.log(lilei.className, hmm.className);
//过了一年又升一级
Student.prototype.className="初三2班"
console.log(lilei.className, hmm.className)
为浏览器内置类型添加新的自定义函数:
什么是内置类型: ES标准中固定的,浏览器已经实现的类型
包括: 11种:
Number String Boolean —— 包装类型
Array Date RegExp Math{}
Error
Function Object
global{}
今后凡是一种类型,都包含两部分:
① 构造函数: 负责创建该类型的子对象
② 原型对象: 负责保存该类型所有子对象共用的方法和属性
查看该类型目前为止提供的所有函数的合集!
构造函数.prototype
想要的函数没有!也可以自己向原型对象中添加新函数
构造函数.prototype.新函数=function(形参列表){
函数体;
//要想获得将来调用该函数的.
前的任意主语对象,只能用this
!
//原型对象中的方法中的this
指将来调用这个函数的 .
前的某一个子对象!
return …
}
for , for in
① for
循环,因为变量i
是从0
开始逐步累加,所以i
始终是数字,所以for
循环专门用于遍历一切数字下标的东西:
比如: 索引数组,类数组对象,字符串
② for in
循环中的in
,本意是遍历一个对象中的每个属性名,但是in
不但遍历子对象,而且还会沿着_ _ proto_ _
向上遍历父对象中可用的共有属性!所以,in
很有可能拿到父对象中,你不想要的内容。
所以,for in循环今后,专门遍历非数字下标的关联数组或对象!
原型链
什么是: 由多级父对象逐级继承,形成的链式结构 , 所有对象的顶级父类型是Object,顶级父对象是Object的原型对象
保存着: 一个对象可用的所有属性和方法
控制着: 属性和方法的使用顺序:
① 先自有
② 自己没有才延原型链向父级查找!
多态
什么是: 一个函数,不同情况下表现出不同的状态
分为:
①重载:
②重写(override): 在子对象中定义和父对象中同名的成员!结果: 再使用这个属性时,总是优先使用子对象自己的同名属性,而永久屏蔽了父对象中同名属性
何时重写: 只要从父对象继承来的成员不好用时,都可在子对象内部定义同名的新成员,覆盖父对象继承的同名
如何: 在子对象内部定义同名的新成员
建议: 所有自定义类型和自定义对象中最好都要提供toString()
来输出对象的内容,便于调试
function Student(sname, sage){
this.sname=sname;
this.sage=sage;
}
//在Student的原型对象中添加toString()方法
Student.prototype.toString=function(){
return `{sname:${this.sname}, sage:${this.sage}}`
}
var lilei=new Student("Li Lei",11);
var arr1=[1,2,3];
var now=new Date();
var obj1={x:1,y:2};
obj1.toString=function(){
return `{x:${this.x}, y:${this.y}}`
}
console.log(lilei.toString());
console.log(arr1.toString());
console.log(now.toString());
console.log(obj1.toString())
自定义继承
如果子对象觉得现在的父对象不好,可以更换父对象
如何: 3种:
①只修改一个子对象的父对象: 其实就是修改子对象的_ _ proto _ _ 属性指向新的父对象即可!
子对象._ proto _=新父对象
问题: _ proto 有些浏览器不让用:
所以: Object.setPrototypeOf(子对象, 新父对象)
设置 原型对象←的←子对象 (为) 新父对象
代替 _ proto _ _
②更换该类型下所有子对象的爹: 其实就是更换构造函数的原型对象: 构造函数.prototype=新原型对象
时机: 应该在创建子对象之前更换!
这样,新创建的子对象,才能自动继承新原型对象
不好的做法: 如果在创建子对象之后更换构造函数的原型对象,则只要构造函数的原型对象发生改变,已经创建的子对象依然跟着旧的原型对象。
var father={
bal:1000000000000,
car:"infiniti"
};
function Student(sname, sage){
this.sname=sname;
this.sage=sage;
}//prototype->{ }
Student.prototype=father;
var lilei=new Student("Li Lei",18);
var hmm=new Student("Han Meimei",19);
console.log(lilei)
console.log(hmm)
console.log(lilei.bal,lilei.car);
console.log(hmm.bal, hmm.car);
总结
一.面向对象:
(1). 封装: 创建对象三种方式:
a. 用{}创建: 创建一个对象
var obj={
属性名: 属性值,
… : … ,
方法名: function(){
… this.属性名 …
},
… : …
}
b. 用new:创建一个对象
1). var obj=new Object();
2). obj.新属性=值
obj.方法名=function(){
… this.属性名 …
}
c. 用构造函数: 反复创建多个相同类型相同结构的对象时
1). 定义构造函数:
function 类型名(形参列表){
this.属性名=形参;this.方法名=function(){ … }
}
2). 用new调用构造函数:
var obj=new 类型名(实参值列表)
3). new做了4件事:
i. 创建新的空对象
ii. 设置新的子对象继承父对象(构造函数的原型对象)
iii. 用新对象替换构造函数中的this,并调用构造函数。通过强行赋值的方式,为新对象添加新属性
iv. 返回新对象的地址保存在变量中
4). 问题1: 构造函数虽然可以重用代码,但是浪费内存
所以今后在构造函数中就不应该包含方法定义
问题2: 方法定义放在哪里?
(2). 继承:
a. 其实每个构造函数都附赠了一个空的原型对象:
构造函数.prototype
b. 在用构造函数创建子对象时,new会自动让子对象继承构造函数的原型对象:
子对象._ _ proto_ _=构造函数.prototype
c. 向原型对象中添加共有方法或属性:强行赋值:
构造函数.prototype.共有方法=function(){ … }
d. 结果: 该类型创建出的所有子对象,都可直接使用原型对象中添加的所有共有方法和属性
e. 总结: 今后只要该类型的子对象都需要的共有方法,必须定义在构造函数的原型对象中