this共有几种情况——>创建对象(js中创建对象共有几种方式)——>js中实现继承共有几种方式(继承)——>实现克隆共有几种方式(深克隆)
1、面向对象三大特点
封装、继承、多态
封装
第一种方式:直接用{}
如何访问对象中的成员:
对象名.属性名
对象名.方法名()
什么是this
每个函数自带的
专门指向正在调用当前函数的.前的对象
的关键字
总结:今后,对象中的方法,只要想使用当前对象自己的属性或其他方法时,都要使用this
第二种方式:用new:new Object()
如何:
var 对象名 = new Object() //先创建空对象{}
//强行给空对象中添加新属性和新方法
对象名.新属性=属性值
对象名.新方法=function(){...this.属性名...}
js中所有对象底层都是关联数组
关联数组 | 对象
[sname:"Han Meimei",sage:11] | {sname:"Han Meimei",sage:11}
I存储结构:都是名值对儿的组合
II 访问成员时:
标准写法都是:对象名/数组名["成员名"]
简写都是:对象名/数组名.成员名
III强行给不存在的位置赋值
不但不会报错,而且还会自动添加该属性
所以,js中,给对象添加一个新属性或新方法,都可以通过强行赋值的方式
IV强行访问不存在的位置的值
- 都不会报错,而是返回undefined
- 所以,可以使用 if(对象名.成员名 !== undefined) 判断一个对象中是否包含某个成员
V都可以用for in循环遍历
for(var 属性名 in 对象名/数组名){
//in 会自动依次取出对象或数组中每个:前的属性名,保存到in前的变量中
//如果想进一步获得属性值:
对象名/数组名[属性名]
}
强调:访问对象属性/数组元素,其实有3中方式:
- 标准:如果下标名后数组名时已知的,对象或数组["已知的属性名或下标名”]
- 简写:如果下标名后数组名时已知的,
- 如果下标名是数组,可简写为[已知的数字下标]
- 如果 下标名是非数组的字符串,可简写为.已知的属性名
- 如果属性名或下标不是写死的,来自一个变量,只能用[变量],不能用.
第三种方式:构造函数
问题:用{}一次只能创建一个对象
如果想创建多个相同结构的对象时,代码会重复很多
极其不便于将来的维护
解决:使用构造函数
什么是构造函数:描述同一类型的所有对象的统一结构的函数
为什么使用构造函数:代码重用
如何使用:2步
i 定义构造函数:
function 类型名(形参1,形参2,....) {
this.属性名 = 形参1
this.xxx = xxx
this.方法名 = function (){ ...this.属性名... }
}
ii使用构造函数 反复创建多个相同结构的对象
var 对象名 = new 类型名(实参值1,实参值2,....)
至少两件事:
创建指定类型的一个新对象
同时把实参值传给构造函数的形参变量
iii原理
调用构造函数时
new做了四件事
1、先创建一个新对象
2、???
3、调用构造函数
-将构造函数中的this->new 刚创建的新对象
-在构造函数内通过强行赋值方式,为新对象添加规定的属性和方法
4、返回新对象的地址,保存到=左边的变量里
总结:this 2种情况
- obj.fun() fun中的this—>.前的obj对象
- new Fun() Fun中的this—>new 创建的新对象
继承
问题:只要将方法定义放在构造函数中,那么每次执行new时都会执行function,就会反复创建相同函数的多个副本,浪费内存
解决:如果将来发现多个子对象都要使用相同的功能和属性值时,都可以使用继承解决
什么是继承:父对象中的成员,子对象无需重复创建,就可直接使用
什么是原型对象:替所有子对象集中保存共用属性值和方法的父对象
何时使用原型对象:只要发现多个子对象,都要使用相同的功能和属性值时,都可将相同的功能和属性值,集中定义在原型对象中
如何创建原型对象:不用自己创建,而是在定义构造函数时,程序自动附赠我们一个空的原型对象
如何找到原型对象:构造函数中都有一个自带的属性prototype,指向自己配对的原型对象
构造prototype
何时继承原型对象:
new的第二步自动让创建的子对象,继承构造函数的原型对象
new的第二步自动设置子对象的__proto__ 属性,指向构造函数的原型对象
总结:new的四步
- 创建一个新的空对象等待
- 让子对象继承构造函数的原型对象
- 调用构造函数,将this替换为新对象,通过强行赋值方式为新对象添加规定的属性
- 返回新对象地址
如何向原型对象中添加共用属性
- 只能强行赋值
- 构造函数.prototype.属性名 = 属性值
- 构造函数.prototype.方法名 = function(){...}
结果:
用子对象.访问任何成员时,js先在子对象内部查找自有的属性
如果子对象没有,则js引擎会自动沿__prototype__属性去父对象查找
如果在父对象中找到了想要的属性或方法,则和访问子对象的方法一样调用
所以
构造函数中一定不要包含方法的定义 ——方法内存
为什么父对象叫原型对象 prototype clone
this:第三种情况:原型对象中的this—>
this 3种:不看定义,只看调用
- obj.fun() this—>.前的obj
- new Fun() this—>new 创建的新对象
- 原型对象中共有的方法里的this—>将来调用这个共有方法的.前的那个子对象
内置类型的原型对象
什么是内置类型:ES标准中规定的,浏览器已经实现,我们可以直接使用的类型
包括:11种
String,Number,Boolean
Array,Date,RegExp
Math(不是类型,已经是一个{}对象)
Error
Function Object
global(全局作用域对象,在浏览器中被window代替)
什么是类型
每种类型一定有2部分组成
1)构造函数:负责创建该类型的子对象
2)原型对象:负责为该类型所有子对象集中保存共有的属性值和方法定义
11种类型中
有9种类型也都由构造函数和原型对象组成
也都可以new创建子对象
内置类型的原型对象
想知道新标准的ES中新增了哪些函数,都可看这个类型的原型对象
比如:Array.prototype,......
问题:如果经常使用一个功能但是原型对象中没有提供
解决:自定义一个函数
保存到原型对象中:
构造函数.prototype.方法 = function(){...}
结果:所有该类型的子对象,都可通过:
子对象.新方法() ——来调用自定义共有方法
原型链:
什么是原型链:由多级父对象逐级继承形成的链式结构
—保存着:一个对象可用的所有属性和方法
—控制着:属性和方法的使用顺序:
就近原则:先子级后父级
多态
什么是多态:同一个函数,在不同情况下表现出不同的状态
包括:2种
a.重载overload:同一个函数,输入不同的参数,执行不同的逻辑
b.重写override:(推翻、遮挡)
在子对象中定义一个和父对象中的成员同名的自有成员
从父对象继承来的个别成员不好用时,就可在子对象中定义同名成员,来覆盖父对象的同名成员
小结:
(谈谈你对面向对象的理解)
(1)封装:创建对象,2种:
如果只创建一个对象:{}
如果反复创建多个相同结构的对象:构造函数
(2)继承:所有子对象共用的属性值和方法,都要放在构造函数的原型对象中
(3)多态:重写:只要觉得从父对象继承来的成员不要用,都在子对象中重写同名成员
2 this共有几种情况
1.obj.fun() this—>.前的obj对象
2.new构造函数() this—>new正在创建的新对象
3.构造函数.prototype.fun = function(){}
因为将来原型对象中的方法,都是“子对象.fun()”方式调用
所以,this—>将来调用这个fun函数的.前的某个子对象
判断this 一定不要看在哪里定义,一定只看将来在哪里,如何被调用
4.fun()、匿名函数自调和回调函数中的this—>window
严格模式(usestrict)下,this—>undefined
因为这类函数调用时,前边即没有.,也没有new
比如:
(function(){ ...this... })
和
arr.forEach(
function(){ ....this....}
)
5. button.onclick = function(){}
或
button.addEventListener("click",function(){....})
DOM事件处理函数里的this—>当前正在触发的.前的DOM元素对象
强调:
这里不能改为箭头函数,一旦改为箭头函数,this指向外层的window
6.Vue中this默认都指当前Vue对象
<button @click="fun">
exportdefault{
methods:{
//vue中methods中的方法中的this默认都指当前vue组件对象
fun(e){ e.target }
//如果想获得当前触发事件的DOM元素对象,必须用$event关键字和e.target联合使用
}
}
7.箭头函数中的this—>当前函数之外最近的作用域中的this
—几乎所有匿名函数都可以用箭头函数简化
—箭头函数是对大多数匿名函数的简写
箭头函数可让函数内外的this保持一致!
构造函数的方法this指向window
对象的方法不能改为箭头函数
ES6提供的方法
var 对象名 = {
属性名:属性值,
方法名() { ....this.属性名... }
}
既不带function又不要加=>
箭头函数没有(不是)作用域
错误
箭头函数只让this,指向外部作用域的this
而箭头函数内的局部变量,依然只能在箭头函数内使用,出了箭头函数不能使用
所以,箭头函数依然是作用域,只不过影响this而已,不影响局部变量
箭头函数底层原理,改bind效果和箭头效果一样
结论:箭头函数底层相当于.bind()
call无法替换箭头函数中this
bind是永久绑定,不可替换
8. 可用call或apply,临时替换一次函数中的this
可用bind,永久替换函数中的this
替换this的3种情况
(1)一次调用函数时,临时替换一次this
call做的三件事
- 立刻调用一次点前的函数
- 自动将.前的函数中的this替换为指定的新对象
- 还能向要调用的函数中传实参值
(2)特殊
如果多个实参值是放在一个数组中给的
需要既替换this,又要拆散数组再传参
需要调用的函数.apply(替换this的对象,包含实参值的数组)
问题:call和apply只能临时替换一次this
如果反复调用函数,每次都要用call和apply临时替换this
(3)创建函数副本并永久绑定this
如何使用:
新函数(实参值...)
强调:
因为bind()已提前将指定对象替换新函数this,
所以后续每次调用新函数副本时,不需要替换this
bind()三件事
1)创建一模一样的新函数副本
2)永久替换this为指定对象
3)永久替换部分形参变量为固定的实参值
总结:
- 只在一次调用函数时,临时替换一次this:call
- 既要替换一次this,又要拆散数组再传参:apply
- 创建新函数副本,并永久绑定this:bind
3、创建对象:JS中创建对象共有几种方式
js中创建对象10种方式:
1、new Object() 缺点:步骤多
2、字面量:var 对象名 = { } 缺点:如果反复创建多个对象,代码会很冗余
3、工厂函数方式:
缺点:本质还是Object(),将来无法根据对象的原型对象准确判断对象的类型
function createPerson(name,age){
var o = new Object()
o.name = name
o.age = age
o.say = function(){
alert("",this.name)
}
return o
}
var p1 = createPerson("Lilei",11)
4、构造函数:2步
-先用构造函数定义一类对象的统一属性结构和方法
-再用new调用构造函数,反复创建相同结构属性,不同属性值的多个对象
function Student(name,age){
this.name = name;
this.age = age;
this.intr = function(){...}
}
var lilei = new Student("LiLei",11)
缺点:如果构造函数中包含方法,则重复创建,浪费内存
5、原型对象方式:先创建完全相同的对象,再给子对象添加个性化属性
缺点:步骤繁琐
function Person(){}
Person.prototype.name = "";
Person.prototype.age = 11;
Person.prototype .say = function(){}
var p1 = new Person();
p1.name = "LiLei"
6、混合模式:先创建完全相同的对象,再给子对象添加个性化属性
缺点:不符合面向对象封装的思想
function Person(name,age){
this.name = name
this.age = age
}
Person.prototype.say = function(){}
var p1 = new Person("Li Lei",11)
7、动态混合:先创建完全相同的对象,再给子对象添加个性化属性。
缺点:语义不符,其实if只在创建第一个对象时有意义
function Person(name,age){
this.name = name
this.age = age
if(Person.prototype.say = undefined){
Person.prototype.say = function(){}
}
}
8、寄生构造函数:构造函数里调用其他的构造函数(借鸡生蛋)
缺点:可读性差
function Person(){
this.name = name
this.age = age
if(Person.prototype.say = undefined){
Person.prototype.say = function(){}
}
}
function Student(name,age,className){
var p = new Person(name.age)
p.className = className
return p
}
var p1 = new Student("Li Lei",11,"初一2班")
9、ES6 class
class:程序中专门集中保存,一种类型的所有子对象
如何定义class
i 用class{}包裹原结构函数+原型对象方法
ii 原构造函数名升级为整个class的名字,所有构造函数统一更名为“constructor”
iii原型对象中的方法,不用再加prototype前缀,也不用 = function,直接简写为方法名(){ ... }
10、稳妥构造函数:闭包,不用this。不用new 安全,可靠
4、继承——JS中实现继承共有几种方式
7种
1、原型链式继承:将父类实例作为子类的原型
//定义一个父类型
function Animal(name){
this.name = name
this.say = function(){
console.log()
}
}
//原型对象方法
Animal.prototype.eat = function(food){
}
function Cat(){}
Cat.prototype = new Animal()
Cat.prototype.name = 'cat'var cat = new Cat()
2、构造函数继承
//定义一个父类型
function Animal(name){
this.name = name
this.say = function(){
console.log()
}
}
//原型对象方法
Animal.prototype.eat = function(food){}function Cat(name,age){
Animal.call(this.name)
this.age= age
}
3、实例继承
//定义一个父类型
function Animal(name){
this.name = name
this.say = function(){
console.log()
}
}
//原型对象方法
Animal.prototype.eat = function(food){}function Cat(name,age){
var o = new Animal(name)
o.age = age
return instance
}
4、拷贝继承:无法获取父类不可for in遍历的方法
//定义一个父类型
function Animal(name){
this.name = name
this.say = function(){
console.log()
}
}
//原型对象方法
Animal.prototype.eat = function(food){}
function Cat(name,age){
var animal = new Animal(name)
for(var p in animal){
Cat.prototype[p] = animal[p]
}
this.age = age
}
var cat = new Cat()
5、组合式继承
//定义一个父类型
function Animal(name){
this.name = name
this.say = function(){
console.log()
}
}
//原型对象方法
Animal.prototype.eat = function(food){}
function Cat(name,age){
Animal.call(this,name)
}
cat.prototype = new Animal()
Cat.prototype.constructor = Cat
var Cat = new Cat()
6、寄生组合继承
//定义一个父类型
function Animal(name){
this.name = name
this.say = function(){
console.log()
}
}
//原型对象方法
Animal.prototype.eat = function(food){}
function Cat(name,age){
Animal.call(this,name)
}
(function(){ //创建一个没有实例的方法
var Super = function(){}
Super.prototype = Animal.prototype //将实例作为子类的原型
Cat.prototype = new Super()
})()
var Cat = new Cat()
7、ES6 class extends继承
5、深克隆——实现克隆共有几种方式
1、创建一个空对象等着
2、遍历原对象中所有属性
3、遍历每一个属性
4、返回克隆好的新对象
浅克隆:只复制对象的第一级属性,如果对象的第一级属性中又包含引用类型,则只复制地址
浅克隆问题:如果对象中又包含引用类型的属性值,则导致克隆后,新旧对象依然共同引用同一个类型的对象属性值
结果:任意一方修改了引用类型的对象内容,都会导致另一方同时受影响
深克隆:不但复制对象的第一级属性,而且,即使对象中又包含引用类型的属性值,深克隆也会继续赋值内嵌类型的属性值
实现克隆对象的三种方法:
1、JSON.stringfy()以及JSON.parse()
无法深克隆undefined值和内嵌函数
2、Object.assign(target,source)
3、自定义递归克隆函数
function deepClone(){
let newObj;//定义一个变量,准备接新副本对象
//如果当前需要深拷贝的是一个引用类型对象
if(typeof target == 'object'){
if(Array.isArray(target)){//如果是一个数组
newObj = [] //将newObj赋值为一个数组,并遍历
for(let i in target){ //递归克隆数组中的每一项
newObj.push(deepClone(target[i]))
}
//判断如果当前的值为null,直接赋值为null
} else if(target === null){
newObj = []
//判断如果当前的值是一个正则表达式对象,直接赋值
}else if(target.constructor === RegExp){
newObj = target
}else{
//否则是普通对象,直接for in 循环递归遍历复制对象中的每个属性值
newObj= {}
for(let i in target){
newObj[i] = deepClone(target[i])
}
}
// 如果不是对象而是原始数据类型,那么直接赋值
}else{
newObj = target
}
return newObj
}