JavaScript 对象的枚举
一、查看属性
obj.prop
obj["prop"]
怎么查看一个属性?
对象点什么什么,增删改查都是这样
对象访问它的属性就是 对象 . 属性名
var obj = { name: "abc" } console.log(obj.name); // 查看name属性
现在看一个对象(deng)叫老邓
1. 老邓有很多小媳妇
2. 有个 sayWife() 方法呼喊他的小媳妇,参数num传序号几,就会把第几个小媳妇返回来
正常来说用switch判断
var deng = { wife1: {name: "小刘"}, wife2: {name: "小张"}, wife3: {name: "小萌"}, wife4: {name: "小王"}, sayWife: function (num){ switch(num){ case 1: return this.wife1; case 2: return this.wife2; case 3: return this.wife3; case 4: return this.wife4; } } } console.log(deng.sayWife(1)); // {name: "小刘"} console.log(deng.sayWife(2)); // {name: "小张"} console.log(deng.sayWife(3)); // {name: "小萌"} console.log(deng.sayWife(4)); // {name: "小王"}
不用switch判断,想实现 this.wifenum 让变量拼上属性名
var deng = { wife1: {name: "小刘"}, wife2: {name: "小张"}, wife3: {name: "小萌"}, wife4: {name: "小王"}, sayWife: function(num){ return this.wifenum; // 变量拼属性名 } } console.log(deng.sayWife(1)); // undefined console.log(deng.sayWife(2)); // undefined console.log(deng.sayWife(3)); // undefined console.log(deng.sayWife(4)); // undefined
this.wifenum 这么写就变成属性"wifenum",
查找这个"wifenum"属性肯定不行,还有一种访问属性的方法,中括号的形式 [ ]
除了 obj.name 访问形式,还可以用 obj['name'] ,方括号中间填上"字符串形式"的属性名也可以访问属性
var obj = { name: "abc" } // 下面这两种基本上是完全等同的 onsole.log(obj.name); // abc console.log(obj['name']); // abc
有个内部原理:
obj.name --> obj['name']
每当 obj.name 的时候,系统内部隐式的转换成 obj['name']
换句话说直接写 obj['name'] 在运行上还快呢,因为不需要转换了,
所以在增、删、改、查、赋值的时候都可以这么 obj['name'] 来写,这是另一种表示属性的方法,只不过我们平时写点更加快捷一些
这两种(obj.name --> obj['name'])看着本质上一样,但在用法上可不一样,
1. 方括号里面必须是字符串 obj['name'],这样写不行 obj[name],直接写name相当于把变量name放进去了
2. 他俩的用法上 obj['name'] 更灵活,方括号里面是字符串,是字符串就可以拼接了
回归到刚才老邓的方法里面, this['wife' + num] 字符串加什么都等于字符串,这样就拼接了
var deng = { wife1: {name: "小刘"}, wife2: {name: "小张"}, wife3: {name: "小萌"}, wife4: {name: "小王"}, sayWife: function (num){ return this['wife' + num]; // 字符串加什么都等于字符串,这样就拼接了 } } console.log(deng.sayWife(1)); // {name: "小刘"} console.log(deng.sayWife(2)); // {name: "小张"} console.log(deng.sayWife(3)); // {name: "小萌"} console.log(deng.sayWife(4)); // {name: "小王"}
想要实现属性名的拼接,只能通过方括号的形式,点是不行的,
后期会遇到很多这样属性拼接的问题,学到事件的时候 on 会加很多的东西,
比如
on + click
on + mousedown
属性拼接学到事件的时候,必须是 on 加一个什么东西去拼接
二、对象的枚举
1. for in
2. hasOwnProperty()
3. in
4. instanceof
枚举是什么意思?
枚举的英文单词是 enumeration,是遍历枚举的意思
什么是遍历呢?
给一组数据,想知道每一个数据是什么样子的,这个过程就叫做遍历的过程。
比如,
现在十个同学,想知道这十个同学每个人叫什么名字,或这十个同学每一个同学的个人信息,通过一个 for 循环挨个打印出来,挨个知道的这个过程就叫遍历的过程。
举个例子,
数组里面有好些个数据,现在想知道每一位的数据都是什么,这就是一个遍历的过程,遍历也相当于枚举(enumeration)
var arr = [1,2,3,4,5,6,7,8,9]; for(var i = 0; arr.length > i; i++){ console.log(arr[i]); }
数组可以遍历,现在想遍历一个对象,想知道这个对象的每一个属性值是什么,
这个对象可能是后端传来的,不知道里面有什么东西,也没站到一个编辑器的角度去看,传的数据看不到,不知道循环几圈,没有 length 属性怎么办?
1、for in
想遍历一个对象必须要借助一个新的知识叫 for in 循环,就是一个简化版的for循环,能实现这个对象的遍历
var obj = { name: "Li", age: 37, sex: "female", height: 167, weight: 62, } for (var prop in obj) { console.log(prop); } // name // age // sex // height // weight
然后再看一下 prop 是什么类型的
var obj = { name: "Li", age: 37, sex: "female", height: 167, weight: 62, } for (var prop in obj) { console.log(prop + ': ' + typeof(prop)); // 打印"prop"的类型 }
prop都是string字符串类型的, for in 这个循环把对象里面的每一个属性名都提取出来了,而且有多少属性名就循环多少圈
name: string
age: string
sex: string
height: string
weight: string
这个循环叫 fon in 循环,它的目的只有一个,就是遍历对象用的,单独给对象设立一个循环用来便利的
fon in 循环是通过对象属性的个数来控制循环圈数的,对象有多少个属性就循环多少圈,而且在循环每一圈的时候,会把对象的属性名放到 prop 里面(变量prop可以换名字比如换成key)
有了属性名、有了对象,我们就可以知道每一个圈,每个属性对应的属性值
var obj = { name: "Li", age: 37, sex: "female", height: 167, weight: 62, } for(var prop in obj){ console.log(obj.prop); // prop代表属性名,是不是这样obj.prop就可与知道对应的属性值? }
打印结果全是undefined,5个undefined为什么?
"prop"是字符串,是字符串为什么不报错,还能打印出undefined呢?
如果给对象obj再加一个属性 prop:123 (属性名是prop),
1. 打印结果是6个123,
2. 因为把"prop"当做属性名了
var obj = { name: "Li", age: 37, sex: "female", height: 167, weight: 62, prop: 123 // 这里添加一个属性prop: 123 } for(var prop in obj){ console.log(obj.prop); // 打印出6个123 }
为什么把"prop"当做属性名了?
1. 系统底层会有个原理,比如 obj.name 会变成 obj['name'] ,中括号里是字符串的'name'
2. 同样把规律应用到这,这么写 obj.prop 这个prop还能是变量吗?它会变成 obj['prop'] ,
系统会理解这是在访问 prop属性,不会把prop当做变量
3. 每次循环 obj.prop 会转换成这样 obj['prop'],变成一个定量了("prop"字符串),
每次是定量,每次就会找有没有 prop这个属性,结果就不是理想的那个样子了
4. 这样写 obj[prop] ,prop每次是变量,每次代表不同的字符串,把不同字符串放进去,相当于访问不同的属性了,
在其他地方写点没问题,但在 for in 循环枚举里写必须是方括号
5. 注意千万别 obj['prop'] 这样写加引号,这样和写点没有区别了,
方括号里的prop不用加引号 obj[prop] ,因为 prop 自身就是一个变量,变量的值都是字符串,是符合规定的
obj[prop] 这么写会把对象里面所有的属性值遍历出来
var obj = { name: "Li", age: 37, sex: "female", height: 167, weight: 62, prop: 123 } for (var prop in obj) { console.log(obj[prop]); }
循环出所有属性的值
Li
37
female
167
62
123
以后写属性尽量养成一个习惯,所有对象点属性的形式,尽量写成方括号的形式
现在给对象obj手动加一个__proto__属性,手动把原来的原型改成别人,再循环遍历obj的属性,会不会把原型上的东西拿下来?
var obj = { name: "Li", age: 37, sex: "female", height: 167, weight: 62, __proto__: { // 给obj手动加一个属性__proto__ lastName: "Fang", } } for(var prop in obj){ console.log(obj[prop]); }
会把原型上的东西遍历出来
Li
37
female
167
62
Fang 这是原型上的东西
如果在遍历的时候,不想把对象原型上的属性拿出来怎么办呢?
有没有一种方法判断这个属性是对象的,还是原型上的呢?有,有这个方法叫 hasOwnProperty()
2. hasOwnProperty()
任何一个对象,只要它叫对象就有 hasOwnProperty()
1. hasOwnProperty() 是个方法里面需要传参数,把对象属性名的字符串形式传进去,也就是把prop传进去
2. 这个方法是验证prop是不是对象自己的属性,然后会返回布尔值
3. 判断布尔值的结果,如果是true说明是自己属性或方法,否则说明是原型连上的
是自己的属性或方法就打印,原型链上的、原型上的就不打印了
var obj = { name: "Lux", age: 37, sex: "female", height: 172, weight: 62, __proto__: { lastName: "Fang" } } for (var prop in obj) { if (obj.hasOwnProperty(prop)) { console.log(obj[prop]); } }
这样加上一层判断,fang就不复存在了,因为不是对象自身的方法
Lux
37
female
172
62
obj.hasOwnProperty(prop) 就是判断属性prop是不是对象obj自己的的,如果是对象obj自己的就打印true,如果不是就返回false,通过这样的方法来排除对象原型上的东西
注意一点:
for in 循环理论上是可以返回原型上的东西,以及原型链上的东西,但是一旦这个原型链延展到 Object.prototype 上,不会打印终端上的,
也就是说我们手动加的__proto__,它身上还有一个__proto__,指向的是 Object.prototype ,一但延展到原型链的最顶端就会放弃,不会遍历最顶端系统自带的东西
for in 会把自己设的原型给打印出来,但系统带的它不会打印出来,往原型链终端 Object.prototype 上加一个属性(好问题试一下)
Object.prototype.abc = "123"; // 原型链终端上加abc = "123" var obj = { name: "Lux", age: 37, sex: "female", height: 172, weight: 62, __proto__: { lastName: "Fang" } } for (var prop in obj) { if (!obj.hasOwnProperty(prop)) { // 判断加!,意思是只打印对象原型上的属性 console.log(obj[prop]); } }
遍历结果
Fang
123
也就是说,
但凡是自己手动设置的属性,不论放到那都能打印出来,
但凡是系统自设的原型,无论在哪都一定不会出来,
所以 hasOwnProperty() 方法是用来过滤的,看看这个属性到底是不是对象自己的,当然它的应用不只在这,还有其他地方也能应用的话也能应用
但是遍历属性、遍历方法的时候,一般它们两都是成套出现的,一个 fon in 循环里面加一个 hasOwnproperty() 为了过滤一下,
一般遍历属性的时候都不希望拿原型属性,这还是遍历访问,如果修改更不需要拿原型上属性了
3. in
第二的操作符叫 in ,它和 hasOwnPorperty() 差不多,也看这个属性到底是不是这个对象的
现在看这个 height 属性到底是不是obj对象的?
会返回一个结果true或false,这么写 height in obj 行吗,会不会报错?
属性是字符串,这样直接写height不加引号是变量,所以一定把这个属性变成字符串形式'height'
var obj = { name: "Lux", age: 37, sex: "female", height: 172, weight: 62, __proto__: { lastName: "Fang" } } console.log(height in obj); // Uncaught ReferenceError: height is not defined
字符串形式的属性名"height",存不存在对象里?存在返回true,不存在返回false
var obj = { name: "Lux", age: 37, sex: "female", height: 172, weight: 62, __proto__: { lastName: "Fang" } } console.log('height' in obj); // true
这个 in 和 hasOwnproperty() 差不多,那为什么不用 in 用 hasOwnPorperty(),说明它俩有区别,
in 不分青红皂白的,你的也算你的,你父亲的也算你的。
比如 lastName 是 obj 的吗?正常来说不是,是原型上的
var obj = { name: "Lux", age: 37, sex: "female", height: 172, weight: 62, __proto__: { lastName: "Fang" } } console.log('lastName' in obj); // true
但是返回true,所以 in 只能判断这个对象上能不能访问到这个属性,包括原型上的,能访问返回true不能访问返回false,
它不是判断这个属性属不属于这个对象,判断这个属性属不属于这个对象只能用 hasOwnProperty()
in 操作符只能判断能不能在这个对象上调用这个属性,原型上的也算
in 操作符出现的概率,使用的概率极其极其的底,基本上就是没用过,但确实有这个东西,
入职考试时会考,比如看这几个的区别是什么,
有时候中国人骨子里就愿意搞这些形式主义,明明用不到也愿意考一下,如果换成西方绝对不会考没有用啊
in 虽然不重要,但 hasOwnProperty() 和 instanceof 是非常重要的,尤其是第三个 instanceof
4. instanceof
instanceof 也是操作符,必须要学好它经常考,instanceof的操作用法类似于in,但是和in是完全不同的
用法 A instanceof B 官方给出的解释是,对象A是不是构造函数B构造出来的(A是对象,B是构造函数)
对象Li是Person构造出来的返回true,然而这个用法没有这么简单,官方给的解释是完完全全不够用的
function Person(){ } var Li = new Person(); console.log(Li instanceof Person); // true
因为在看 Li instanceof Object ,对象 Li 是 Object 构造出来的吗?明显不是,但结果为什么返回true
function Person(){ } var Li = new Person(); console.log(Li instanceof Object); // true
再写一个数组,数组也是对象, 空数组 instanceof Array 是正常的,数组就是数组构造出来的
console.log([] instanceof Array); // true
虽然数组字面量也是数组构造出来的返回true,但是 [] instanceof Object 也返回true
console.log([] instanceof Object); // true
这是什么意思?
姬成老师总结的一句话,必须记住这句话,其他的怎么记都记不完全
"A instanceof B" 是看对象A的原型链上有没有B的原型,这个instanceof的根本语法就应该这样理解。
对象Li的原型链上,有没有构造函数Person的原型?有,顶头有第一个就是
function Person(){ } var Li = new Person(); console.log(Li instanceof Person); // true
Li instanceof Object 再看对象Li的原型链上有没有Object的原型?有,原型链最顶端就是Object.prototyep
function Person(){ } var Li = new Person(); console.log(Li instanceof Object); // true
Li instanceof Array 对象Li的原型上有没有Array?没有,Li的原型上没有Array,这个必然是false
function Person(){ } var Li = new Person(); console.log(Li instanceof Array); // false
{} instsanceof Person 对象字面量(空对象)的原型链上,有没有构造函数Person?
1. {} 的原型直接指向 Object.prototype
2. 和 Perosn.prototype 没关系,没关系返回false
function Person(){ } var Li = new Person(); console.log({} instanceof Person); // false
以前上面这样 {} instanceof Person 直接写对象字面量会报错,要把 {} 赋值一个变量,现在可能是浏览器升级不报错了
obj = {} console.log(obj instanceof Person); // false
A instanceof B 真正判断的是A的原型链上有没有B的原型,但是本质上是看A是不是B构造出来的,但A认为在原型链上的也算它的构造函数(构造器),但是instanceof解决了一个非常强大的问题
三、区分数组、对象的三种方法
typeof数组,返回的是object
typeof对象,返回的也是object
console.log(typeof([])) // object console.log(typeof({})) // object
现在给一个数据,已知这个数据可能是数组也可能是对象,不一定是什么,怎么区分开?
判断一下变量arr,是数组还是对象?
var arr = [] || {};
问一个问题,
数组算不算一种对象?数组算对象。
1. 数组的constructor是 Array() { [native code] }
console.log([].constructor); // Array() { [native code] }
2. 对象的的constructor是 ƒ Object() { [native code] }
var obj = {}; console.log(obj.constructor); // Object() { [native code] }
区分出来了,这是第一种区分方法
第二种区分方法
数组 instanceof Array 返回true
console.log([] instanceof Array); // true
对象 instanceof Array 返回false
var obj = {}; console.log(obj instanceof Array); // false
这样也区分出来了
还有第三种区分方法就是 toString()
各个构造函数都重新的 toString() 方法,现在只想用 Object.prototype 上的 toString(),因为只有它 Object.prototype.toString() 能区别出数组、对象的区别
想用数组调用 Object.prototype.toString() 方法怎么做?
Object.prototype.toString.call([]);
确实用 .call( [] ) 能办到,知道这是怎么回事吗?这个 Object.prototype 上的 toString() 是一个方法
Object.prototype.toString = function(){ // this // 1.首先识别this // 2.返回响应的结果 } obj.toString();
正常来说这个 toString() 方法是怎么用的?
1. 正常是被谁调用了obj.toString(),
这里面涉及一个 this 的小知识点,谁调用的它这个this就是谁。
2. 而toString()是为了处理前面调用者的,如果是对象调用它,那this就是对象。
3. toString()里面一定会用this去处理前面的东西,因为toString()把前面的的东西识别出来,再把特定的字符串格式返回。
4. toString()怎么识别前面的东西?在定义toString()方法的时候,不可能知道谁调动的它,所以只能用this,处理的是这个this。
5. 首先识别this然后返回相应的结果(this是对象就是对象,是其他东西就是其他东西)。
toString方法里面,光靠猜也知道 Object.prototype.toString()方法里面肯定会用到this,能猜到肯定会用this,
现在让 Object.prototype.toString.call() 方法执行,用 .call() 来执行,用 .call() 执行相当于空执行,
不想空执行,想让里面的this变成数组,所以数组就会替换原来的this, .call( [] ) 就替换this指向了,
替换this了之后,toString()里面执行的时候,原来谁调用它谁是this,现在this变成数组了,toString()就会识别数组来返回它的结果。
如果放数字 Object.prototype.toString.call(123),数字就是this,就会识别数字来当做适配对象
Object.prototype.toString.call([]); // "[object Array]" Object.prototype.toString.call(123); // "[object Number]" Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call(???) 里面放数组打印的是数组 "[object Array]",
然后放数字打印的是数字 "[object Number]",
如果放对象就和直接调用没区别了 "[object Object]",但是数组和对象有区别,返回的字符串类型不一样。
这是区别数组和对象的第三种方法
第一种 constructor
第二种 instanceof
第三种 toString()方法