函数:程序中保存一段可反复使用的代码片段的程序结构
应用地方:只要一段代码可能被多次反复使用,都要先封装在一个函数中,再调用函数,执行任务
. 如果函数内部必须某些数据才能正常执行时,必须定义形参!
. 如果函数外部的后续程序想继续使用函数的执行结果时,必须定义返回值return。
重载:一件事,根据传入不同的参数值,动态执行不同的逻辑时,都用重载
1 function 一个函数名(不写形参变量){ 2 //arguments对象自动接住所有实参值 3 if(arguments.length==0){ 4 执行一种逻辑 5 }else if(arguments.length==1){ 6 执行另一种逻辑 7 }else{ 8 执行其它逻辑 9 } 10 }View Code
(1). 什么是arguments对象:
a. 每个函数中自带的: 不用自己创建,可直接使用——js语言关键字
b. 专门自动保存所有传入函数的实参值的——内容
c. 类数组对象——存储结构
(2). 什么是类数组对象:
a. 长得像数组的对象
b. 类数组对象和数组相比:
1). 相同: 下标; .length; for循环遍历
2). 不同: 类型不同。
i. 类数组对象,是Object类型的对象
ii. 数组,是Array类型的
iii. 结果: 类数组对象无法使用数组家所有函数
(3). 如何借助arguments变通实现重载效果?
a. 不要定义多个函数,只定义一个函数,且不要定义任何形参!
b. 在函数内,通过判断arguments中的实参值个数或实参值内容,决定何时,执行何种不同的逻辑
匿名函数
.何时: 2种:
(1). 绝大多数回调函数,都用匿名函数
(2). 避免使用全局变量时,都用匿名函数自调
. 回调函数:
(1). 什么是: 我们自己定义的函数,但是自己不调用,而是交给别的函数,由别的函数自动调用!
(2). 比如: arr.sort(function(a,b){return a-b})
()中的function,虽然是我们自己定义,但是不是我们自己调用:
因为我们不知道何时调用,调用了几次,每次传的什么参数
()中的function,在sort函数内部,根据sort函数的需要自动调用,自动传参。
(3). 为什么: 节约内存:
(4). 原理:
a. 有名称的函数,函数名是一个变量,引用着函数对象。即使函数用完了,今后不用了,也因为变量名的引用,导致有名称的函数无法释放,占用内存,浪费内存!
b. 匿名函数,没有函数名!就没有变量引用着。所以用完就被释放了!
匿名函数自调:
(1). 问题: 全局变量极其不靠谱:
a. 将来多人协作开发时,所有人的程序,最终都会放在一起运行!
b. 如果刚好两个人的代码中都操作了相同名称的全局变量!就会造成全局污染!结果,两个人的功能都无法正常执行!
所以:项目中,禁止使用全局变量!
解决:
今后所有功能的js代码,都要用匿名函数自调包裹起来。
a. 如何:
//创建一个临时函数
(function(){
功能的原代码——该怎么写就怎么写,不需要做任何改变!
})();//立刻调用
强调: 匿名函数自调的前一条相邻的语句和匿名函数自调结束时,必须加";"
b. 好处:
1). 如果功能中用到个别变量,也会被圈在匿名函数内,不会成为全局变量!
2). 功能的原代码,不需要做任何改定!
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 //倒计时1——你写的代码 11 (function(){ 12 var i=5; 13 //启动周期性定时器,每隔一秒执行一次 14 //保存定时器序号,用于将来停止定时器之用 15 var timer1=setInterval(function(){ 16 //如果倒计时还没到0 17 if(i!=0){ 18 //就继续秒杀 19 console.log(`1号商品,秒杀进行中...${i}`); 20 //倒计时-1 21 i--; 22 }else{//否则,如果倒计时已经是0 23 //就秒杀结束,并停止定时器 24 console.log("1号商品秒杀结束!!!"); 25 clearInterval(timer1); 26 } 27 },1000);//每隔1秒 28 })();//必须加; 29 30 //倒计时2——别人写的功能中 31 (function(){ 32 var i=5; 33 //启动周期性定时器,每隔一秒执行一次 34 //保存定时器序号,用于将来停止定时器之用 35 var timer2=setInterval(function(){ 36 //如果倒计时还没到0 37 if(i!=0){ 38 //就继续秒杀 39 console.log(`2号商品,秒杀进行中...${i}`); 40 //倒计时-1 41 i--; 42 }else{//否则,如果倒计时已经是0 43 //就秒杀结束,并停止定时器 44 console.log("2号商品秒杀结束!!!"); 45 clearInterval(timer2); 46 } 47 },1000);//每隔1秒 48 })() 49 </script> 50 </body> 51 </html> 52 运行结果: 53 1号商品,秒杀进行中...5 54 2号商品,秒杀进行中...5 55 1号商品,秒杀进行中...4 56 2号商品,秒杀进行中...4 57 1号商品,秒杀进行中...3 58 2号商品,秒杀进行中...3 59 2号商品,秒杀进行中...2 60 1号商品,秒杀进行中...2 61 1号商品,秒杀进行中...1 62 2号商品,秒杀进行中...1 63 1号商品秒杀结束!!! 64 2号商品秒杀结束!!!View Code
强调:
所有回调函数优先使用匿名函数——用完释放,节约内存
所有js代码都应该保存在匿名函数自调中,禁止使用全局变量,避免全局污染!
作用域:
. 什么是作用域: 一个变量的可用范围
. 为什么: 避免不同范围的变量之间互相干扰!
. js中包括: 2级作用域:
(1). 全局作用域:
a. 什么是: 专门保存所有全局变量和全局函数的范围
b. 本质: 其实就是window对象。
c. 特点:
1). 优点: 可反复使用。
2). 缺点: 极容易被全局污染!——全家共用的一个水杯
(2). 函数作用域:
a. 什么是: 在一个function范围内,专门保存局部变量的范围
b. 特点:
1). 优点: 不会被外部篡改
2). 缺点: 不可重用!
c. 强调: 局部变量包含2种情况:
1). 在函数内var出的变量
2). 函数的形参变量虽然没有写var,但是也是局部变量!
. 作用域的使用顺序: 就近原则:
(1). 调用函数时,优先使用函数作用域内的局部变量
(2). 除非函数中没有创建想用的变量时,才*自动去全局找
作用域原理:
(1). 定义函数时:
a. function都相当于new Function,都会在内存中创建一个函数对象,来保存函数中的代码段。
b. 每个函数对象身上都有一个"好友列表",普通函数的好友列表中只有两个格子。
1). 离自己近的格子,暂时为空,为将来某个重要的对象预留
2). 离自己远的格子,引用着window对象。因为所有函数,最终都可以使用全局作用域中的变量。
(2). 调用函数时:
a. 先创建一个临时的函数作用域对象,集中保存函数中需要用到的所有局部变量。
b. 并将临时函数作用域对象的地址值保存到函数对象的好友列表中离自己近的格子中。
(3). 函数执行过程中:
a. 依次读取出函数中的每条语句开始执行。
b. 执行过程中,变量的使用顺序: 就近原则: 先局部,后全局。
1). 优先在函数作用域中查找局部变量使用
2). 函数作用域对象中没有,才*去window中查找变量使用.
(4). 函数调用后:
a. 函数对象会将自己的好友列表中离自己近的格子清空
b. 导致临时创建的函数作用域对象,没人要了
c. 导致函数作用域对象,连同作用域中的局部变量一起释放了!
——所以,局部变量不可重用!
作用域链:
(1). 什么是: 由多个作用域串联形成的存储结构
(2). 其实,上图中的"好友列表",学名就叫作用域链
(3). 作用域链保存着一个函数可用的所有变量(局部变量+全局变量)
(4). 作用域链还控制着变量的使用顺序: 就近原则,先局部后全局
闭包
问题: 全局变量和局部变量都有不可兼得的优缺点。
(1). 全局变量:
a. 优点: 重用
b. 缺点: 极其被污染、篡改
(2). 局部变量:
a. 优点: 不会被污染
b. 缺点: 不可重用
解决: 如果想重用一个变量,还不希望变量被篡改时,可用闭包来实现。
如何: 3步:
(1). 用一个外层函数包裹要保护的变量和使用变量的内层函数
(2). 外层函数将内层函数返回到外部
(3). 要使用内层函数的人必须调用外层函数,用变量接住返回出来的内层函数,才能继续使用内层函数。
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 //想定义一个函数,帮小孩儿管理零花钱 11 //每花一笔钱,就从总金额中减去花的钱 12 //1. 用外层函数包裹要保护的变量和内层函数 13 function mother(){ 14 var total=1000; 15 //2. 外层函数将内层函数返回到外部 16 return function(money){ 17 total-=money; 18 console.log(`还剩${total}`); 19 } 20 } 21 //3. 必须调用外层函数,用变量接住返回出来的内层函数 22 var pay=mother(); 23 //pay=function(money){ 24 // total-=money; 25 // console.log(`还剩${total}`); 26 //} 27 //如果pay变量接到的是一个函数对象,则可以加()调用。 28 pay(100);//900 29 total=0;//别人的程序,可能无意间 30 pay(100);//800 31 pay(100);//700 32 </script> 33 </body> 34 </html> 35 运行结果: 36 还剩900 37 还剩800 38 还剩700View Code
面向对象: 封装 继承 多态
封装
有3 种方式封装对象:
1. 用{}只创建一个对象:
2. 用new来创建:
(1). 如何: 2步:
a. 先创建一个空对象:var 对象名=new Object()
b. 再强行向空对象中添加新属性和新方法
对象名.新属性=值
对象名.新属性=function(){ ... this.属性名 ... }
(2). 揭示: js中所有对象底层其实都是关联数组:
a. 对象和关联数组都是名值对儿的组合。
b. 无论访问数组的元素,还是访问对象中的属性,都有两种方式:
1). 标准写法: 数组名/对象名["下标名或属性名"]
2). 简写: 数组名/对象名.下标明或属性名
3). 特例: 如果要访问的下标名或属性名不是写死的,来自于一个动态变化的变量,则既不能用.,也不能用[""],只能用[变量]
c. 访问数组或对象中不存在的成员,不会报错,而是返回undefined。
固定用法: 如何判断一个对象是否包含某个成员: 强行访问
数组或对象.下标名或属性名!==undefined 说明包含该成员
===undefined 说明不包含该成员
d. 强行给数组或对象中不存在的位置赋值,不会报错,而是自动为数组或对象添加该新的成员。
固定用法: 如何给数组或对象中添加一个新成员: 强行赋值!
数组或对象.下标名或属性名=新值!
e. 都可以用for in循环遍历每个成员
//in会依次取出数组或对象中每个下标名或属性名
//保存在in前的变量中
for(var 变量 in 数组或对象){
//可以通过"数组或对象[变量]"方式来访问数组或对象中每个成员
}
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 var leifeng={ 11 sname:"Li Lei", 12 sage:11, 13 intr:function(){ 14 console.log(`I'm ${this.sname}, I'm ${this.sage}`) 15 } 16 } 17 console.log(leifeng); 18 19 var leifeng=new Object(); 20 leifeng.sname="Li Lei"; 21 leifeng["sage"]=11; 22 leifeng.intr=function(){ 23 console.log(`I'm ${this.sname}, I'm ${this.sage}`) 24 } 25 //想获取雷风的className属性 26 console.log(leifeng.className); 27 //为leifeng强行添加className 28 leifeng.className="初一2班" 29 console.log(leifeng); 30 31 //想遍历雷风的每个属性 32 // 可以随意修改 33 for(var shuxingming in leifeng){ 34 //错误:in前的是一个变量,值随遍历不断变化,所以变量无论如何不能放在""中! 35 //console.log(leifeng["shuxingming"]); 36 //错误: .是[""]的简写,在底层同样会被翻译为[""]原版。所以,错误原因和第一种情况是一样的! 37 //console.log(leifeng.shuxingming); 38 //正确: 因为[]中需要一个字符串类型的下标名或属性名,而in前的变量,每次获得的刚好就是一个下标名或属性名的字符串。所以,刚好对象。 39 console.log(leifeng[shuxingming]); 40 } 41 </script> 42 </body> 43 </html> 44 运行结果: 45 {sname: "Li Lei", sage: 11, intr: ƒ} 46 undefined 47 {sname: "Li Lei", sage: 11, className: "初一2班", intr: ƒ} 48 Li Lei 49 11 50 ƒ (){ 51 console.log(`I'm ${this.sname}, I'm ${this.sage}`) 52 } 53 初一2班View Code
(4). 示例: 克隆一个对象:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 //地址:0x1234 11 var ran={ 12 toufa:`乌黑浓密`, 13 yanjing:`永远睁不开`, 14 bizi:`大鼻头`, 15 zuichun:`性感` 16 }; 17 //错误: 不会创建新对象,只是将原对象的地址值,赋值给另一个变量。对象的个数没有增长。所以,着不算克隆! 18 // var ran2=ran; 19 //正确: 20 // ran 21 // 0x1234 22 // ↓ 23 function clone(oldObj){ 24 //1. 创建空对象 25 // 0x9091 //0x9091 26 var newObj={}; //new Object() 27 //2. 遍历旧对象中每个属性 28 // 0x1234 29 for(var shuxingming in oldObj){ 30 //3. 每遍历旧对象中一个属性,就要为新对象添加同名属性,属性值也和旧属性值相同 31 //3.1 获得旧对象中当前属性的旧值 32 // 0x1234 33 var oldValue=oldObj[shuxingming]; 34 //3.2 为新对象添加同名属性,属性值为旧属性值 35 //0x9091 36 newObj[shuxingming]=oldValue; 37 } 38 //4. 返回新对象 39 // 0x9091 40 return newObj; 41 } 42 //调用clone()函数,并将旧对象ran传入clone函数中,在函数内克隆出一个新对象,返回出来。 43 // 0x9091 44 var ran2=clone(ran); 45 // 0x9091 46 console.log(ran2); 47 //判断ran2和ran是否是同一个对象 48 console.log(ran==ran2);//false 克隆成功! 49 // 0x1234 0x9091 50 </script> 51 </body> 52 </html> 53 运行结果: 54 {toufa: "乌黑浓密", yanjing: "永远睁不开", bizi: "大鼻头", zuichun: "性感"} 55 falseView Code
js最核心的本质: js的内存中,一切都是关联数组
3. 用构造函数反复创建多个相同结构的对象:
(1). 什么是构造函数: 描述同一类型所有对象的统一结构的函数
(2). 为什么: 重用
(3). 何时: 只要想反复创建同一类型的多个相同结构的对象,只是属性值不同而已时,可用构造函数来创建
(4). 如何: 2步
a. 先定义构造函数:
function 类型名(形参列表){
this.属性名=形参;
... = ... ;
this.方法名=function(){
... this.属性名 ...
}
}
强调: 只有前边加上this.的属性和方法,将来才能进入新对象中!
反之,前边没有this.的,都无法进入将来的新对象中。
b. 反复调用构造函数创建多个相同类型的对象
var 对象名=new 类型名(属性值列表)
构造函数必须用new来调用
5). 原理: new做了4件事:
a. 先创建一个新的空对象:
b. 自动设置新对象的_ _proto_ _隐藏属性指向构造函数的原型对象,形成继承关系!(带后面介绍继承详细讲解)
c. 调用构造函数,传入属性值:
1). new会将所有this都吸引到正在创建的新对象身上
2). 在构造函数内部:
this.属性名=属性值
↓
新对象.属性名=属性值
通过强行赋值的方式,将新属性和新方法强行添加到新对象中
d. new会返回新对象地址,保存到=左边的变量中!
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 //软件设计第一原则: don't repeat yourself 11 //先定义构造函数,描述所有学生的统一结构 12 // 学生类型 13 function Student(sname, sage){ 14 //所以学生都要有姓名和年龄属性 15 //因为将来创建对象时,才能知道具体每个学生的姓名和年龄,而且,每个学生的姓名和年龄各不相同,所以,定义构造函数时,暂时用变量占位。习惯上,变量名应该和属性名相同! 16 this.sname=sname; 17 this.sage=sage; 18 //所有学生都要会做自我介绍 19 this.intr=function(){ 20 console.log(`I'm ${this.sname}, I'm ${this.age}`) 21 } 22 } 23 24 var leifeng=new Student("Li Lei",11); 25 var hmm=new Student("Han Meimei",12); 26 27 console.log(leifeng); 28 console.log(hmm); 29 </script> 30 </body> 31 </html> 32 运行结果: 33 Student {sname: "Li Lei", sage: 11, intr: ƒ} 34 Student {sname: "Han Meimei", sage: 12, intr: ƒ}View Code
继承:
问题: 如果将方法定义放在构造函数内,那么每创建一个新对象,都会重复创建相同方法的副本——浪费内存!
解决:只要多个子对象,都要使用一个共同的功能时,可用继承方式来实现!
什么是继承: 父对象中的成员,子对象无需重复创建,就可直接使用!
如何: js中都是采用继承原型对象的方式来实现继承
(1). 什么是原型对象(prototype):每种类型中,替将来所有的子对象集中保存共有成员的父对象。
(2). 何时: 只要多个子对象都需要共用一个功能时,可将这个功能添加到原型对象中一份即可!
(3). 结果: 只要保存在原型对象中的成员,所有子对象无需重复创建,就可直接使用
(4). 如何创建原型对象:
a. 原型对象不用自己创建!
b. 只要我们每定义一个构造函数妈妈,都会自动附赠我们一个空的原型对象爸爸。
(5). 如何访问原型对象:
a. 每个构造函数内都自带一个隐藏的prototype属性,指向当前构造函数配对的原型对象。
b. 构造函数.prototype
(6). 如何向原型对象中添加共有成员: 只能强行赋值:
构造函数.prototype.共有成员=新值或function(){ ... }
从此!构造函数中就不应该再包含任何方法定义!
所有共有的方法,都要集中添加到原型对象中!
何时继承:
(1). 不用手工设置继承!
(2). 当用new调用构造函数,创建该类型的新对象时,new的第二步会自动设置新对象的_ _proto_ _隐藏属性指向构造函数的原型对象,形成继承关系!
(3). js规定,凡是从_ _proto_ _指出的引用关系,就是继承关系!
(4). 结果: 将来使用子对象访问一个成员时: 就近原则,先自己后原型
a. 都会先在子对象自己身体里找,如果找到要用的成员,就优先使用自己身体里的成员
b. 如果自己身体里没有要用的成员,则js引擎会自动延_ _proto_ _去父对象原型对象中查找父对象中保存的共有属性。如果在父对象中找到了想用的成员,则子对象一样可以直接用.访问!就和访问自己的成员一模一样!
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 function Student(sname, sage){ 11 this.sname=sname; 12 this.sage=sage; 13 //从此构造函数中就不应该包含方法定义。 14 //所有共用的方法都要集中添加到原型对象中。 15 } 16 //在Student构造函数配对儿的原型对象中为将来所有子对象添加共有的自我介绍方法 17 Student.prototype.intr=function(){ 18 console.log(`I'm ${this.sname}, I'm ${this.age}`) 19 } 20 //输出Student构造函数配对儿的附赠的原型对象 21 console.log(Student.prototype); 22 23 var leifeng=new Student("Li Lei",11); 24 var hmm=new Student("Han Meimei",12); 25 26 console.log(leifeng); 27 console.log(hmm); 28 </script> 29 </body> 30 </html> 31 运行结果: 32 {intr: ƒ, constructor: ƒ} 33 Student {sname: "Li Lei", sage: 11} 34 sage: 11 35 sname: "Li Lei" 36 __proto__: 37 intr: ƒ () 38 constructor: ƒ Student(sname, sage) 39 __proto__: Object 40 Student {sname: "Han Meimei", sage: 12} 41 sage: 12 42 sname: "Han Meimei" 43 __proto__: 44 intr: ƒ () 45 constructor: ƒ Student(sname, sage) 46 __proto__: ObjectView Code
. 自有属性和共有属性:
(1). 什么是:
a. 自有属性: 保存在对象内部,只归当前子对象独有的属性
b. 共有属性: 保存在原型对象中,归多个子对象共有的属性
(2). 相同: 获取属性值的方式完全一样!都可:
子对象.属性名
(3). 不同: 修改属性值时:
a. 如果要修改子对象的自有属性: 子对象.自有属性=新值
b. 如果要修改原型对象中的共有属性:
1). 错误: 子对象.共有属性=新值
i. 共有属性归所有子对象共有,绝不允许任何一个子对象擅自修改共有属性,否则会牵一发而动全身!
ii. 但是,如果硬用子对象修改共有属性,js也会自动变通执行:
不会修改共有属性,而是在当前子对象中添加一个和共有属性同名的自有属性。
iii. 从此!因为leifeng有了自有的className属性,就不会再和其它对象共用共有className属性,从此leifeng的className就无法再和其它对象保持一致!分道扬镳了!
2). 正确: 今后只要修改共有属性,都必须用"构造函数.prototype"方式来修改!
构造函数.prototype.共有属性=新值
示例: 获取或修改自有属性和共有属性:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 function Student(sname, sage){ 11 this.sname=sname; 12 this.sage=sage; 13 } 14 //在原型对象中替所有同学保存一个公共的班级名 15 Student.prototype.className="初一2班" 16 //创建两个学生: 17 var leifeng=new Student("Li Lei",11); 18 var hmm=new Student("Han Meimei",12); 19 //想获取两个学生的姓名 20 console.log(leifeng.sname, hmm.sname); 21 //想获取两个学生的班级名 22 console.log(leifeng.className, hmm.className); 23 24 //想修改leifeng的名字为李小雷 25 leifeng.sname="Li Xiaolei"; 26 //再次想获取两个学生的姓名 27 console.log(leifeng.sname, hmm.sname); 28 29 //过了一年,同学们都升了一级: 30 //错误: 31 // leifeng.className="初二2班"; 32 //正确: 33 Student.prototype.className="初二2班" 34 //再次想获取两个学生的班级名 35 console.log(leifeng.className, hmm.className); 36 //又过了一年,同学们又都升了一级: 37 //正确: 38 Student.prototype.className="初三2班" 39 //再次想获取两个学生的班级名 40 console.log(leifeng.className, hmm.className); 41 42 console.log(leifeng); 43 console.log(hmm); 44 //验证leifeng的爹__proto__和hmm的爹__proto__是不是同一个对象 45 console.log(leifeng.__proto__==hmm.__proto__);//true 46 </script> 47 </body> 48 </html> 49 运行结果: 50 Li Lei Han Meimei 51 初一2班 初一2班 52 Li Xiaolei Han Meimei 53 初二2班 初二2班 54 初三2班 初三2班 55 Student {sname: "Li Xiaolei", sage: 11} 56 sage: 11 57 sname: "Li Xiaolei" 58 __proto__: 59 className: "初三2班" 60 constructor: ƒ Student(sname, sage) 61 __proto__: Object 62 Student {sname: "Han Meimei", sage: 12} 63 sage: 12 64 sname: "Han Meimei" 65 __proto__: 66 className: "初三2班" 67 constructor: ƒ Student(sname, sage) 68 __proto__: Object 69 trueView Code
内置类型的原型对象:
(1). 问题: 不仅function Student(){},可以用new调用。而且还可以new Array(), new Date(), new RegExp()...为什么这么多东西都可以new呢?!
(2). 其实,凡是可以new的都是一个构造函数。而且,每个可以new的构造函数都有一个与之配对儿的原型对象。比如:
a. new Array():
有一个构造函数: function Array(){ ... }
还有一个原型对象: Array.prototype [ sort(), push(), ... ]
b. new Date():
有一个构造函数: function Date(){ ... }
还有一个原型对象: Date.prototype [ toLocaleString(), setDate(),... ]
(3). 总结: 凡是可以new的,都称为一种"类型",每种类型注定都包含2部分组成:
a. 构造函数: 专门用于创建该类型的一个子对象
b. 原型对象: 专门替所有子对象保存共有的成员
问题: 如果项目中经常需要用到的一个功能,但是原型对象没有提供!
解决: 只要自定义一个函数,放入该类型的原型对象中保存,就可保证,当前项目中该类型所有子对象都可共用这个自定义的函数
如何: 类型名.prototype.共有方法名=function(){ ... ...
强调: this->将来调用这个共有方法的.前的某个子对象
如何调用: 子对象.共有方法名()
示例: 为数组类型添加自定义的求和方法
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 //假设,在我的项目中经常需要对数组内容求和: 11 Array.prototype.sum=function(){ 12 console.log(`调用自定义的sum()`) 13 //对数组内容求和的固定套路: 复习第一阶段数组求和 14 //1. 先定义一个变量,保存临时的和,开局暂时是0 15 var result=0; 16 //2. 遍历原数组中每个元素(不要用for in) 17 //因为将来arr1.sum()时,希望这里的数组变成.前的arr1 18 //将来arr2.sum()时,又希望这里的数组变成.前的arr2 19 //所以,只有一种方法可以自动获得将来.前的对象: this 20 //这里:千万不要看this定义在哪里!只看将来调用sum()函数时,.前是哪个对象,this将来就指哪个对象 21 for(var i=0;i<this.length;i++){ 22 //3. 将当前元素值,累加到result变量上 23 result+=this[i]; 24 } 25 //4. 返回result中的累加结果即可 26 return result; 27 } 28 29 30 //希望 31 var arr1=[1,2,3]; 32 var arr2=[1,2,3,4,5]; 33 console.log(arr1.sum());//6 34 console.log(arr2.sum());//15 35 </script> 36 </body> 37 </html> 38 运行结果: 39 调用自定义的sum() 40 6 41 调用自定义的sum() 42 15View Code
代码中的解析:
for循环 和 for in循环差别
1. for循环,因为循环变量i从0开始,每次递增1,所以i只可能是数字,2. 所以不会超出数字的范围去遍历别的地方
for in循环的in,不但遍历所有的数字下标,而且会自动延_ _proto_ _原型链,继续遍历父对象中可用的成员,范围会超出数字下标的范围
3. 结论:
(1). 只要遍历数字下标的索引数组,都用普通for循环
(2). 只有遍历关联数组和对象属性时,才可用for in。因为此时,我们反而希望把这个对象可用的所有属性都变量到(无论是自有还是共有!)
原型链:
(1). 问题: 原型对象也是对象,那么原型对象有没有父对象?
(2). 答: js中任何对象都有_ _proto_ _属性指向它的父对象
(3). 什么是原型链: 由多级父对象,逐级继承,形成的链式结构,就称为原型链
(4). 一个对象的原型链上:
a. 保存着: 一个对象可以使用的所有属性
b. 控制着: 属性的使用顺序: 就近原则,先自有,后原型
多态:
. 什么是多态: 只要一个函数不同情况下表现出不同的状态。
. 包含2种情况:
(1). 重载(overload): 一件事,传入不同的参数,执行不同的逻辑(已讲)
(2). 重写(override):
. 什么是重写: 如果子对象觉得从父对象继承来的成员不好用!子对象就可以在自己内部创建一个和父对象成员同名的自有成员。
. 结果: 从此,这个子对象,优先使用自己的自有成员,不再使用父对象中不好用的共有成员!
. 示例: 为Student类型重写toString方法
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 function Student(sname, sage){ 11 this.sname=sname; 12 this.sage=sage; 13 } 14 //想让Student家的孩子,也有好用的toString 15 Student.prototype.toString=function(){ 16 return `sname:${this.sname}, sage:${this.sage}` 17 } 18 var leifeng=new Student("Li Lei",11); 19 var arr=[1,2,3]; 20 var now=new Date(); 21 console.log(leifeng.toString()) 22 console.log(arr.toString()) 23 console.log(now.toString()) 24 </script> 25 </body> 26 </html> 27 运行结果: 28 sname:Li Lei, sage:11 29 1,2,3 30 Mon Feb 01 2021 18:21:27 GMT+0800 (中国标准时间)View Code
. 自定义继承
问题: 如果父对象中只是个别成员不好用,我可以重写。但是如果整个父对象,几乎都不能用!怎么办?
解决: 自定义继承关系— —换爹
如何: 2种:
(1). 只更换一个子对象的父对象:
a. 子对象._ _proto_ _=新父对象
b. 问题: _ _proto_ _是浅紫色,意为不太想让人随便使用——不保险
c. 解决: 只要修改一个子对象的父对象:
Object.setPrototypeOf( 子对象, 父对象 ) //和_ _proto_ _效果一样!
设置原型对象的 设置 子对象 的原型对象为 父对象
示例: 只修改leifeng的父对象:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 function Student(sname,sage){ 11 this.sname=sname; 12 this.sage=sage; 13 } 14 var leifeng=new Student("Li Lei",18); 15 var hmm=new Student("Han Meimei",19); 16 17 //诱惑: 18 var father={ 19 money:10000000000000, 20 car:"infiniti" 21 } 22 //leifeng想,要是能认father当爹! 23 // leifeng.__proto__=father; 24 Object.setPrototypeOf(leifeng,father); 25 26 console.log(leifeng.money, leifeng.car); 27 console.log(hmm.money, hmm.car); 28 29 console.log(leifeng); 30 console.log(hmm); 31 </script> 32 </body> 33 </html> 34 运行结果: 35 10000000000000 "infiniti" 36 undefined undefined 37 Student {sname: "Li Lei", sage: 18} 38 sage: 18 39 sname: "Li Lei" 40 __proto__: 41 car: "infiniti" 42 money: 10000000000000 43 __proto__: Object 44 45 Student {sname: "Han Meimei", sage: 19} 46 sage: 19 47 sname: "Han Meimei" 48 __proto__: 49 constructor: ƒ Student(sname,sage) 50 __proto__: ObjectView Code
(2). 想批量更换多个子对象的父对象:
a. 只要更换构造函数的原型对象为新的父对象,则将来创建的所有子对象,都自动继承新的父元素
构造函数.prototype=新父对象
b. 问题: 如果在已经创建了子对象之后,才换爹,导致旧子对象无法更换新爹,依然继承旧爹。只有新创建的子对象,才能继承新爹。
c. 解决: 如果要更换构造函数的原型对象,必须在创建子对象之前更换!
d. 示例: 更换所有子对象的父对象
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 function Student(sname,sage){ 11 this.sname=sname; 12 this.sage=sage; 13 } 14 15 //诱惑: 16 var father={ 17 money:10000000000000, 18 car:"infiniti" 19 } 20 21 //想让今后的孩子都继承新的父对象: 22 Student.prototype=father; 23 24 var leifeng=new Student("Li Lei",18); 25 var hmm=new Student("Han Meimei",19); 26 27 console.log(leifeng.money, leifeng.car); 28 console.log(hmm.money, hmm.car); 29 30 console.log(leifeng); 31 console.log(hmm); 32 </script> 33 </body> 34 </html> 35 运行结果: 36 10000000000000 "infiniti" 37 10000000000000 "infiniti" 38 Student {sname: "Li Lei", sage: 18} 39 sage: 18 40 sname: "Li Lei" 41 __proto__: 42 car: "infiniti" 43 money: 10000000000000 44 __proto__: Object 45 Student {sname: "Han Meimei", sage: 19} 46 sage: 19 47 sname: "Han Meimei" 48 __proto__: 49 car: "infiniti" 50 money: 10000000000000 51 __proto__: ObjectView Code
面向对象小总结:
1. 只要使用面向对象,都要先创建对象——封装
(1). 如果只创建一个对象,用{}
(2). 如果反复创建多个相同结构的对象,用构造函数
2. 今后,只要一个类型中所有子对象共用的属性值或方法,都要集中保存在原型对象中——继承
3. 只要从父对象继承来的成员不好用,就可以重写!——多态
4. 如果整个父对象都不是想要的,就可以修改继承关系——换爹