【JS从入门到精通】05-对象
对象
JS中数据类型
- String 字符串
- Number数值
- Boolean 布尔值
- Null空值
- Undefined 未定义
以上这五种类型属于基本数据类型,以后我们看到的值只要不是上边的5种,全都是对象
这是创建对象的最简单的方法。
使用对象文字,您可以在一条语句中定义和创建对象。对象文字指的是花括号 {} 中的名称:值对(比如 age:18)。
//情况2:有可能在开发时候不确定应该存入什么属性和方法,都是根据实际程序的需要来存入
var obj = {}; //等号右侧如果是引用数据类型,则左侧变量保存的则是这个对象的内存地址值(每一个对象的内存地址值都是唯一的)
//属性
obj.name = "李港";
obj.age = 18;
obj.sex = "男";
//方法
obj.study = function(address,who){
return '酷爱在'+address+'和'+who+'学习';
}
console.log(obj);
//调用方法
console.log(obj.study('教室里','同学们'));
var obj1 = {};
obj1.name = "杨晨涛";
obj1.sex = "男";
obj1.age = 18;
//方法
obj1.study = function(address,who){
return '酷爱在'+address+'和'+who+'学习';
}
console.log(obj1);
//调用方法
console.log(obj1.study('教室里','同学们'));
//比较
console.log(obj == obj1); //false
//情况1:在声明的期间知道应该存放哪一些属性和方法
var obj = {
//属性
name:'张三',
age:23,
sex:"男",
star:'白羊座',
//方法
eat:function(){
console.log('喜欢吃饭');
},
sleep:function(){
console.log('喜欢睡觉');
},
study:function(){
console.log('喜欢学习');
},
love:function(address){
console.log('喜欢'+address+'谈恋爱');
}
}
console.log(obj);
//获取对象的属性
console.log(obj.name); //张三
console.log(obj['age']); // 23
//调用对象的方法
obj.love('家');
obj.love('咖啡馆');
obj.love('公园');
1.2 使用new关键字创建对象
var obj = new Object();
给对象添加属性
obj.name = '晓沫';
obj.age = 18;
obj.gender = "女";
给对象添加方法
obj.buy = function(){
console.log('晓沫爱购物~~~');
}
调用方法
obj.buy()
1.3 使用工厂类创建对象
之前的两种创建对象方式,大家不难发现,我们只能一个对象一个对象的创建,当想要批量创建多个对象的时候,代码量就会很多,所以我们需要一个可以重复做创建对象的一个函数,辅助我们批量创建对象,那么工厂类的核心其实就是利用函数达到功能复用的效果,当然函数名要根据实际功能来命名。
function createPerson(name,age,sex){
//1.利用new关键字来创建空对象
var obj = new Object(); //{}
//2.给这个对象添加属性和方法
obj.name = name; //具体哪一个对象,实际的值来自于函数的实际参数
obj.age = age;
obj.sex = sex;
obj.study = function(){
console.log('学习');
}
//3.返回创建好的对象
return obj;
}
调用函数,可以创建多个对象
var p1 = createPerson('晓沫',18,'女');
var p2 = createPerson('小明',18,'男');
1.4 使用构造函数创建对象
使用new运算符调用构造函数,可以构造一个实例对象
用法如下var objectName = new functionName (args)
参数说明如下:
objectName:返回的实例对象的名字
FunctionName:构造函数,与普通函数基本相同
args:实例对象初始化配置参数列表
用大写首字母对构造器函数命名是个好习惯。
-
在堆内存中开辟了一个空间,存储该对象
-
this指向的是当前的实例对象
-
this.属性的方式给当前对象添加属性或者方法
-
不需要return,直接将对象返回
-
构造函数会直接将对象给变量,而不是通过return返回的
1.4.1 创建构造器
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
1.4.2 调用构造器
通过 new 关键词调用构造器函数可以创建相同类型的对象:
var p1 = new Person("cuihua", 18, "男");
var p2 = new Person("gangtie", 19, "女");
var p3 = new Person("huahua",20,"x");
1.4.3 为对象添加属性
为已有的对象添加新属性:
p1.hobby = "eat";
//新属性被添加到 p1,不是任何其他 person 对象
1.4.4 为对象添加方法
为已有的对象添加新方法:
p1.hobby = function () {
return "drink";
};
//新方法被添加到 p1,不是任何其他 person 对象。
总:
//在函数的内部有一个非常关键的对象引用(地址值):this
function createCup(color,brand,price,texture){
//第一次调用函数,则this引用了对象的地址值0x11
//引用地址值的目的就是为了能够操作这个地址下的对象
//给0x11地址值下的对象里面添加了color、brand、price、texture四个属性和WithTheLiquid方法
//添加属性
this.color = color;
this.brand = brand;
this.price = price;
this.texture = texture;
this.color = '白色';
//添加方法
this.WithTheLiquid = function(){
console.log('装液体');
}
//第二次调用函数,则this引用了对象的地址值0x66
//给0x66地址值下的对象里面添加了color、brand、price、texture四个属性和WithTheLiquid方法
}
//只要是函数名前面出现了new关键字,则是在堆内存中创建一个空对象
var c1 = new createCup('粉色','crwhre',30,'玻璃');// {} 分配一个内存地址 0x11
var c2 = new createCup('红色','crwhre',35,'塑料');// {} 分配一个内存地址 0x66
var c3 = new createCup('黄色','crwhre',37,'不锈钢');// {} 分配了一个内存地址 0x88
console.log(c1);
console.log(c2);
console.log(c3);
var obj = {a:1,b:2,a:3};
console.log(obj);
思考:
1.4.5 内建JavaScript构造器
JavaScript 提供用于原始对象的构造器:
var x1 = new Object(); // 一个新的 Object 对象
var x2 = new String(); // 一个新的 String 对象
var x3 = new Number(); // 一个新的 Number 对象
var x4 = new Boolean(); // 一个新的 Boolean 对象
var x5 = new Array(); // 一个新的 Array 对象
var x6 = new RegExp(); // 一个新的 RegExp 对象
var x7 = new Function(); // 一个新的 Function 对象
var x8 = new Date(); // 一个新的 Date 对象
Math() 对象不再此列。Math 是全局对象。new 关键词不可用于 Math。
function Person(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
this.eat = function(){
console.log(this.name+'吃卤煮~~');
}
}
var person = new Person('大沫沫',18,'女');
思考:
如果在构造函数中return了一个基本数据类型和一个引用类型对象,那么构造方法返回的是什么?
结论:
return后面无论返回哪一个基本数据类型数据,都是新创建的新的对象,与返回值无关
return后面如果返回引用数据类型,则得到就是return后面的数据,与创建的新对象就无关了
new关键字具体做了什么?
属性指的是与 JavaScript 对象相关的值,通常可以被修改、添加和删除,
JavaScript 对象是无序属性的集合。
-
对象的属性是由键值对组成的
-
每个键值对,键和值之间用 :分隔 例如:name:'zs'
-
所有的键都被称作是这个对象的属性名,值叫做属性值
-
对象的属性,本质上都是字符串,只不过大部分属性是可以省略引号不写的;
少数情况会出现不符合命名规范的,就必须带引号
例如:'man-type':'渣男' 这个属性名必须使用引号;属性名不符合命名规范就得带引号
var obj = {
name:'小明',
'age':18,
gender:'female',
play:function(){
console.log('爱玩~');
},
'character-type':'渣男'//这个属性名必须使用引号;属性名不符合命名规范就得带引号
};
2.2 属性的增和改
通过简单的赋值,向已存在的对象添加新属性,假设 person 对象已存在 - 那么您可以为其添加新属性:
-
属性的添加的点语法
点语法,写起来简单,但是某些场合无法使用.去操作
obj.weight = 90; 有则更改,无则添加
下面两种情况必须使用[],其余的都可以使用.语法
obj.character-type = '嘿嘿';
无法操作,因为.后面的属性名不合法,只能使用[]去操作
var a = 'aaa'; obj.a = 'bbb'; //但是如果obj[a] = 'bbb' [a]就会被等价替换成'aaa' {'aaa':'bbb'}
本意是要以变量当中的值为属性,添加一个‘bbb’的值 ‘aaa’:'bbb';
这样写最终会把a当做对象的属性去操作,完全等价于obj['a'] = 'bbb'
如果是要使用变量当中的值为属性,那么.语法也没办法操作;
-
中括号语法
-
obj['weight'] = 100;
有则更改,无则添加 -
obj['character-type'] = '嘿嘿';
当对象的属性是不合法的名字必须使用[] -
var a = true; obj[a] = 'bbb';
如果我们需要以变量当中的值为属性去操作对象的话,必须使用[]
-
2.3 属性的删除
delete 关键词从对象中删除属性:
-
delete obj.age;
-
delete obj['character-type'];
-
delete obj[a];
-
delete 关键词会同时删除属性的值和属性本身。
-
删除完成后,属性在被添加回来之前是无法使用的。
-
delete 操作符被设计用于对象属性。它对变量或函数没有影响。
2.4 属性的查询
var person = {name:"red romance", age:18, color:"yellow"};
1. objectName.property eg: person.age
2. objectName["property"] eg: person["age"]
3. objectName[expression] eg: x = "age"; person[x]
2.5 for..in语句遍历对象属性
for...in 循环中的代码块会为每个属性执行一次。
var person = {name:"Barry", age:18};
for(var x in person) {
console.log(x,person[x]); //键名键值
console.log(x) //键名
console.log(person[x]) //键值
}
3.1 创建对象方法
study:function(){
//写具体的业务逻辑代码
}
3.2 访问对象方法
objectName.study();
var person = {
name : "Barry",
age : 18,
getname : function() {
return "诸葛钢铁";
}
};
console.log(person.getname());
练习:创建一个可以描述自己的对象,其中包括(姓名,年龄,喜欢做的事情)
思考:如果调用方法的时候没有添加圆括号,会发生什么?
答:会得到这个对象键名对应的键值function(){}结构,并不会得到函数体中的内容
3.3 this关键字 [非常重要]
this是一个内置的对象,存储的是对象的地址值。
this经常在函数内部去使用,在函数外部统统代表的是window对象。
this这个变量当中存储的是一个对象的地址,this主要代表的是这个函数的执行者或者调用者是谁。
要知道,函数中this的指向在函数定义时是决定不了的,只有在函数执行时才能决定指向谁。
实际上,this主要代表的是这个函数的执行者或者调用者,也就是说,谁调用函数,this就指向谁。
<script>
字符串中的方法:
ES5:
1.charAt
2.charCodeAt
3.concat
4.fromCharCode
5.indexOf
6.lastIndexOf
7.localeCompare
8.slice、substr、substring
9.split
10.toLowerCase、toUpperCase
11.replace、match、search
ES6:
1.includes
2.startsWith
3.endsWith
4.repeat
</script>
<script>
var test = "Hello World";
var test1 = test.toUpperCase();
console.log(test1);
</script>
ES5方法
//charAt()可以兼容于任何的浏览器 [下标]的方式是在IE7以下的浏览器无法正常使用 undefined
var str = '1*234567890123A';
console.log(str.charAt(1));
//功能:找到对应索引位置的字符
//参数:一个索引值
//返回值:返回找到的对应字符
//2.charCodeAt:根据传入的下标返回对应的字符的ASCII码
console.log(str.charCodeAt(0));
//功能:找到对应索引位置的字符的ASCII码
//参数:一个索引值
//返回值:返回找到的对应字符的ASCII码
//concat:字符串的合并,合并的时候不一定非要和字符串合并,任何类型都可以
console.log(str.concat('iii'));//拼接
//功能:把原串和指定的字符串拼接到一起
//参数:指定一个新的字符串
//返回值:返回拼接好的字符串
//console.log(str);//原串都不会发生改变
console.log(String.fromCharCode(49));//ASCII编码
//功能:把ASCII码转化为对应的字符
//参数:指定一个ASCII码
//返回值:返回对应的字符
console.log(str.indexOf('236',3));//********
//功能:从原串当中找指定的子串,求出子串下标位置
//参数:只有一个参数:默认从0下标一直搜索到字符的最后一位,查找第一个参数出现的首次位置的下标
// 两个参数:从5下标开始一直搜索到字符的最后一位,查找第一个参数出现的首次位置的下标
//返回值:返回对应的子串下标,如果没有找到返回-1
console.log(str.lastIndexOf('23',10));//这个和indexOf类似,只不过这个是从右往左查
//参数: 只有一个参数:默认从0下标一直搜索到字符的最后一位,查找第一个参数出现的末次位置的下标
// 两个参数:从0下标开始一直搜索到第二个参数值截止,查找第一个参数出现的末次位置的下标
//下面这三个依赖于正则表达式,后面学正则的时候,给大家加进来
//console.log(str.replace('1','**'));//替换字串
//console.log(str.match(/1/g));//找到匹配项返回数组
//console.log(str.search());
str = '12345678';
console.log(str.localeCompare('12345678'));//比较大小
//功能:比较原串和指定的字符串大小
//参数:指定一个字符串
//返回值:如果原串大返回1 如果原串小返回-1 如果相等返回0
console.log(str.slice(2,-1));//**************
//功能:从原串当中截取指定位置(索引)的字符串,形成新串
//参数:1个参数:slice(起始下标) 从这个下标开始一直截取到字符串的末尾
// 2个参数:slice(起始下标,终点下标-1)
//返回值:返回截取的字符串
console.log(str.substr(0,4));//后面的参数是长度
//功能:从原串当中截取的字符串,形成新串
//参数:1个参数:substr(起始下标) 从这个下标开始一直截取到字符串的末尾
// 2个参数:substr(起始下标,截取的长度) 从这个起始下标开始截取几位字符
//返回值:返回截取的字符串
console.log(str.substring(4,0));//后面的参数不允许是负数
//功能:从原串当中截取的字符串,形成新串
//参数:1个参数:substring(起始下标) 从这个下标开始一直截取到字符串的末尾
// 2个参数:substring(起始下标,终点下标-1)
//返回值:返回截取的字符串
//1.split 分割字符串的方法
//在一些特定的场景下使用
var str = "2022-5-14";
var arr = str.split('-');
console.log(str.split('-')[0]); //2022
console.log(str.split('-')[1]); // 5
console.log(str.split('-')[2]); // 14
//需求:将月份加一个前导0,前提是月份在10月之前
if(arr[1]<10){
arr[1] = '0' + arr[1];
}
console.log(arr[1]);
//数组对象中有可以根据自定义符号来重组字符串:join
console.log(arr);
console.log(arr.join('/')); //2022/05/14
//split中的参数是字符串中必须存在的;
//join中的参数是可以自定义的
//2.toLowerCase、toUpperCase
//功能是在对字符进行大小写的转换
//应用场景:验证码
var code = "tYhM"; //网站模拟验证码
var num = prompt('请输入你的验证码');
console.log(num); //tyhm
//然后咱们转换形式,要求统一,无论用户输入什么,只要比较值,不要求大小写
TYHM == TYHM
console.log(code.toUpperCase() == num.toUpperCase());
console.log(str.toString());
//功能:把一个对象转化为字符串
//参数:无
//返回值:就是自己,是个新串
//3.replace、match、search
//replace:字符串的替换
var str1 = "hello";
console.log(str1.replace('l','q')); //heqlo
//正则表达式
var pattern = /l/g
console.log(str1.replace(pattern,'q')); //heqqo 全局搜索 全局替换
//match:全局匹配搜索结果
var pattern1 = /l/
console.log(str1.match(pattern1)); //['l', index: 2, input: 'hello', groups: undefined]
//全局搜索必须搭配g
var pattern2 = /l/g
console.log(str1.match(pattern2)); //['l', 'l']
//search:类似于indexOf 搜素字符首次出现的下标位置
// console.log(str1.search('l')); // 2
// console.log(str1.search('a')); // -1
ES6方法
// 1. includes(str) : 判断是否包含指定的字符串 返回值为布尔值
var str = 'ajshdhak123eiuf';
console.log(str.includes('1235'));
// 2. startsWith(str) : 判断是否以指定字符串开头 返回值为布尔值
console.log(str.startsWith('aa'));
// 3. endsWith(str) : 判断是否以指定字符串结尾 返回值为布尔值
console.log(str.endsWith('ff'));
// 4. repeat(count) : 重复指定次数
console.log(str.repeat(2))
<script>
var person = {
name: "action",
lastName : "Gates",
language : "en",
get lang() {
return "这是测试getter的方法";
}
};
console.log(person.lang);
</script>
3.6 setter方法
<script>
var person = {
name : "zhangsan",
language : "",
set lang(lang) {
this.language = lang;
}
};
// 使用 setter 来设置对象属性:
person.lang = "zh-hans";
console.log(person.language);
</script>
四、堆栈的区别
-
栈(stack):栈会自动分配内存空间,会自动释放,存放基本类型和引用数据类型的变量
所有在方法中定义的变量都是放在栈内存中,随着方法的执行结束,这个方法的内存栈也自然销毁。
优点:存取速度比堆快
缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
在栈中有两个执行环境:
1)全局执行环境(全局执行上下文):存储的是基本数据类型和引用数据类型变量
2)函数执行环境(函数执行上下文):执行的是函数体内部的数据
栈内存:全局执行环境和函数执行环境存在 先进后出,后进先出的特点,相比较堆来说,内存较小
全局执行环境中不存在执行顺序问题,依此执行代码
-
堆(heap):动态分配的内存,大小不定也不会自动释放,存放引用类型的对象
引用类型数据的变量是通过内存的地址值到堆内存中进行查找相应的数据
注意:堆内存中存储数据是无序的,相比较栈来说,内存较大
指那些可能由多个值构成的对象,保存在堆内存中。
堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(参数传递),创建对象是为了反复利用。
4.1 基本数据类型使用
下图演示了这种基本数据类型赋值的过程:
b获取的是a值得一份拷贝,虽然,两个变量的值相等,但是两个变量保存了两个不同的基本数据类型值。
b只是保存了a复制的一个副本。所以,b的改变,对a没有影响。
var a = 10;
var b = a; //a将自己的值赋值给了b变量 值传递
b = 20; //b将自己的值修改成了20
//基本数据类型是你改了,与我无关
console.log(a); //10
console.log(b); //20
4.2 引用数据类型使用
引用类型是存放在堆内存中的对象,变量其实是保存的在栈内存中的一个指针(保存的是堆内存中的引用地址),这个指针指向堆内存。
引用类型数据在栈内存中保存的实际上是对象在堆内存中的引用地址。
通过这个引用地址可以快速查找到保存中堆内存中的对象
var obj1 = new Object();
var obj2 = obj1;
obj2.name = "我有名字了";
console.log(obj1.name); // 我有名字了
说明这两个引用数据类型指向了同一个堆内存对象。
obj1赋值给obj2,实际上这个堆内存对象在栈内存的引用地址复制了一份给了obj2,
但是实际上他们共同指向了同一个堆内存对象,
所以修改obj2其实就是修改那个对象,通过obj1访问也能访问的到。
1、开辟内存空间(堆)
2、this指向该内存(让函数内部的this)
3、执行函数代码
4、生成对象实例返回(把空间的地址返回)
原型对象就是函数对象的一个属性prototype的值( 地址),这个prototype属性值是原型对象;
也被叫做显式原型对象.ES5中适合在原型内部添加东西---修改。
console.dir(Villa);
-
隐式原型对象
由这个函数实例化出来的对象身上都会有一个属性叫_proto_,
它和函数对象prototype地址一样, 代表同一个对象,如果我们通过_proto_去操作原型对象,
称为隐式原型对象。ES5中适合查看原型。
var v1 = new Villa(1000,'中式',10000000);
console.log(v1);
6.2 原型对象的格式
function Villa(size, styleType, price) {
this.size = size;
this.styleType = styleType;
this.price = price;
// this.live = function(){
// console.log('住的很舒服');
// }
}
Villa.prototype.live = function() {
console.log('住的很舒服');
} //把方法添加在原型对象当中,让所有的实例化对象共享
var v1 = new Villa(1000, '新中式', 10000000);
v1.live();
console.log(v1.styleType);
var v2 = new Villa(2000, '简欧', 20000000);
v2.live();
console.log(v2.styleType);
描述的是对象在查找属性或者方法的过程
实例化对象在找属性或者方法的时候,先从自身proto去找看有没有这个属性或者方法;
如果有,直接使用这个属性的值,如果没有,会继续顺着这个对象的隐式原型对象(__proto__)找到这个对象的原型对象(和它的构造函数的显式原型对象是同一个),
看看原型对象是否存在这个属性,如果有就使用原型对象当中的这个属性值;
如果还没有,再去找原型对象的隐式原型对象(默认就是Object显式原型对象),找到以后去看看有没有这个属性,如果有就使用这个属性值;如果没有就返回undefined(代表已经找到顶了);
var obj = {
name:'马大哥',
age:33
}
function Dog(name,age){
this.name = name;
this.age = age;
}
Object.prototype.eat = function(){
console.log('吃肉');
}
var d1 = new Dog('旺财',3);
d1.eat(10,20);
obj.eat();
总结:
那什么是原型链呢?
简单理解就是原型组成的链,对象的_proto_它的是原型,而原型也是一个对象,也有_proto_属性,原型的proto又是原型的原型,就这样可以一直通过_proto_想上找,这就是原型链,当向上找找到Object的原型的时候,这条原型链就算到头了。
原型对象和实例之间有什么关系呢?
通过一个构造函数创建出来的多个实例,如果都要添加一个方法,给每个实例去添加并不是一个明智的选择。这时就该用上原型了,在函数的原型上添加一个方法,这个原型的所有实例便都有了这个方法。
- 在函数的prototype属性中
- 任何函数的prototype下的构造器默认都指向当前函数本身
- 设计初心:为了能够给属性的值进行初始化(ES6)
7.1 概念
任何函数对象都有apply和call方法
apply和call可以使用第一个参数传对象,让函数或者方法的执行者(this)指向这个对象;
7.2 语法
前提:
函数无形参的时候:
- 函数对象.call(想要指向的那个新对象);
- 函数对象.apply(想要指向的那个新对象);
函数有形参的时候:
- 函数对象.call(想要指向的那个新对象,实参1,实参2....); 以逗号间隔
- 函数对象.apply(想要指向的那个新对象,[实参1,实参2....]); 实参类型必须为数组
7.3 作用(背)
笔试题中会容易出现
-
- 先帮你修改this指向
- 然后函数立即执行
八、包装类
我们都知道js分为基本数据类型和引用数据类型。
基本数据类型数据可以有属性吗?答案是没有,但是为什么基本数据类型却可以调用一些方法呢。
var str="hello world";
var long=str.length;
console.log(long); //得出结果为11
明明没有却可以调用length的方法,就是因为包装类的原因,函数在执行的前一刻发现你写的代码其实是存在问题
但是因为js是解释型语言,系统会为你进行包装类的操作;
js中提供了三种特殊的引用类型(String Number Boolean)
每当我们给基本数据类型赋属性值时 后台都会给我们偷偷转换 调用包装类
执行的步骤:(背)
1.创建出一个和基本类型值相同的对象
2.这个对象就可以调用包装对象下的方法/属性,将结果并且返回给一个临时的变量
3.然后这个临时创建的对象就被销毁了
面试的时候,有可能会被问到有哪些数据类型会有包装(封装)类啊?
思考一个问题,每一次定义number、string、boolean类型的对象在内存中都会创建一个包装类对象,就会对内存造成一定的压力,那如何解决呢??
答:变量自己使用完毕之后,就会将自己销毁了(指向为null,弹出栈)
九、闭包高级
要理解闭包,首先必须理解Javascript特殊的变量作用域。
变量的作用域无非就是两种:全局变量和局部变量。
-
JavaScript语言的特殊之处,就在于函数内部可以直接读取全局变量。
var n=999;
function f1(){
alert(n); //999
}
f1();
-
在函数外部自然无法读取函数内的局部变量
function f1(){
var n=999;
}
f1();
alert(n); //报错
-
函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量
//var n;
function f1(){
n=999;
}
f1();
alert(n); //999
-
如何从外部读取局部变量?
出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到
的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。
function f1(){
var n=999;
console.log(a); //报错
function f2(){ //var fn2 = //function(){}
alert(n); // 999
var a = 100;
return a;//100
}
return f2;
}
var result = f1()
console.log(result); //function(){}
console.log(result())
在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不
行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子
对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!
function f1(){ //f1在函数执行完毕之后,函数执行环境就销毁了
var n=999;
function f2(){ //f2一直存在堆里面
alert(n);
}
function f3(){
}
//返回一个f2的方法体
return f2; //var f2 = function(){}
}
console.log(n); //报错
var result=f1();
result(); // 999
result();
-
闭包的概念
代码中的f2函数,就是闭包,闭包"(closure)定义非常抽象,很难看懂。
闭包其实就是能够读取其他函数内部变量的函数,由于在Javascript语言中,只有函数内部的子函数才能读取
局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
-
如何产生闭包(条件)?
-
内部函数和外部函数需要嵌套
-
内部函数需要引用外部函数的变量(局部变量、形式参数)
-
内部函数需要作为外部函数的返回值
-
外部函数需要调用
-
-
闭包到底是什么?
-
理解一: 闭包是嵌套的内部函数(绝大部分人)
-
理解二: 包含被引用(外部函数)变量的对象(极少数人)
-
理解三: 所谓的闭包是一个引用关系,该引用关系存在于内部函数中,引用的是外部函数的变量的对象
(深入理解)
-
-
常见的闭包
-
将函数作为另一个函数的返回值
-
-
闭包的作用
function fn(){ var a = 0; var b = 1; function fn1(){ a++; console.log(a); } return fn1; } var f = fn(); f(); f();
-
延长外部函数变量的生命周期
-
让函数外部可以操作(读写)到函数内部的数据(变量/函数)/函数外部可以引用函数内部的变量
-
注意: 浏览器为了性能后期将外部函数中不被内部函数使用的变量清除了
-
-
闭包的缺点和解决(内存泄漏和内存溢出)
-
内存泄漏 : 内存无法释放;
-
内存溢出 : 内存被撑爆;
-
f = null; 解决方式;
function fn(){ var a = 0; function fn1(){ a++; console.log(a); } return fn1; } var f = fn(); f(); f(); f = null;//释放闭包
-
-
使用闭包的注意点
-
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的
性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
虽然我们可以获取内部变量 但是不要轻易修改这个内部变量。
-
-
必会
-
闭包是什么
-
判断闭包的条件
-
手写闭包
-
闭包的作用
-
闭包的缺点
-
-
面试题精讲
-
面试题1
var name = "The Window"; var object = { name: "My Object", getNameFunc: function() { return function() { return this.name; }; } }; console.log(object.getNameFunc()());
-
面试题2
var name2 = "The Window"; var object2 = { name2: "My Object", getNameFunc: function () { //如果说你想在内部其它函数当中使用外部函数的this,就把外部函数this保存起来 var that = this; return function () { return that.name2; }; } }; console.log(object2.getNameFunc()());
-
十、自定义对象
由开发人员自己创建的对象
使用new
关键字调用的函数,是构造函数constructor
,构造函数是专门用来创建对象的
函数使用typeof
检查一个对象时,会返回object
在对象中保存的值称为属性
- 添加或修改对象属性的语法:
对象.属性名=属性值;
- 读取对象属性的语法:
对象.属性名
- 删除对象属性的语法:
delete 对象.属性名;
var obj = new Object();
// 向obj中添加一个name属性
obj.name = "孙悟空";
// 向obj中添加一个gender属性
obj.gender = "男";
// 向obj中添加一个age属性
obj.age = "18";
// 打印obj
console.log(typeof obj); // object
console.log(obj); // {"age":"18","gender":"男","name":"孙悟空"}
console.log(obj.name); // 孙悟空
属性名
对象的属性名不强制要求遵守标识符的规范,什么乱七八糟的名字都可以使用,但是我们使用是还是尽量按照标识符的规范去做
如果要使用特殊的属性名,不能采用.
的方式来操作,而需要使用另一种语法:对象["属性名"]=属性值
,读取时也需要采用这种方式
obj["name"] = "齐天大圣";
console.log(obj["name"]); // 齐天大圣
使用[]
这种形式去操作属性,更加的灵活,在[]
中可以直接传递一个变量,这样变量值是哪个就会读取哪个属性
var n = "nihao";
obj[n] = "你好";
console.log(obj[n]); // 你好
回顾:.
、[]
、new
这几个运算符的优先级是最高的
属性值
JS对象的属性值,可以是任意的数据类型,包括对象
var obj2 = new Object();
obj2.name = "猪八戒";
obj.bro = obj2;
console.log(obj.bro.name); // 猪八戒
in
运算符
通过该运算符可以检查一个对象中是否含有指定的属性
如果有则返回true
,没有则返回false
语法:"属性名" in 对象
console.log("test" in obj); // false
console.log("name" in obj); // true
十一、基本数据类型和引用数据类型
基本数据类型 String Number Boolean Null Undefined
引用数据类型 Object
基本数据类型
- JS中的变量都是保存到栈内存中的,基本数据类型的值直接在栈内存中存储
var a = 1;
var b = a;
console.log("a=" + a + ", b=" + b); // a=1, b=1
b = 2;
console.log("a=" + a + ", b=" + b); // a=1, b=2
引用数据类型
- 对象是保存到堆内存中的
- 每创建一个新的对象,就会在堆内存中开辟出一个新的空间,而变量保存的是对象的内存地址(对象的引用)
- 如果两个变量保存的是同一个对象引用,当一个通过一个变量修改属性时,另一个也会受到影响
var obj3 = obj;
obj3.name = "斗战胜佛";
console.log(obj.name); // 斗战胜佛
console.log(obj3.name); // 斗战胜佛
比较
- 当比较两个基本数据类型的值时,就是比较值。
- 而比较两个引用数据类型时,它是比较的对象的内存地址,如果两个对象是一摸一样的,但是地址不同,它也会返回
false
var o1 = new Object();
var o2 = new Object();
o1["name"] = "周瑜";
o2["name"] = "周瑜";
console.log(o1 == o2); // false
十二、对象字面量
使用对象字面量,可以在创建对象时,直接指定对象属性的语法:{属性名: 属性值, 属性名: 属性值...}
对象字面量的属性名可以加引号也可以不加(建议不加),如果要使用一些特殊的名字,则必须加引号
属性名和属性值是一组一组的名值对结构,名和值之间使用:
连接,多个名值对之间使用,
隔开
如果一个属性之后没有其他的属性了,就不要写,
了
var obj = {
name: "孙悟空",
age: 1000,
gender: "男",
bor:{
name: "猪八戒"
}
}
console.log(obj); // {"age":1000,"bor":{"name":"猪八戒"},"gender":"男","name":"孙悟空"}
十三、方法
对象的属性值可以是任何的数据类型,也可以是个函数(下一节知识)
函数也可以称为对象的属性,如果一个函数作为一个对象的属性保存,那么我们称这个函数是这个对象的方法
调用函数就说调用对象的方法,但是它只是名称上的区别没有其他的区别
var obj2 = {
name: "猪八戒",
age: 18,
sayName: function() {
console.log(obj2.name);
}
};
obj2.sayName(); // 猪八戒
十四、枚举对象中的属性
使用for...in
语句语法:
for(var 变量 in 对象) {
语句...
}
for...in
语句对象中有几个属性,循环体就会执行几次
每次执行时,会将对象中的一个属性的名字赋值给变量
var obj = {
name: "孙悟空",
age: 1000,
gender: "男",
address: "花果山"
};
for(var key in obj){
console.log(key + "=" + obj.key);
// name=undefined
// age=undefined
// gender=undefined
// address=undefined
console.log(key + "=" + obj[key]);
// name=孙悟空
// age=1000
// gender=男
// address=花果山
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)