五、面向对象
1. 继承
- 自有属性和共有属性
-
什么是自有属性:保存在子对象中,只归子对象独有的属性
-
什么是共有属性:保存在原型对象中,归当前类型下所有子对象共有的属性
-
获取属性值时:无论获取自有属性值,还是获得共有属性值,都可用
子对象.属性名
,无差别 -
修改属性值时:
-
如果修改一个子对象的自有属性,才可以
子对象.自有属性=新值
-
如果修改多个子对象共有的属性,必须使用原型对象亲自修改:
构造函数.prototype.共有属性=新值
-
错误做法:如果强行用子对象直接修改共有属性:结果,原型对象中的共有属性保持不变,而是只给当前这一个子对象添加一个新的同名的自有属性。从此,这个子对象,因为已经有了同名的自有属性,就不会再使用同名的共有属性。从此,共有属性发生变化,当前子对象的这个同名自有属性也不会跟随变化。从此,这个子对象和其他子对象,在这个属性的使用上,分道扬镳。
-
-
示例:修改共有属性
<script> 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); console.log(lilei.className,hmm.className); // "初一2班" "初一2班" // 修改自有属性 lilei.className="初二2班"; console.log(lilei.className,hmm.className); // "初二2班" "初一2班" // 过了一年,同学们升了一级 // 修改原型对象中共有属性: Student.prototype.className="初二2班"; console.log(lilei.className,hmm.className); // "初二2班" "初二2班" // 又过了一年,同学们又升了一级 // 修改原型对象中共有属性: Student.prototype.className="初三2班"; console.log(lilei.className,hmm.className); // "初二2班" "初三2班" // 此时lilei的className就跟原型对象中的className属性分道扬镳 </script>
-
- 内置类型的原型对象:
-
什么是内置类型/对象:ES标准中已经规定的,浏览器已经定义好的,我们可以直接使用的类型/对象
-
11种内置类型对象
String Number Boolean----包装类型(扩展)
Array Date RegExp Math(Math不是类型,而是一个对象,不能用new)
Error
Function Object
global(不是类型,是一个对象,不能new,且在浏览器中被window代替)
除了Math和global两种外,其余9中都可以new -
今后凡是可以new的,都是构造函数。只要有构造函数,都会牵扯出一个大家庭,每个大家庭中,至少包含2个:
- 构造函数(妈妈)
- 负责反复创建多个相同结构的子对象
- 构造函数肚子里的属性,会成为将来子对象中的自由属性
- 原型对象(爸爸)
- 负责替该类型所有子对象集中保管共有的方法
- 原型对象有什么方法,子对象也就有什么方法----继承
- 构造函数(妈妈)
-
比如:内置类型Array:包含2部分
function Array(){... 内部代码 看不见 ...}
- 所有数组共用的函数,都放在Array.prototype中
- 如果想用的数组函数,原型对象中没有,可以自己手动想数组的原型对象中添加一个新函数,结果,将来所有数组的子对象,都可用这个自定义的公共函数
- 示例:为数组原型对象中添加求和函数sum
<script> // 需求:为数组添加一个可对数组内容求和的函数 Array.prototype.sum=function(){ // this:指将来调用这个函数的某个数组的子对象 var result=0; for(var i=0;i<this.length;i++){ result+=this[i]; } return result; } console.log(Array.prototype); //[sum: ƒ, constructor: ƒ, concat: ƒ, copyWithin: ƒ, fill: ƒ, …] var arr1=[1,2,3]; console.log(arr1.sum());//6 var arr2=[1,2,3,4,5]; console.log(arr2.sum());//15 </script>
-
- 原型链
-
什么是原型链:由多级父元素逐级继承形成的链式结构
-
保存着一个对象可用的所有属性和方法
-
控制着成员的使用顺序:先自有,再共有
-
示例:验证原型链
<script> function Student(sname,sage){ this.sname=sname; this.sage=sage; } Student.prototype.intr=function(){ console.log(`I'm ${this.sname},I'm ${this.sage}`) } var lilei=new Student("Li Lei",11); console.log(lilei);//Student {sname: "Li Lei", sage: 11} console.log(lilei.__proto__==Student.prototype);//true console.log(lilei.__proto__.__proto__==Object.prototype);//true console.log(Student.__proto__==Function.prototype);//true console.log(Student.__proto__.__proto__==Object.prototype);//true </script>
-
2. 多态
-
什么是多态:同一个函数,在不同情况下,表现出不同的状态
-
包括2种情况:重载和重写(override)
-
什么是重写:子对象中定义了和父对象中重名的成员
-
为什么重写:因为从父对象中继承来的属性或方法并不总是好用的
-
何时重写:只要从父对象继承来的某个成员不好用,就可以重写
-
如何:只要在子对象中,定义和父对象中名称相同的一个成员,从此子对象再使用这个成员时,都会优先使用自己定义的成员,而不再使用父对象的成员
-
示例:在自定义类型和对象中重写Object原型对象里的toString()方法
<script> function Student(sname,sage){ this.sname=sname; this.sage=sage; } // 希望所有学生类型的对象,都有toString()可用,可输出学生的属性值 // 在Student的原型对象中重写和父对象中同名的toString()方法 Student.prototype.toString=function(){ return `{ sname:${this.sname},sage:${this.sage} }` } var lilei=new Student("Li Lei",11); var obj={ x:1, y:2, // 希望obj也有好用的toString() toString:function(){ return `{x:${this.x},y:${this.y}}` } } var arr=[1,2,3]; var now=new Date(); console.log(lilei.toString());//{ sname:Li Lei,sage:11 } console.log(lilei);//Student {sname: "Li Lei", sage: 11} console.log(obj.toString());//{x:1,y:2} console.log(arr.toString());//1,2,3 console.log(now.toString());//Tue May 05 2020 20:38:23 GMT+0800 (中国标准时间) </script>
3. 自定义继承
如果子对象觉得整个父对象都不好用,可以换父对象:2种方法
- 只修改一个对象的父对象
-
不推荐写法:
子对象.__proto__=新父对象
问题:不是所有浏览器都开放__proto__
让我们随意使用 -
推荐的等效做法:
Object.setPrototypeOf(子对象,父对象)
-
示例:仅修改hmm的父对象
<script> function Student(sname,sage){ this.sname=sname; this.sage=sage; } Student.prototype.bal=9.9; Student.prototype.car="none"; var lilei=new Student("Li Lei",11); var hmm=new Student("Han Meimei",12); var father={ bal:100000000000000, car:"infiniti" } // hmm想用father中的成员,可让hmm继承father // hmm.__proto__=father; Object.setPrototypeOf(hmm,father); console.log(hmm.bal,hmm.car);//100000000000000 "infiniti" console.log(lilei.bal,lilei.car);//9.9 "none" </script>
-
- 同时修改多个子对象的父对象
-
只要更换构造函数的prototype属性,指向新的原型对象即可
构造函数.prototype=新原型对象;
-
时机:应该在创建子对象之前就要更换
好处:之后再创建的子对象都自动继承新的父对象
而已经创建的子对象所继承的父对象不变,仍是旧父对象
-
示例:同时更换lilei和hmm的父对象
<script> var father={ bal:100000000, car:"infiniti" } function Student(sname,sage){ this.sname=sname; this.sage=sage; } Student.prototype.bal=9.9; Student.prototype.car="none"; // 创建更换原型对象之前的子对象 var xiaoming=new Student("Xiao Ming",13); // 将构造函数的原型对象更换 Student.prototype=father; // 新原型对象的构造函数更换为Student father.constructor=Student; // 创建子对象 var lilei=new Student("Li Lei",11); var hmm=new Student("Han Meimei",12); console.log(xiaoming.bal,xiaoming.car);//9.9 "none" console.log(lilei.bal,lilei.car);//100000000 "infiniti" console.log(hmm.bal,hmm.car);//100000000 "infiniti" </script>
-
六、ES5
ECMAScript语言(js语言的核心语法)标准的第五个升级版本.
1. 严格模式
- 什么是严格模式:比普通js运行机制要求更严格的模式
- 为什么:旧的js语言存在很多广受诟病的缺陷
- 何时:所有的js程序,都需要运行在严格模式下
- 如何开启严格模式:在当前代码段的顶部添加字符串:
"use strict"
- 新要求:4个
- 禁止给未声明的变量赋值
- 旧js中:强行给未声明的变量赋值,会自动在全局创建该变量。-----极容易造成全局污染
- 示例:旧js中,非严格模式下给未声明的变量赋值
<script> // "use strict"//启用严格模式 function send(){ var gf; // 假设不小心把gf写成了qgf qgf="今晚308,w84u"; console.log(gf); } send(); console.log(qgf); // 如果未启用严格模式 // 输出undefined和今晚308,w84u // 如果启用严格模式 // 报错:qgf未定义 </script>
- 静默失败升级为错误
- 静默失败:执行不成功,但是还不报错
- 缺点:静默失败极其不便于调试
- 严格模式下:将所有的静默失败都升级为错误
- 优点:极其便于调试,避免歧义
- 示例:对比严格、非严格模式下执行错误的操作
<script> "use strict"//启用严格模式 var eric={ eid:1001,//只读 ename:"埃里克" } // 设置eid为只读 Object.defineProperty(eric,"eid",{ writable:false,//设置eric的eid属性为不可修改--只读 configurable:false//设置不允许再修改writable }) // 尝试修改eric的eid eric.eid=1002; console.log(eric); // 不启用严格模式 // 不报错 输出{eid: 1001, ename: "埃里克"} // 启用严格模式 // 报错:提示不可修改只读属性eid </script>
- 普通函数调用和匿名函数自调中的this默认值undefined,而不再指window
- 旧js:普通函数调用和匿名函数自调中的this默认指window
- 问题:容易导致全局污染
- 严格模式下:普通函数调用和匿名函数自调中的this指undefined,而不再指window
- 好处:大大减少了因为this导致的全局污染
- 示例:对比严格、非严格模式下的错误使用构造函数
<script> "use strict"//启用严格模式 function Student(sname,sage){ this.sname=sname; this.sage=sage; } // 正确使用构造函数的用法 // var lilei=new Student("Li Lei",11); // console.log(lilei); // 错误的使用构造函数的用法 var hmm=Student("Han Meimei",12); // Student前没有.也没有new,所以Student中的this暂时指向window // 相当于:window.sname="Han Meimei"; // 相当于:window.sage=12; console.log(hmm); console.log(window.sname); console.log(window.sage); // 非严格模式下输出: // undefined // Han Meimei // 12 // 严格模式下输出: // 报错:无法设置Student中未定义的属性sname </script>
- 禁止使用
arguments.callee
- 什么是
arguments.callee
:是在函数内部,获得当前函数本身的一种关键词 - 何时:递归
- 问题:如果在函数内递归调用时,写死当前函数的函数名,则一旦当前函数名改变,就必须同时修改函数体中写死的函数名,一旦漏写就报错----紧耦合
- 解决:在函数内用
arguments.callee
自动获得当前函数对象本身,直接用当前函数对象进行递归调用,与函数名无关 - 为什么严格模式要禁用
arguments.callee
:因为递归调用效率极低(重复计算量太大) - 所以严格模式强力不建议使用递归调用
arguments.callee
,使用就会报错 - 解决:多数递归调用都可以用循环来解决----但是难度较大
- 总结:但是改用递归还是首先选择递归----因为简单
除非递归在项目中确实影响效率了,才被迫找循环的方法替代 - 示例:使用递归实现斐波那契数列
<script> "use strict"//启用严格模式 // 斐波那契数列 // 1 1 2 3 5 8 13 21 34 55 // 1 2 3 4 5 6 7 8 9 10 // 数学公式:f(1)=f(2)=1,n>2是f(n)=f(n-1)+f(n-2) function f(n){ if(n<3){ return 1; }else{ var fun=arguments.callee;//自动获得当前函数本身 return fun(n-1)+fun(n-2); } } // 测试 console.log(f(10)); // 非严格模式下输出:55 // 严格模式下:报错 </script>
- 什么是
- 禁止给未声明的变量赋值
总结:
一、面向对象
-
封装: 创建对象 3种方法:
- 只创建一个对象,且已经知道对象的成员是什么:
var 对象名={ 属性名: 属性值, ... : ... , 方法名: function(){ ... this.属性名 ... } }
- 只创建一个对象,但是暂时不知道对象的成员: 2步
- 先创建一个空对象等着:
var 对象名={} //new Object()的简写
- 等知道对象的成员之后,再为对象强行添加新属性和方法:
对象.属性名=属性值
对象.方法=function(){ ... this.属性名 ...}
- 先创建一个空对象等着:
- 想反复创建多个相同结构的对象时: 2步:
- 先定义构造函数:
//因为构造函数是为了描述同一类型的多个对象统一的属性结构 //所以,构造函数名通常是一种类型的名称,比如学生,商品,订单,用户... function 类型名(形参变量列表){ this.属性名=形参变量; ... = ... //今后构造函数中不要包含方法定义 }
- 用new调用构造函数创建对象
var 对象名=new 类型名(属性值列表);
new 做了4件事:- 创建一个新的空对象
- 让子对象继承构造函数的原型对象(自动设置子对象的_ proto _指向构造函数的prototype对象)
- 调用构造函数时,同时将构造函数中的this临时替换为new正在创建的这个新对象
结果构造函数中的每一句话,都会强行给新对象添加构造函数早就规定好的统一的新属性 - 返回新对象地址,保存到等号左边的变量中
- 先定义构造函数:
- 只创建一个对象,且已经知道对象的成员是什么:
-
继承:
- 何时: 今后只要多个子对象共用的方法或属性值,只要集中放在原型对象中一份,所有子对象即可共用。
- 向原型对象中为所有子对象添加共有的方法
构造函数.prototype.共有方法名=function(){ ... this.属性名 ... }
-
多态:
重写: 如果从父对象继承来的个别成员不好用!就可在子对象中定义同名成员。结果,子对象再使用同名成员时,优先使用自己自有的同名成员。不再使用父对象不好用的同名成员。 -
自定义继承: 如果子对象觉得整个父对象都不好用,可认别的父对象当爹
- 只修改一个子对象的父对象:
- 不推荐:
子对象.__proto__=新父对象
- 推荐:
Object.setPrototypeOf(子对象, 新父对象)
- 不推荐:
- 如果更换该类型下所有子对象的父对象
构造函数.prototype=新父对象
- 时机: 最好在创建子对象之前,就要替换!
this的指向: 4种:
注意:判断this一定不要看定义在哪儿!!只看调用时.前是谁。- obj.fun() fun中的this->.前的obj对象
- new Fun() Fun中的this-> new正在创建的新对象
- 构造函数.prototype.fun=function(){ … } fun中的this->将来调用这个fun()函数的当前类型的子对象
- (function(){ … })() 或 普通函数调用fun() this默认->window
- 只修改一个子对象的父对象:
二、ES5
- 严格模式: 4个新要求:
(1). 禁止给未声明的变量赋值
(2). 静默失败升级为错误
(3). 匿名函数自调和普通函数调用中的this不再指window,而是undefined
(4). 禁用了arguments.callee,不推荐使用递归,但该写还是要写。
补充: 函数也是一个对象,既保存函数体,同时也有自己的属性
console.log(Student) 仅输出函数内容
console.dir(Student) 仅输出函数对象在内存中的属性
以上两个输出的内容合起来,才是一个完整的函数对象。
补充: 不要用for in
来遍历索引数组
因为in不仅遍历当前对象的所有自有成员,而且会延_ _proto_ _
继续遍历原型对象中深紫色的共有成员(忽略浅紫色的成员)。