该文章的内容主要包括原型对象、原型继承、函数更细节的部分、闭包、递归和正则表达式
这是js系列的最后一篇文章。在开始看这篇文章之前最好先要掌握js基础语法。开头先给js的数据类型做了一个简单的梳理。
文章目录
JavaScript中的数据类型
基本类型(值类型)
- undefined
- null
- boolean
- number
- string
复杂类型(引用类型)
- Object
- Array
- Date
- RegExp
- Funtion
- 基本包装类型
- boolean
- number
- string
- 单体内置对象
- Global
- Math
类型检测
typeof
instanceof
Object.prototype.toString.call()
原型基础
引入
原型对象的作用
- 数据共享,节省空间(需要共享的卸载原型对象中,不需要的写在构造函数中)
- 为了实现继承
简单了解原型对象
问题:用构造函数创建实例化对象时,如果实例化对象中有相同的属性或方法,实例化多个对象会占多个内存,浪费空间
解决:通过原型的方式,把共享的方法单独提出来写
//构造函数
function Person(name,age){
this.name=name;
this.age=age;
}
//通过原型对象来添加方法和属性,共享方法和属性
Person.prototype.eat=function(){}
Person.prototype.height="160"
//实例化对象
var per1=new Person("小明",18)
var per2=new Person("小红",16)
per1.eat()//可调用
per2.eat()//可调用
构造函数,原型对象与实例对象的区别
构造函数可以实例化对象
构造函数中有个属性prototype,它是构造函数的原型对象;而构造函数的原型对象(
prototype
)中有一个构造器,这个构造器指向的就是该原型对象所在的构造函数原型对象有个构造器,就是构造函数本身
实例对象的原型对象(
__proto__
)指向的是构造函数的原型对象构造函数的原型对象(
prototype
)中的方法可以被实例对象直接访问原型链:实例对象和原型对象之间的关系,关系是通过原型(
__proto__
)来联系的。原型对象也有
__proto__
,它指向的是Objec原型对象,而Objec原型对象的__proto__
最后指向的是null。这在原型指向中有图解
三者的特点
-
构造函数和原型对象的方法都可以相互访问
//以原型对象举例 function Person(name,age){ this.name=name; this.age=age; } Person.prototype.eat=function(){ this.play() } Person.prototype.play=function(){}
-
实例对象使用的方法和属性,先在实例对象中找,没有再到原型对象里找
function Person(name,age){ this.name=name; this.age=age; } Person.prototype.age=20 //情况1:实例对象有该属性 var person=new Person("小明",18) console.log(person.age)//--->18 //情况2:实例对象没有该属性 var person=new Person("小明") console.log(person.age)//--->20
-
可以给内置对象的原型对象添加方法,会直接改变源码
-
如果构造函数没有某一属性,实例对象却去访问了,结果是
undefined
而不是报错function Person(age){ this.age=age } var per=new Person(){10} cosnole.log(per.name)//undefined //解释:因为js是一门动态类型的语言。如果对象没有,但只要obj.——点了,那么这个对象就相当于有了这个东西,只是没有属性值。
原型对象的简单写法
Person.prototype={
//注意:需要手动修改构造器的指向
constructor:Person
height:"160",
age:18,
eat:function(){}
}
原型指向
functin Person(age){
this.age=age
console.log(this)//------------------构造函数的this
}
Person.prototype.eat=function(){
console.log(this)//------------------原型对象的方法中的this
}
var per=new Person(10)
console.log(per)//-----------------------实例对象
//打印得出结论:构造函数中this是实例对象;原型对象中方法中的this是实例对象;
改变原型指向
//人的构造函数
function Person(){}
//人的原型对象方法
Person.prototype.eat=function(){}
//学生的构造函数
function Student(){}
//学生的原型对象方法
Student.prototype.say=function(){}
//改变原型指向
//方式1:
Student.prototype=new Person()
//实例化student对象
var stu=new Student()
stu.say()//报错
stu.eat()//成功
//人的构造函数
function Person(){}
//添加原型对象方法
Person.prototype.eat=function(){}
//改变原型指向
//方式2:
Person.prototype={
say:function(){}
}
//实例化Person对象
var per=new Person()
per.eat()//报错
per.say()//成功
如何在原型指向后添加方法?
//在上面方式一的基础上,只需要在指向改变后面添加方法即可。此时的say在Person实例对象中
Student.prototype=new Person()
Student.prototype.say=function(){}
stu.say()//成功
原型继承
继承就是类与类之间的关系。js没有类,但可以通过构造函数模拟类,然后通过原型来实现继承。继承也是为了实现数据共享
1. 改变原型指向
问题:同样是人,但是身份不一样,人们具有的属性和方法就会有差异。因为这些差异我就要重新创建一个构造函数来创建实例对象,太烦太占空间了!
解决:改变原型指向
//我需要继承的人共有的方法和属性
function Person(name,age){
this.name=name;
this.age=age
}
Person.prototype.eat=function(){console.log("人可以吃")}
Person.prototype.sleep=function(){console.log("人可以睡")}
//这是我作为学生特有的属性,我该怎么继承人的呢?
function Student(score){
this.score=score
}
//解决:改变学生的原型指向
Student.prototype=new Person("小明",18)
//给Student原型对象添加方法
Student.prototype.study=function(){console.log("学生要学习")}
//ok完成
//实例化对象
var stu=new Student(100)
//学生所继承的
console.log(stu.name)//小明
console.log(stu.age)//18
stu.eat()//人可以吃
stu.sleep()//人可以睡
//学生所特有的
console.log(stu.score)//100
stu.study()//学生要学习
2. 借用构造函数
问题:但是吧,如果我改变了指向,做到了继承。那如果我创建第二个学生?第三个学生呢?他们的名字和年龄是一样的!只有分数不一样。太可怕了,怎么办?
解决:借用构造函数:
构造函数名字.call(当前对象,属性,属性,...)
——解决属性值重复问题
//被继承的
function Person(name,age){
this.name=name;
this.age=age
}
Person.prototype.eat=function(){console.log("人可以吃")}
//我需要继承
function Student(name,age,score){
Person.call(this,name,age,score)
//这里相当于直接把Person的构造函数拿过来使用
//这里的this指向实例对象,上面讲过。表示实例对象在调用Person的构造函数
this.score=score
}
//ok完成
//实例化对象
var stu=new Student("小明",18,100)
console.log(stu.name)//小明
console.log(stu.age)//18
console.log(stu.score)//100
stu.eat()//报错,继承不了方法
3. 组合继承
问题:借用构造函数的方法不能继承方法,怎么办!
解决:组合继承:改变原型指向+借用构造函数
//被继承的
function Person(name,age){
this.name=name;
this.age=age
}
Person.prototype.eat=function(){console.log("人可以吃")}
//借用构造函数
function Student(name,age,score){
Person.call(this,name,age,score)
this.score=score
}
//改变原型指向,不传值
Student.prototype=new Person()
//给Student原型对象添加方法
Student.prototype.study=function(){console.log("学生要学习")}
//ok完成
//实例化对象
var stu=new Student("小明",18,100)
console.log(stu.name)//小明
console.log(stu.age)//18
console.log(stu.score)//100
stu.eat()//人可以吃
stu.study()//学生要学习
4. 拷贝继承
把一个对象中的属性或者方法直接复制到另一个对象中
var person1={
name:"小明",
age:20,
eat:function(){
console.log("吃东西")
}
}
var person2=person1
//仅仅改变了地址的指向
//真正的拷贝继承
function Person(){}
Person.prototype.name="小明"
Person.prototype.age=18
Person.prototype.eat=function(){}
var person={}
for(var key in Person.prototype){
person[key]=Person.prototype[key]
}
cosnole.log(person.name)//小明
person.eat()//成功
函数进阶
回顾
-
函数的声明
function f1(){}
-
函数表达式
var f1=function(){}
函数的不同调用方式
-
普通函数
function f1(){} f1()
-
构造函数
//通过new调用,创建对象 function F1(){} var f1=new F1()
-
对象的方法
function F1(){ this.eat=function(){} } var f1=new F1() f1.eat()
函数中的this
- 普通函数的this是谁?——window
对象.方法
中的this是谁?——实例对象- 定时器方法中的this是谁?——window
- 构造函数中的this是谁?——实例对象
- 原型对象方法中的this是谁?——实例对象
严格模式
//普通
function f1(){console.log(this)}
f1()//window
//严格模式
"use strict"
function f1(){console.log(this)}
f1()//undefined //你没有指明是谁调用的,我也不知
window.f1()//window //你指明了,那就window把
改变指向
-
apply和call
apply和call的第一个参数如果没有传入或者传入了null,对象的this就是window。
它们都可以调用函数,调用的时候改变指向,效果 一样。只是传入参数的方式不一样,apply是数组,call是一个一个的参数-
apply(对象,[参数1,参数2,...])
-
call(对象,参数1,参数2...])
f1.apply(null,[1,2]) f1.call(null,1,2)
-
-
bind
复制,即不用调用函数就能改变指向:
bind(obj,参数1,参数2)
//人的构造函数 function Person(age){ this.age=age } Person.prototype.go=function(){ console.log(this+"--"+this.age) } //学生的构造函数 function Student(age){ this.age=age } //实例化人与学生 var per=new Person(10) var stu=new Student(100) //复制一份人的实例对象,传入学生对象===>指向改变 var f=per.go.bind(stu)//[object object]--100
应用
//一个构造函数 function ShowRandom(){ this.number=parseInt(Math.random()*10+1) } //给原型对象添加两个方法 ShowRandom.prototype.show1=function{ //定时器里边的this.show2里的this是window //此时我想改变指向,改成实例对象而不是wimdow,这里就要用到.bind(this),这个时候,show2指向的就是实例对象。即bind里面写的是哪个对象,bind前面的方法就指向谁 window.setInterval(this.show2.bind(this),1000) } ShowRandom.prototype.show2=function{ console.log(this.number) } //实例对象 var sr=new ShowRandom() //调用 sr.show2()
函数也是对象
函数也是对象,但对象不一定是函数
对象中有
__proto__
原型,是对象
构造函数中有prototype
原型
//构造函数
function F1(){}
console.log(F1)//既有prototype,也有__proto__,即构造函数既是对象又是函数
//已知Math是内置对象
console.log(Math)//有__proto__但没有prototype,即对象不是函数
所有的函数实际上是Function的构造函数创建出来的实例对象
var f1=new Function()
数组中的函数调用
数组可以储存任何类型的数据
var arr=[
function(){console.log("我是f1")},
function(){console.log("我是f2")},
function(){console.log("我是f3")}
]
//回调函数:函数作为参数使用
arr.foreach(function(item){
item()
})
//我是f1 我是f2 我是f3
函数的成员
-
name
——函数名称,只读 -
arguments
——实参的个数 -
length
——形参的个数 -
caller
——函数调用者function f1(x,y){ console.log(f1.name)//f1 console.log(f1.arguments)//[10,20,...] console.log(f1.arguments.length)//3 console.log(f1.length)//2 console.log(f1.caller)//f2 } function f2(){ f1(10,20,30) } f2()
函数作为参数
//这是一个以函数为参数的函数
function f1(fn){
fn()
}
//给函数从传入匿名函数
f1(function(){})
//给函数传入命名函数,只需传入名字
function f2(){我是一个要被传进去的命名函数}
f1(f2)
应用——定时器传入函数
function f1(fn){
setInterval(function(){
console.log("定时器开始")
fn()
console.log("定时器结束")
},1000)
}
//传入匿名函数
f1(function(){console.log("我在中间")})
//打印输出
定时器开始
我在中间
定时器结束
函数作为返回值
function f1(){
console.log("我是f1")
return function(){console.log("我来了")}
}
f1()
//此时只能打印出:我是f1
//已知f1返回了一个匿名函数,那么我把这个返回函数赋值给一个变量,这个变量就是这个返回的函数
var f=f1()
//调用这个返回的函数
f()//打印出:我来了
闭包
闭包的作用:缓存数据,延长作用域链
函数闭包:在函数中有一个函数
function f1(){
num=10
function f2(){
console.log(num)
}
f2()
}
f1()
对象闭包:函数中有对象
function f1(){
var num=10;
var obj={
age:num
}
console.log(obj.age)
}
1()
应用
function f1(){
var num=10
return function(){
return num
}
}
//把返回的函数赋值给变量
var f=f1()
//把返回函数返回的值赋值给变量
var result=f()
console.log(result)//10
function f1(){
var num=10;
return function(){
num++;
return num
}
}
var f=f1()
console.log(f())//11
console.log(f())//12
console.log(f())//13
//解析:在这个例子中,函数f1只调用了一次,返回函数的num是在第一次调用f1的基础上处理的,即在num=10的基础上。之后处理的是返回函数自身,不受外部干扰
递归
函数中调用函数自己
function getSum(x){
if(x==1){
return 1;
}
return x+getSum(x-1)
}
getSum(5)//15
//解析:当你传5的时候-->5+getSum(4)
//此时这个getSum(4)就是-->4+getSum(3)
//这个getSum(3)就是-->3+getSum(2)
//这个getSum(2)就是-->2+getSum(1)
//这个getSum(1)就是1
//总的就是:5+4+3+2+1
浅拷贝
相当于把一个对象中的所有内容,复制一份给另一个对象。即把一个对象的地址给了另一个对象,两者指向相同。
var obj1={
name:"小明",
dog:["小黑","小白"]
}
var obj2={}
//写一个函数,把a对象中的所有属性复制到b对象
function extend(a,b){
for(var key in a){
b[key]=a[key]
}
}
//调用复制函数
extend(obj1,obj2)
consolo.log(obj1)//一样
console.log(obj2)//一样
深拷贝
把一个对象中的所有属性和方法,一个一个的找到并在另一个对象中开辟相应的地址,一个一个的储存到另一个对象中
var obj1={
name:"小明",
dog:["小黑","小白"]
pen:{
color:black;
price:5
}
}
var obj2={}
//写一个函数,把a对象中的所有属性复制到b对象
function extend(a,b){
for(var key in a){
//先把对象的属性的值放在item存储
var item=a[key];
//判断属性类型
//如果是数组
if(item instanceof Array){
//开辟一个新数组(数组)
b[key]=[]
//再把存的数组拷贝到新的空间去
exentd(item,b[key])
//下面同理
}else if(item instanceof Object){
b[key]={}
exentd(item,b[key])
}else{
//如果值不是数组也不是对象,即普通的数据,就直接把值直接拷贝给另一个对象
b[key]=item
}
}
}
//调用复制函数
extend(obj1,obj2)
consolo.log(obj1)//一样
console.log(obj2)//一样
//与浅拷贝不同的地方就是开辟了一个新地址
正则表达式
按照一定的规则组成一个表达式,这个表达式主要匹配字符串
元字符
-
.
——表示除了\n以外的任意一个字符 -
[]
——表示范围-
[0-9]
——表示0到9之间的任意一个数字。//注意:如果想表示100-200不能直接写成[100,200]而是: [1][0-9][0-9]
-
[a-z]
——表示所有小写字母中的任意一个 -
[A-Z]
——表示所有大写字母中的任意一个 -
[a-zA-Z]
——表示所有字母中的任意一个 -
[0-9a-zA-Z]
——表示所有数字或字母中的任意一个[]
还可以表示把元字符中的意义取消,如[.]
就只是一个.
-
-
|
——或者 -
()
——分组 提升优先级//要么是数字、要么是小写字母、要么是大写字母。其中小写字母优先 [0-9]|([a-z])|[A-Z] //分组 从左边开始计算 ([0-9])([a-z])([A-Z])
-
*
——表示前面的表达式出现了0次到多次[a-z][0-9]* "sgfja" //可以匹配,因为*包含了出现0次的情况
-
+
——表示前面的表达式出现了1次到多次[a-z][9]+//表示小写字母后面最少有一个9
-
?
——表示前面的表达式出现了0次或1次[4][a-z]?//表示有4且4后面有1个小写字母或者没有小写字母 "12e324jjk"//可以匹配
-
限定符——限定前面表达式出现的次数
-
{0,}
——表示前面的表达式出现了0次到多次,和*
一样 -
{1,}
——表示前面的表达式出现了1次到多次,和+
一样 -
{0,1}
——表示前面的表达式出现了1次到多次,和?
一样可以更明确前面表达式出现的次数 {5,10}//出现5到10次 {4}//只能4次
-
-
^
——表示以什么开始,或者是取非^[0-9]//以数字开头 ^[a-z]//以小写字母开头 [^0-9]//非数字 [^0-9a-zA-Z]//特殊符号
-
$
——表示以什么结束[0-9]$//以数字结束 ^[a-z][0-9]$//严格模式:是能是一个字母一个数字比如"a1"
-
\d
——数字中的任意一个,即[0-9] -
\D
——非数字中的一个 -
\s
——空白符的一个 -
\S
——非空白符的一个 -
\w
——非特殊符号 -
\W
——特殊符号
创建正则表达式对象
正则表达式要写在一对//中
-
通过构建函数创建对象
var reg=new RegExp(/\d{5}/)
-
通过字面量的方式创建
var reg=/\d{5}/
使用正则表达式
//创建对象,正则表达式要写在一对//里面
var reg=new RegExp(/\d{5}/)
//要检测的字符串
var str="我的电话是10086"
//调用正则表达式方法验证
reg.test(str)//返回的是布尔值
字符串中使用正则表达式
match()
——匹配符合正则表达式的并返回该字符串
var str="中国移动10086;中国联通10010"
var array=str.match(/\d{5}/)//这样只能匹配一个
var array=str.match(/\d{5}/g)//全局匹配 ["10086","10010"]
replace()
——替换符合正则表达式的字符
var str="小明睡懒觉了"
str=str.replace(/睡懒觉/,"起床")
console.log(str)//小明起床了
正则表达式的方法
-
test()
——用来查看正则表达式与指定的字符串是否匹配。返回true
或false
-
exec()
——在一个指定字符串中执行一个搜索匹配。返回一个结果数组或null//一个字符串 var str="中国移动10086;中国联通10010" //一个正则对象 var reg=/\d{5}/ //把exec返回的结果放在array变量中 var array=reg.exec(str)
其他
-
g
——表示全局模式匹配,在表达式后加 -
i
——表示忽略大小写 -
RegExp.$n——获取匹配后的第n组值,一个
()
表示一组
OK完结撒花~