前言
引用数据类型主要用于区别基本数据类型,描述的是具有属性和函数的对象。
JavaScript中常用的引用数据类型包括**Object类型**、**Array类型**、**Date类型**、**RegExp类型**、**Math类型**、**Function类型**以及**基本数据类型的包装类型**,如Number类型、String类型、Boolean类型等。
- 引用数据类型的实例需要通过new操作符生成,有的是显式调用,有的是隐式调用。
- 引用数据类型变量赋值传递的是内存地址。
- 引用数据类型的比较是对内存地址的比较,而基本数据类型的比较是对值的比较。
new 操作符
new操作符在执行过程中会改变this的指向,所以在了解new操作符之前,我们先解释一下this的用法。
this的用法:
事实上我们并未通过return返回任何值,为什么输出的信息中会包含name和age属性呢?其中起作用的就是this这个关键字了。在JavaScript中,如果函数没有return值,则默认return this。代码里his实际值是Cat空对象,后两句就相当于给Cat对象添加name和age属性。function Cat(name, age){console.log(this) // Cat {}this.name = name;this.age = age;}console.log(new Cat('miaomiao', 8)) // Cat {name: "miaomiao", age: 8}
new操作符背后做了什么: ```javascript var cat = new Cat()
// 实际new操作符做了3件事 var cat = {}; cat.proto = Cat.prototype; Cat.call(cat);
1. 创建一个空对象。1. 将空对象的__proto__属性指向Cat对象的prototype属性。1. 将Cat()函数中的this指向cat变量。于是cat变量就是Cat对象的一个实例。<a name="jP6cj"></a># Object类型<a name="miARd"></a>## Object类型的实例函数<a name="kFVSa"></a>### 1. hasOwnProperty(propertyName)函数该函数的作用是判断对象自身是否拥有指定属性,不会检查实例对象原型链上的属性。```javascript// 1. Objectvar o = new Object();o.name = 'xjp'console.log(o.hasOwnProperty('name')) // true name属性为实例o自己定义的,而非继console.log(o.hasOwnProperty('toString')) // false toString为继承属性// 2. 自定义对象var Student = function(name){this.name = name}Student.prototype.sayHi = function(){alert('Hi' + this.name)}var st = new Student('张三')console.log(st.hasOwnProperty('name')) // true 调用构造函数时,通过this.name附加到实例对象上console.log(st.hasOwnProperty('sayHi')) // false sayHi为原型上的成员
2. propertyIsEnumerable(propertyName)函数
该函数的作用是判断指定属性是否为实例属性并且是否是可枚举的,如果是原型链上的属性或者不可枚举都将返回“false”。
// 1. 数组var arr = [1,2,3];arr.name = 'Array'console.log(arr.propertyIsEnumerable('name')) // true name为实例属性console.log(arr.propertyIsEnumerable('join')) // false join函数继承自Array类型console.log(arr.propertyIsEnumerable('length')) // false length属性继承自Array类型console.log(arr.propertyIsEnumerable('toString')) // false toString函数继承自Object类型// 2. 自定义对象var Student = function(name){this.name = name}Student.prototype.sayHi = function(){alert('Hi' + this.name)}var a = new Student('tom')console.log(a.propertyIsEnumerable('name')) // true name为自身定义的实例属性console.log(a.propertyIsEnumerable('sayHi')) // // false sayHi为原型上的成员Object.defineProperty(a, 'name', {enumerable:false})console.log(a.propertyIsEnumerable('name')); // false name设置不可枚举
Object类型的静态函数
静态函数指的是方法的调用基于Object类型自身,不需要通过Object类型的实例。
1. Object.create(prototype, prototypeDescriptor)函数
作用:创建并返回一个指定原型和指定属性的对象。该对象的proto === 指定原型对象。
第一个参数prototype为对象的原型,可以为null。若为null,则对象的原型为undefined
第二个参数prototypeDescriptor属性描述符,格式如下:
{value: '', // 设置属性的值writable: true, // 设置此属性是否可写入修改;默认false:只读enumerable: true, // 设置此属性是否可枚举;默认false:不可以枚举configurable: true // 设置此属性是否可配置,如是否可以修改此属性的特性及是否可以删除属性;默认fakse}
2. Object.defineProperties(obj, prototypeDescriptor)函数
3. Object.getOwnPropertyNames(obj)函数
作用:获取对象的所有实例属性和函数,不包含原型链继承的属性和函数,数据格式为数组。
function Person(name,age,gender){this.name = name;this.age = age;this.gender = gender;this.getName = function(){return this.name}}Person.prototype.eat = function(){return '吃饭'}var p = new Person();console.log(Object.getOwnPropertyNames(p)) // ['name', 'age', 'gender', 'getName'] eat()函数处在原型上
4. Object.keys()函数
作用:获取对象可枚举的实例属性,不包含原型链继承的属性,数据格式为数组。
keys()函数区别于getOwnPropertyNames()函数的地方在于,keys()函数只获取可枚举类型的属性。
Array类型
判断一个变量是数组还是对象
**typeof**运算符在判断基本数据类型时会很有用,但是在判断引用数据类型时,不能区分是数组还是对象
1. instanceof运算符
**instanceof**运算符通过查找原型链来检测某个变量是否为某个类型数据的实例,使用**instanceof**运算符可以判断一个变量是数组还是对象,要封装一下先判断数组类型,再判断对象类型。
// 数组不仅是Array类型的实例,也是Object类型的实例。var arr = [1,2,3]console.log(arr instanceof Array) // trueconsole.log(arr instanceof Object) // truevar obj = { name: 'xjp' }console.log(obj instanceof Array) // falseconsole.log(obj instanceof Object) // true// 封装 判断变量是数组还是对象function getDataType(o) {if (o instanceof Array) {return 'Array'} else if (o instanceof Object) {return 'Object'} else {return '参数不是objec类型'}}
2. 判断构造函数
判断一个变量是否是数组或者对象,从另一个层面讲,就是判断变量的构造函数是Array类型还是Object类型。
因为一个对象的实例都是通过构造函数生成的,所以,我们可以直接判断一个变量的constructor属性。
var arr = [1,2,3]console.log(arr.constructor === Array) // trueconsole.log(arr.constructor === Object) // falsevar obj = { name: 'xjp' }console.log(obj.constructor === Array) // falseconsole.log(obj.constructor === Object) // true
每个变量都会有一个proto属性,表示的是隐式原型。一个对象的隐式原型指向的是构造该对象的构造函数的原型:
[].constructor === [].__proto__.constructor // true[].__proto__ === [].constructor.prototype // true[].__proto__ === Array.prototype // truevar arr = [1,2,3]console.log(arr.__proto__.constructor === Array) // trueconsole.log(arr.__proto__.constructor === Object) // false// 判断变量是数组还是对象function getDataType(o) {// 获取构造函数var constructor = o.constructor || o.__proto__.constructorif (constructor === Array) {return 'Array'} else if (constructor === Object) {return 'Object'} else {return 'param is not object type'}}
3. toString()函数
基本任意类型都包含**toString()**函数(除null、undefined)。不同数据类型的toString()函数返回值也不一样,所以通过toString()函数就可以判断一个变量是数组还是对象。
这里我们会借助**call()**函数,直接调用Object原型上的toString()函数,把主体设置为需要传入的变量,然后通过返回值进行判断。
var a = [1, 2, 3];var b = {name: 'kingx'};var c;console.log(Object.prototype.toString.call(a)); // [object Array]console.log(Object.prototype.toString.call(b)); // [object Object]console.log(Object.prototype.toString.call(c)); // [object Undefined]console.log(Object.prototype.toString.call(null)); // [object Null]console.log(Object.prototype.toString.call('xjp')); // [object String]console.log(Object.prototype.toString.call(18)); // [object Number]
4. Array.isArray()函数
使用Array.isArray()函数只能判断出变量是否为数组,并不能确定是否为对象。
// 下面的函数调用都返回“true”Array.isArray([]);Array.isArray([1]);Array.isArray(new Array());// 鲜为人知的事实:其实 Array.prototype 也是一个数组。Array.isArray(Array.prototype);// 下面的函数调用都返回“false”Array.isArray();Array.isArray({});Array.isArray(null);Array.isArray(undefined);Array.isArray(17);Array.isArray('Array');Array.isArray(true);
reduce()函数
reduce()函数最主要的作用是做累加处理,即接收一个函数作为累加器,将数组中的每一个元素从左到右依次执行累加器,返回最终的处理结果。
语法:**arr.reduce(callback(accumulator、currentValue、currentIndex、array), initialValue)**
**initialValue**用作callback的第一个参数值,如果没有设置,则会使用数组的第一个元素值。**accumulator**表示上一次调用累加器的返回值,或设置的initialValue值。如果设置了initialValue,则accumulator=initialValue;否则accumulator=数组的第一个元素值。**currentValue**表示数组正在处理的值。**currentIndex**表示当前正在处理值的索引。如果设置了initialValue,则currentIndex从0开始,否则从1开始。**array**表示数组本身。
应用场景:
- 找出数组中出现次数最多的元素
```javascript
var getMost = function(arr) {
var maxNum=1, maxEle;
var obj = arr.reduce(function(p, k) {
}, {}); // 将initialValue设置为一个空对象{},key表示数组元素,value表示元素出现的次数 return ‘出现次数最多的元素为:’+ maxEle + ‘,次数为’ + obj[maxEle] }; getMost([3, 5, 10, 10, 5, 7, 7, 10, 10]); // “出现次数最多的元素为:10,次数为4”p[k]?p[k]++ :p[k] = 1; // 这边可以统计出每个元素出现的次数if(p[k] > maxNum) {maxEle=k;maxNum++;}return p;
// 借助ES6与逗号运算符进行代码优化 Array.prototype.getMost = function () { var obj = this.reduce((p, n) => (p[n]++ || (p[n] = 1), (p.max = p.max >= p[n] ? p.max : p[n]), (p.key = p.max > p[n] ? p.key : n), p), {}) return ‘出现次数最多的元素为:’ + obj.key + ‘,次数为:’ + obj.max } var arr = [3, 5, 10, 10, 5, 7, 7, 10, 10]; console.log(arr.getMost()); // 出现次数最多的元素为:10,次数为:4
2. 求数组的最大值和最小值```javascript// 最大值Array.prototype.max = function () {return this.reduce(function (preValue, curValue) {return preValue > curValue ? preValue : curValue; // 比较后,返回大的值});};// 最小值Array.prototype.min = function () {return this.reduce(function (preValue, curValue) {return preValue > curValue ? curValue : preValue; // 比较后,返回小的值});};var arr = [2,4,10,7,5,8,6];console.log(arr.min()); // 2console.log(arr.max()); // 10
- 数组去重
function arrayUnique(array) {var obj = {}, type;return array.reduce(function (preValue, curValue) {type = typeof curValue;if (!obj[curValue]) {obj[curValue] = [type];preValue.push(curValue);} else if (obj[curValue].indexOf(type) < 0) { // 判断数据类型是否存在,如1和"1"obj[curValue].push(type);preValue.push(curValue);}return preValue;}, []);}var array = [1, 4, 5, 7, 4, 8, 1, 10, 4, '1'];console.log(arrayUnique(array)); // [1, 4, 5, 7, 8, 10, "1"]
