JavaScript面向对象(四)
前面的内容我们经常会使用到this这个指针变量,之前我们只是大概的说了一下,今天我们就来具体看下
this关键字
this
它是一个关键字,也是一个指针,具体指的是什么则要看这个关键字指向了谁,它指向谁就是谁
对象中的this
之前我们在创建对象的时候使用过this,那么现在来看下这个情况
var name = "zhangsan";
var stu = {
name:"lisi",
sayHello:function(){
console.log(this.name);
}
在上面代码当中,我们看到了sayHello的方法里面有一个关键字this,那么这个里面的this指向的是谁??
那么现在一个问题,我们之前说的是this指向当前对象
问题:这个当前对象到底是谁?
1、在全局环境下,this指向的当前对象window
2、在自定义对象当中this指向的是当前调用这个方法的对象
根据上面的特点,全局环境的this指向的是window对象,我们可以补充以下几点知识
之前我们制作变量的时候,我们只是可以通过var关键字来声明变量,其实变量还可以有另外的理解方式
window就是浏览器窗口对象,也可以理解成是js中最大的对象,所有的东西都在window的下面,那么我们在全局环境中定义的变量我们就可以把它理解成事window对象的属性,而我们在全局环境下声明的函数,就可以理解成window对象的方法,所以我们平时在声明变量其实就是在给window对象加添属性
var name = "zhangsan";
上面的代码定义了一个全局变量,其实全局变量就是window对象的属性
window.name = "haha"
所以我们这里如果过使用 this.name
去调值,实际调出来的是全局变量 name
的值,因为全局环境下的this指向的是window对象,所以 this.name
指的也就是 window.name
,而 window.name
就是全局变量 name
var name = "zhangsan";
var stu = {
name:"lisi",
sayHello:function(){
console.log(this.name);
}
}
stu.sayHello(); //lisi
var s = stu.sayHello; //将sayHello的方法体赋值给s变量
s(); //调用s方法 zhangsan
代码分析:
var s = stu.sayHello;
相当于window.s = stu.sayHello
,所以接下来的调用s() 其实就相当于 window.s()
根据我们之前讲过的,对象方法里面的this指向调用这个方法的对象,现在这个方法是被window调用的,所以里面的this应该指向window
构造函数里面的this
函数也叫方法,在面对对象思想里面,函数就是方法
之前学习的时候,我们学过在构造函数里面也使用了this,它的this指向又是谁?
var name = "haha";
function Person(){
console.log(this.name);
}
构造函数本质上与普通函数没有去别的,关键在于它的调用方式,用new调用就是构造函数
Person() //本质上就是window.Person(),所有这里this指向的是window
new Person() //构造函数在调用的时候,体内的this指向当前创建的对象
经过上面的学习,我们可以发现,this的指向是可以改变,现在我们就看下在房里面的this的转移问题
方法调用方式不同,this 的指向也就不同
函数在不同调用形式决定this指向【重点】
刚刚我们收了通过构造函数的掉哦那个可以改变this的指向
先简单回顾一下函数的调用形式
1、方法名()
2、var 方法名 = function(){}()
3、!function(){}()
4、(function(){})()
5、new function(){}
除了上面几种调用方式至外,我们还又另外三种方法的调用形式,这三种方式也可以改变this的指向
通过call来调用方法
通过 方法名.call(this指向,...原来方法里面的参数)
var name = "zhangsan"; //window.name
function sayHello(str){
console.log(str);
console.log(this.name);
}
var stu1 = {
name:"小明"
};
var stu2 = {
name:"小芳"
}
sayHello("普通调用"); //window.sayHello() ,this指向window
sayHello.call(window,"我是通过call来调用的");
sayHello.call(stu1,"我也是通过call调用的");
sayHello.call(stu2,"我也是通过call调用的");
通过call的这种形式,我们可以手动改变这个方法体内的this指向
通过apply来调用方法
这种调用跟call调用是一模一样的,唯一不同的区别在于原来方法的参数赋值上面
var name = "zhangsan"; //window.name
function sayHello(str,x,y){
console.log(str);
console.log(this.name);
}
var stu1 = {
name:"小明"
};
var stu2 = {
name:"小芳"
}
sayHello("普通调用"); //window.sayHello() ,this指向window
sayHello.apply(window,["我是通过call来调用的",1,2]);
sayHello.apply(stu1,["我也是通过call调用的"]);
sayHello.apply(stu2,["我也是通过call调用的"]);
call于apply唯一的区别就是在apply传参的时候要变成一个数组
通过bind来调用函数
bind方法在调用的时候,不会立即执行原来的方法,而是会返回一个新的方法名,通过新的方法名再去调用原方法
var name = "zhangsan";
function abc(){
console.log(this.name);
}
var stu = {
name:"小明"
}
var x = abc.bind(stu); //返回一个新方法名
x();
x方法是通过abc.bind(stu)得到的,所以abc方法里面的this指向stu对象
刚刚上面执行的bind方法是一个没有参数的方法,如过有参数怎么办?
var name = "zhangsan";
function abc(x,y){
console.log(arguments);
console.log(this.name,x,y);
}
var stu = {
name:"小明"
}
abc(10,11)
var d = abc.bind(stu,99,100); //返回一个新方法名
d();
var e = abc.bind(stu,200);
e(300);
var f = abc.bind(stu);
f(500,501)
var g = abc.bind(stu,600,601);
g(602,603)
对象在内存中的储存特点
通过前几节内容的学习我们了解对象的封装(对象的创建)以及对象的继承,经过这些学习我们可以对对象的数据类型有几下几个总结
1、对象是一个复杂数据类型,在JavaScript当中所有的数据类型里面,有5种基础数据类型和1种复杂数据类型(复杂数据类型指的就是我们的对象)
2、基础数据类型我们可以使用typeof去检测,而复杂数据类型使用typeof检测出来的结果都是object,所以复杂数据类型我们使用另外一个关键字检测instanceof,我们new一个构造函数也称之为对象的实例化
变量的首次赋值初始化,对象的创建叫实例化
基础数据类型还复杂数据类型其都有自己本质上的一些特点,这个特点表面看到的是不同的,基本本质在内存结构上面
当程序在运行的时候所有的数据都会保存在计算机的内存当中,但是基础数据类型与复杂数据类型在保存的形式上是不一样的,我们先来一个简单例子
var a = "张三";
var b = a;
console.log(b); //张三
a = "李四";
console.log(b); //张三
通过上面代码我们可以得到一个结论,两个变量之间是互不干扰的,a与b之间是相互独立的(基础数据类型的特性)
再来看一段代码
var obj1 = {
userName : "张三",
age : 18
}
var obj2 = obj1;
console.log(obj2.userName); //张三
obj1.userName = "李四";
console.log(obj2.userName); //李四
通过上面的代码,我们又可以得到一个结论,两个对象之间通过赋值的形式是相互影响的(复杂数据类型的特点)
要弄清楚上面的这个问题,我们就要从内存的角度去考虑问题(数据结构)
在内存当中的数据结构分为4个大部分,主要是“ 堆,栈,链,表 ” ,其中堆和栈分是储存我们JavaScript数据的两个内存结构
首先从结论上来讲
1、基础数据类型的数据都是保存在内存的栈当中
2、复杂数据类型是存在内存的堆当中的
对象这种实际的数据都存在栈,通过引用内存地址,讲数据看起来像是储存到堆里面,即使上是引用到了对象里面,
所以我们的复杂数据类型又叫引用数据类型
复杂数据类型赋值是地址赋值
基础数据类型赋值时拷贝赋值
var arr1 = ["a","b","c","d","e"];
//数组也算是一种对象
var arr2 = arr1;
arr1[0] = "haha";
console.log(arr2);
在工作当中,我们会经常涉及到两个对象之间的拷贝互不影响,怎么办?
现在的话我们会接触到一个叫做对象的深拷贝
对象的深拷贝
var obj1 = {
userName : "zhangsan",
age:18
}
var obj2 = obj1;
上面的代码是将obj1拷贝了一份到obj2上面去,但是这种拷贝我们叫浅拷贝,因为这种拷贝只是单纯把内存地址拷贝了一份,那我们把原始数据一起拷贝就需要进行一次深拷贝了
简单的深拷贝
var obj1 = {
userName : "zhangsan",
age:18
}
Object.defineProperty(obj1,"sex",{
value:"男",
enumerable:false,
writable:false,
configurable:false
})
var obj2 = {};
var propertyNamesArr = Object.getOwnPropertyNames(obj1); //拿到obj1所有的属性名
//遍历所有的属性名然后开始赋值
propertyNamesArr.forEach(function(item,index,_arr){
//item代表遍历的每一项,就是obj1对象里面的每一个属性名
obj2[item] = obj1[item];
//左边就相当于是在给obj2空对象设置属性名,右边是将obj1当中属性值调出,然后赋值给左边
})
这里注意一点,不要使用for-in或者是Object.keys()去获取属性,因为这两种无法获取到 enumerable:false
的属性,所以我们只能用 Object.getOwnPropertyNames()
来获取属性
数组的深拷贝
var arr1 = ["a","b","c","d","e"];
//深拷贝的原理其实跟对象差不多
//第一种方式
// var arr2 = new Array(arr1.length);
// for(var i = 0; i < arr1.length; i++){
// arr2[i] = arr[i];
// }
//第二种
// var arr2 = arr1.map(function(item,index,_arr){
// //item代表遍历的数组每一项,我现在只需要把这一项直接返回出去就好,不设置条件,返回一个新数组
// return item;
// })
//第三种
var arr2 = arr1.concat();
通过 Object.assign()
来实现简单对象拷贝
他的语法格式:
var 目标对象 = Object.assign(目标对象,源对象)
他会将源对象拷贝到目标对象中去,同时也会返回一个拷贝好的对象
var obj1 = {
name:"zhangsan",
age:18
}
Object.defineProperty(obj1,"sex",{
value:"nan",
enumerable:false,
writable:true,
configurable:false
})
var obj2 = {}
var obj3 = Object.assign(obj2,obj1);
这个时候obj1就被拷贝出来到obj2当中,通过assign还把拷贝的结果返回了出来,我们可以用一个变量接收,但是Object.assign() 无法拷贝出enumerable:false的属性
复杂对象的深拷贝
所谓复杂对象的深拷贝就是在对象的里面又包含对象的情况下怎么进行拷贝
var classInfo = {
className:"H2203",
classAddress:"金融港",
students:["小明","小芳","小红","小紫"],
teacher:{
tName:"邢国忠",
sex:"男",
age:20
},
rank:null
}
对上面的对象执行一次深拷贝
function copyObj(oldObj){
//在接收参数的时候,我们要做一次判断,如果过是基本数据类型怎么办,如果过是null或者undefined怎么?
if(typeof oldObj != "object" || oldObj == null){
return oldObj;
}
if(Array.isArray(oldObj)){
//说明是个数组
var newObj = [];
}else{
var newObj = {};
}
Object.getOwnPropertyNames(oldObj).forEach(function(item,index,_arr){
if(typeof oldObj[item] == "object"){
//要么是对象,要么就是null
if(oldObj[item] === null){
newObj[item] = oldObj[item];
}else{
var _temp = copyObj(oldObj[item]); //执行递归
newObj[item] = _temp;
}
}else{
//基础类型,function
newObj[item] = oldObj[item];
}
});
return newObj;
}