面向对象
对象:现实中一切事物皆为对象
面向对象的实体:包含属性和行为的集合
js没有类的概念,叫做对象,思想上是基于对象的,js下定义对象还是用函数体进行定义,
基于对象和面向对象的区别
面向对象:自己设置图纸,照着图纸盖房子
基于对象:别人有一个房子,我照着房子创建
面向对象的三大特征:封装、继承、多态
封装
每一个对象就是一个封装,把具有共同属性和行为的事物放在一起
定义对象
var a -->饿汉式
a=11
var a=11 -->懒汉式
- 通过Object创建
var p=new Object()
p.name="小明"//创建变量
p.age=22
p.say=function(){//创建方法
console.log("我叫"+p.name,p.age)
}
p.say()//调用方法
- 简写
var p={
name:"小明",
age:23,
say:function(){
console.log(this.name,this.age)
}
}
p.say()
属性操作
configurable是否可以通过dalete删除
enumerable是否可以通过for-in进行遍历
writable是否可以修改属性
value给属性添加默认值
get获取属性的方法
set设置属性的方法
思想:限定陌生人不能操作,但是自己可以通过get和set访问
例如:
限定属性:
var obj={name:"小明",age:13}
Object.defineProperty(obj,"name",{
configurable:false,
writable:false,
value:"小兰"
})
设置get,set:
var obj={_name:"小明",age:13}
Object.defineProperty(obj,"name",{
configurable:true,
writable:false,
enumerable:true,
value:"小兰",
get:function(){//获得变量
return this._name+"111"
},
set:function(name){//修改变量
this._name=name
}
})
设置多个属性:
var obj={_name:"小明",age:13}
Object.defineProperties(obj,{
name:{writable:false,},
age:{enumerable:false}
})
注意: writable 不能和set一起用, enumerable不能和get一起用
自己创建get和set方法
var obj={
name:"小敏",
age:22,
getName:function(){
return this.name
},
getAge:function(){
return this.age
},
setName:function (name) {//外界
this.name=name//内部
},
setAge:function(age){
this.age=age
}
}
console.log(obj.getName(),obj.getAge())
obj.setName("小红")
obj.setAge(34)
console.log(obj.getName(),obj.getAge())
构造器
用来给对象赋初始值,构造器是一种特殊的函数,如果不写,js会自动分配一个默认无参的构造器
例如:
new关键字具体做了什么
- 创建了一个新对象
- 将构造器的作用域赋给新对象
- 用this关键字指向这个对象
- 通过proto寻找父类对象
function Emp(name,age) {//构造器 this.name=name this.age=age this.say=function(){ console.log(this.name,this.age) } } var e=new Emp("小丽",21)//调用构造器 e.say() console.log(e) var obj={} console.log(obj) e.name="小米" e.say()
- 返回新对象给调用者
私有变量
在对象下变量通过this关键字进行执行,表示通过地址找到对象下的属性和方法,如果要定义私有变量的属性和方法,可以不通过this关键字定义
例如:
function Emp(age){
var name="小明"//私有变量
this.age=age
}
var e=new Emp(22)
console.log(e.age)
console.log(e.name)
this指向
this关键字用于对象体内,用来指向对象下的属性和方法,方便调用
Global:在node环境下this的全局作用域指向Global
Window:在浏览器环境下this的全局作用域指向window
例如:
var a=11
console.log(a)
console.log(window.a)
console.log(this.a)
this的指向和在哪定义,在哪执行都没有关系
函数执行时,看函数前面是否有”.”,有的话,”.”前面是谁就指向谁,如果没有,就指向window
例如:
var name="我是window"
var obj={
name:"我是obj",
info:function(){
console.log(this.name)
}
}
console.log(obj.name)//obj
console.log(window.name)//window
console.log(name)//window
特殊情况
- 当this出现在匿名函数里,永远指向的是window
例如:
var name="我是window"
var obj={
name:"我是obj",
info:function(){//匿名函数
return function(){return this.name
}
}
}
var v= obj.info()()
console.log(v)//window
2.当给一个dom元素绑定一个事件的时候,事件执行的方法,this指向的是当前元素
btn.onclick=function(){
this.name=222
console.log(this)//指向btn按钮
console.log(this.name)
}
改变this指向
var name="我是window"
var obj={
name:"我是obj",
info:function(){//匿名函数
var th=this//在obj环境下存一个对象
return function(){
return th.name//window
}
}
}
var v= obj.info()()
console.log(v)//obj
继承
当子类继承父类对象时,可以直接拿到父类下的属性和方法,无需自己重新定义,减少代码写作,js下是单继承,用原型链实现
原型链
每个构造器都有一个原型对象,原型对象包含一个指向构造器的指针,name我们让原型对象指向另一个对象示例,就会实现机制
例如:
function Father(){
this.name="张三"
this.say=function(){
console.log(this.name)
}
}
function Son(){
this.age=23
}
Son.prototype=new Father()//继承
var s=new Son()
console.log(s)
console.log(s.age)
console.log(s.name)//可以调用父类对象的属性和方法
s.say()
多态-重写
子类和父类下有相同的方法。执行不同的操作,默认执行过程是,先找自己下的属性或方法执行,没有则去父类下找属性和方法,如果出现重名情况,则子类会覆盖父类的方法
function Father(){
this.name="张三"
this.say=function(){
console.log("唱京剧")
}
}
function Son(){
this.age=23
this.say=function(){
console.log("唱流行歌曲")
}
}
Son.prototype=new Father()//继承
var s=new Son()
console.log(s)
s.say()//调用自己的方法,覆盖了父类的
改变this指向的方法->call,apply,bind
三个函数本身是用来改变this指向的可以在任何对象下使用,在继承关系里,我们来调用父类构造器,只是定义上有区别
调用对象.call(改变的对象,值1,值2....)call定义多个参数
调用对象.apply(改变的对象,[值1,值2....])apply只定义对象和数组
调用对象.bind(改变的对象,值1,值2....)bind返回一个函数自己手动执行
例如:
function Father(name,sex){
this.name=name
this.sex=sex
}
function Son(name,sex,age){
this.age=age
// Father.call(this,name,sex)
// Father.apply(this,[name,sex])
Father.bind(this,name,sex)()
}
Son.prototype=new Father()//继承
var s=new Son("小明","男",23)
console.log(s.age)
console.log(s.name)//可以输出构造器里的赋值
console.log(s.sex)
console.log(s)
原型链的关键字
prototype:用来指向对象原型
_proto_:用来指向当前实例所属对象
constructor:用来指向构造器
实例有_proto_关键字,对象有prototype和constructor,构造器会有prototype
对象下的属性和方法
In判断属性是否属某个对象
Object.defineProperty()限定对象的属性操作
Object.defineProperties()限定对象的多个属性操作
Instanceof判断是否属于某个对象
Object.getPrototypeOf()获得对象原型
Object.isPrototypeOf()判断对象是否存在另一个原型上
Object.getOwnPropertyNames()返回对象下所有属性
Object.hasOwnProperty()判断对象自身是否有指定属性
Object.keys()返回对象可枚举的属性
Object.prototype.propertyIsEnumerable()判断是否是可枚举属性,返回布尔值
//console.log(s.prototype.propertyIsEnumerable("sex"))
Object.freeze()冻结一个对象,(只能查询)
//Object.freeze(obj)
Object.isFrozen()判断是否制冻结对象
Object.seal()密封一个对象,(只能查询和修改)
//Object.seal(obj)
Object.isSeal()判断是否是密封对象
Object.preventExtensions()让对象不可扩展(不能添加)
//Object.preventExtensions(obj)
Object.isExtensible()判断是否是不可扩展的对象
判断数组的方法有哪些
- Instanceof判断,但是会判断出父类对象
console.log(x instanceof Date)
- IsArray()Es5后新增的方法
console.log(Array.isArray(x))
- 通过构造器判断,每个对向都有自己的构造器
console.log(x.constructor==Array)(构造器)
- 通过prototype判断,如果是实例可以改变this指向
console.log(Object.prototype.toString.call(x)=="[object Array]")
- getPrototypeOf()返回原型进行判断
console.log(Object.getPrototypeOf(x)==Array.prototype)
判断对象的方法有哪些
同上,没有isArray()方法
typeof emp==”Object”//缺点null也会验证为对象
判断对象下的属性有哪些
- in 验证包括自身和原型的属性
function f(){
this.name="zhang"
}
function s(){
this.age=12
}
s.prototype=new f()
var v=new s()
console.log("name" in v)//true
- hasOwnProperty验证自身对象的属性
console.log(v.hasOwnProperty("name"))//false
- 判断是否是undefined,验证包括自身和原型的属性
console.log(typeof v.age=="undefined")//false
console.log(v.name==undefined)//false
遍历对象的方法有哪些
- for in遍历 包括自身可枚举的和原型的属性
for(var k in v){
console.log(k,v[k])
}
2.Object.keys()遍历自身属性
console.log(Object.keys(v))
3.Object.getOwnPropertyNames()遍历自身可枚举和不可枚举属性
console.log(Object.getOwnPropertyNames(v))
对象的复制
深拷贝和浅拷贝
浅拷贝:在引用变量赋值给另一个变量时,引用的是一个堆内存地址,当一个变量改变时,另一个变量跟着改变
深拷贝:在引用变量赋值给另一个变量时,开辟了一个新的堆内存地址,当一个变量改变时,另一个变量不改变
- 自己遍历,实现深拷贝
var obj={age:12,name:"zhang"}
var o={}
for(var v in obj){
o[v]=obj[v]
}
obj.name="ss"
console.log(obj)
console.log(o)
- 借助数组的函数,凡是可以返回新数组的函数,都可以执行赋值
var x=[1,2,3]
var y=x.concat()
x[0]=66
console.log(x)//[66, 2, 3]
console.log(y)// [1, 2, 3]
- 借助Object对象下的方法
var o=Object.assign(obj)//复制对象,实现浅拷贝
var o=Object.create(obj)//创建对象,实现深拷贝
- ES6下扩展运算符(...)
var x=[1,2,3]
var y=[...x]
x[0]=22
console.log(...x)//22 2 3
console.log(y)//[1,2,3]
- Json数据格式
JSON.parse()转化成对象
JSON.stringify()对象转化成json
//var obj={name:"li",agr:12,color:["red","blue"],y:{a:1,b:2,c:3}}
console.log(obj) console.log(JSON.stringify(obj))//{"name":"li","agr":12,"color":["red","blue"],"y":{"a":1,"b":2,"c":3}}
缺点:对象的属性如果是symbol类型是无法复制的
var obj={name:"li",agr:12,color:["red","blue"],y:{a:1,b:2,c:3}}
var json=JSON.stringify(obj)
var o=JSON.parse(json)
定义对象有哪些方式
- 工厂模式
function Person(name){
var o=new Object()
o.name=name
o.say=function(){
console.log(o.name)
}
return o
}
优点:能够解决创建多个对象的问题,兼容各个浏览器
缺点:没有解决对象识别的问题,不知道一个对象的具体类型
2.构造函数模式
function Person1(name){
this.name=name
this.say=function(){
console.log(o.name)
}
}
优点:可以创建多个对象解决对象识别问题
缺点:每个对象都会创建不同function实例,如果完成同样的任务就没有必要。
3.原型模式
function Person(){}
Person.prototype.name="xiaoming"
Person.prototype.say=function(){
console.log(o.name)}
优点:不用构造器传参,可以同时创建多个对象
缺点:原型的数据被共享,当属性被改变时,所有实例都改变
4.组合使用构造器和原型模式
function Person(name){
this.name=name
}
Person.prototype={
constuctor:Person,
say:function(){
console.log(this.name)
}
}
优点:每个实例都有自己的属性副本,同时又共享了方法,既可以节省内存,又可以赋值
5.动态原型模式
function Person(name){
this.name=name
if(typeof this.say!="function"){
Person.prototype.say=function(){
console.log(this.name)
}
}
}
优点:满足组合模式的所有优点,并且代码比较规整,统一初始化
6.寄生构造函数模式
function toStrarr(){
var arr=new Array()
arr.push.apply(arr,arguments)
arr.topstr=function(){
return this.join("|")
}
return arr
}
优点:类似于工厂模式,用来给原型对象定义新的属性和方法,而无需自己创建新对象,节省内存。
7.稳妥构造函数模式(最好的)
function Person(name){
var o=new Object
o.name=name
_age=23
if(typeof o.say!="function"){
o.say=function(){
console.log(this.name)
}
}
return o
}
优点:可以定义私有变量,可以共享函数,可以节省内存,可以构造器赋值,代码更安全。