javascript数据类型(二)

之前归纳了一下基本数据类型以及基本数据类型(也称为原始类型)的一些特性。在js当中,除了基本数据类型之外就是对象了,对象是属性的集合,每一属性都由键/值对构成,也可将对象看成是字符串到值的映射。

全局对象/包装对象

全局对象的属性是全局定义的符号,js程序可以直接使用,在js解释器启动的时候,它将创建一个新的全局对象,并定义一组初始属性,包括全局属性,全局函数,构造函数和全局对象(Math,JSON等)

包装对象是用来存取字符串,数字或布尔值属性时创建的临时对象,因为基本数据类型本身是不会有别的属性的,但是它们又有方法,比如之前说的str.trim(),这个’.trim()'其实是对象形式引用的属性值。在处理这种属性引用的时候会先创建这个临时对象,在引用完毕之后就会自动销毁。

对象与基本类型的转换

Js对象有两个不同的方法(toString和valueOf)执行对象和基本类型的转换,不同的类定义了其特定的toString方法,valueOf方法则一般默认返回对象本身。

对象->布尔值:全是true
对象->字符串
  • 如果对象有toString方法,调用这个方法,如果返回原始值,将原始值转为字符串并返回
  • 如果对象没有toString方法,或者没有返回一个原始值,js会调用valueOf()方法,如果返回原始值,将原始值转为字符串并返回
对象->数字
  • 如果对象有valueOf方法,且返回一个原始值,js将原始值转换为数字并返回
  • 如果没有valueOf方法,但有toString方法,且返回一个原始值,js将其转换之后的字符串再转换为数字并返回
对象简述

对象的分类和属性的分类如下:

  • 内置对象:由ECMAScript规范定义的对象或类,例如:数组,函数,日期,正则表达式,Map,Set等等
  • 宿主对象:由js解释器所嵌入的宿主环境定义的,比如HTMLElement对象。因为宿主环境定义的方法可以当成普通的js函数对象,所以宿主对象也可以当作内置对象
  • 自定义对象:用户自定义的对象
  • 自有属性:直接在对象中定义的属性
  • 继承属性:在对象的原型对象中定义的属性

对象最常见的用法是创建,设置,查找,删除,检测和枚举它的属性,对象的属性还拥有可写,可枚举,可配置三种特性,可以对这三种特性进行配置。

  • 可写:是否能够修改属性值,默认是true
  • 可枚举:能否通过for-in循环,或者是否能用Object.keys返回属性,直接在对象上定义的属性一般为true
  • 可配置:是否能通过delete删除属性从而重新定义属性,是否修改属性的特性,是否能把属性修改为访问器属性

实际上ECMAScript有两种属性:数据属性和访问器属性,数据属性包括值,可写,可枚举,以及可配置;访问器属性的可写由setter控制,因此它包括get,set,可枚举和可配置。es5定义了一个名为“属性描述符”的对象来代表这四个特性,并通过object.getOwnPropertyDescriptor()来获得特定属性的属性描述符(只有自有属性),要想获取对象原型的某个属性的属性描述符,需要使用object.getPrototypeOf()获取对象原型,之后再进行遍历。

此外,每个对象还包含三个相关的对象特性,分别为:

  • 对象的原型:用来继承属性
  • 对象的类:一个字符串,用以表示对象的类型信息
  • 对象的可扩展性:用以表示是否可以给对象添加新属性,可以用object.isExtensible判断对象是否可扩展,将对象设为不可扩展可以通过object.seal(),object.preventExtension(),object.freeze()设定。
    • object.preventExtension(o):将o设置为不可扩展,不能添加新的属性,但是可以删,也不会影响原型链
    • object.seal(o):将o设置为不可扩展,同时将它所有的自有属性设置为不可配置(不能加也不能删),但是不会影响原型链
    • object.freeze(o):除了不能增删之外还不能修改,冻结后对象的原型也不能被修改。如果一个属性的值是对象,那这个对象中的属性还是可以修改的
对象属性配置

需要改变属性默认的特征,必须使用特定的方法,目前常用的方法如下:

object.defineProperty()object.defineProperties()new Proxy()

在说这三种方法之前,需要先说明一下访问器属性的两个函数:getter和setter,顾名思义,这俩就是用来存和取值的。使用它也很简单,将访问器定义为一个或两个和属性同名的函数,函数的定义用get和set关键字代替function来声明函数:

// 这段代码简单定义了一个拥有访问器属性的对象
var o = {
	data_prop:value,
  get accessor_prop(){/* main */} 
  set accessor_prop(value){/* main */} //将value进行定义
}

这三个方法可以通过getter和setter将对象的属性进行修改,三种方法在使用上又有所不同。

  • object.defineProperty()

    Object.defineProperty(order, "tabLabel", {
      get: function () {
        //取值的时候会触发
        this.extraOperateDom.innerHTML = "hehheh";
        console.log("order: ", order.tabLabel);
        return order.tabLabel;
      },
      set: function (value) {
        //更新值的时候会触发
        let data = value;
        this.extraOperateDom.innerHTML = "hehheh";
        console.log("set: ", data);
      },
    });
    

    Object.defineProperty(obj, prop, descriptor)方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回该对象。

  • object.defineProperties()

    Object.defineProperty(order,  {
      tabLabel:{
        get: function () {
        //取值的时候会触发
        this.extraOperateDom.innerHTML = "hehheh";
        console.log("order: ", order.tabLabel);
        return order.tabLabel;
      },
      set: function (value) {
        //更新值的时候会触发
        let data = value;
        this.extraOperateDom.innerHTML = "hehheh";
        console.log("set: ", data);
      }
      },
      tabValue:{
        get: function () {
        //取值的时候会触发
        console.log("order: ", order.tabValue);
        return order.tabLabel;
      },
      set: function (value) {
        //更新值的时候会触发
        let data = value;
        console.log("set: ", data);
      }
      },
      
    });
    

    object.defineProperties()和上面那个函数是差不多的,只不过它可以一次性定义多个属性,而楼上一次只能定义一个属性

  • proxy()(vue3使用它来替代了object.defineProperty)

    let obj={a:1,b:{c:2}};
    let handler={
      get:function(obj,prop){
        const v = Reflect.get(obj,prop);
        if(v !== null && typeof v === 'object'){
          return new Proxy(v,handler);//代理内层,Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同
        }else{
          return v; // 返回obj[prop]
        }
      },
      set(obj,prop,value){
        return Reflect.set(obj,prop,value);//设置成功返回true
      }
    };
    let p=new Proxy(obj,handler);
     
    

    proxy用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等),使用的时候需要先声明对象,target 是要使用Proxy包装的目标对象(啥对象都可以,只要是对象),handler是对象对应的一些操作,这样就可以在handler里面对对象进行更多更加有趣的操作。
    Proxy代理对象时只会在调用时递归,不会一开始就全部递归,优化了性能

  • 对比

    • Proxy比Object.defineProperty使用方便。
    • Proxy代理整个对象,啥对象都可以,Object.defineProperty只是代理对象上的某个属性。
    • 如果对象内部要全部递归代理,则Proxy可以只在调用时递归,而Object.defineProperty需要在一开始就全部递归,Proxy性能优于Object.defineProperty。
    • 对象上定义新属性时,Proxy可以监听到,Object.defineProperty监听不到。
    • 数组新增删除修改时,Proxy可以监听到,Object.defineProperty监听不到。
    • Proxy不兼容IE,Object.defineProperty不兼容IE8及以下。
对象的增删查以及数据检测
创建新对象
  • 对象字面量:直接通过声明对象创建新对象

  • 通过new创建(构造函数模式)

    function Person(age,name,job){
    	this.name = name;
      this.age = age;
      this.job = job;
    }
    var person1 = new Person(18,'lalala','worker')
    

    这样子调用构造函数会经历以下四个步骤:

    1. 创建一个对象
    2. 将构造函数中的作用域赋给新对象(this指向了这个新对象)
    3. 执行构造函数中的代码(为新对象添加属性)
    4. 返回新对象
  • object.create(proto, descriptors)

    第一个参数是新创建对象的原型,第二个参数可选,把属性名映射到属性描述符

对象查询

可以通过点或者方括号来获取属性的值

const person={
  age:18,
  name:'lalala',
  job:'worker'
};
console.log(person.age)
console.log(person["age"]) // 方括号里的表达式必须为字符串,或者是能转化为字符串的值,
let age = 'age';
console.log(person[age]) // 其实也可以是一个变量,还可以[`${param}111`]这种形式

在查询过程中,会先在自有属性中进行查询,自有属性中没有就会查询原型链,一直找到对应属性或者原型是null的对象为止。

属性赋值操作首先会检查原型链来判断属性是否允许赋值,就算允许,也是在原始对象上进行赋值操作或者创建新属性,而不会修改原型链,所以只有查询的时候才会体会到继承

对象删除

可以利用delete 运算符删除对象的属性,但是delete只是断开属性和宿主对象的联系,而不会去操作属性中的属性,这样的话已经删除的属性的引用依然存在,所以可能会存在内存泄漏

const a = {
  b:{
    c:'lalala'
  }
}
let aa = a.b;
delete a.b; // 不能直接delete a; 
console.log(a) // {}
console.log(aa) // {c:'lalala'}

delete只能删除自有属性,不能删除继承属性,不能删除可配置性为false的属性(比如通过变量声明和函数声明创建的全局对象的属性)

检测属性
  • in运算符:左侧是属性名,右侧是对象,in可以区分不存在的属性和存在但是为null的属性
  • hasOwnProperty():用来检测给定的名字是否是对象的自有属性,继承属性会返回false
  • propertyIsEnumerable():只有检测到是自有属性且可枚举时才返回true
数组

js数组是js对象的特殊形式数组是值的有序集合,每一个值叫做集合,每个元素在数组中有一个位置,以数字表示,被称为索引(和对象中的属性类比一下),判断是否为数组可以用Array.isArray(arr).

常用的数组方法
  • 栈和队列方法

    • pop()
    • push()
    • shift()
    • unshift()
  • 操作方法

    • concat() // 将多个数组或者多个值拼接起来,并返回一个新数组,如果什么都不传会返回一个复制的新数组 💖不会改变原数组
    • slice(from, to) // 返回指定数组的片段或子数组,返回新数组,传一个0会返回一个复制的新数组💖不会改变原数组
    • splice(start, length, […args]) // 从数组中插入或者删除元素,第一个参数是删除开始的下标,第二个参数是删除几个,填0的话表示一个都不删,忽略表示全部都删,第三个参数开始指定需要插入到数组中的元素,返回的是被删除的元素💖会改变原数组
    • fill(value, from, to) // 批量填充某个值,第一个参数表示拿来填充的值,第二个参数表示开始填充的索引,默认为0,第三个参数表示结束索引,默认为数组长度,这个函数只能修改数组内容,并不能增加数组长度,如果value值为一个引用数据类型,则fill之后,数组里面的值指向的是同一个地址。如果改变了其中一个,则其它的都会改变
  • 位置方法

    • indexOf() // 如果没有,返回-1
    • lastIndexOf() // 如果没有,返回-1
    • includes() // 如果没有,返回false
  • 迭代方法

    • every()

      // 手写every函数
      Array.prototype.every = function(fn){
        if(typeof fn !== 'function'){
          return new Error(`${fn} is not a function`)
        }
        for(let i = 0; i < this.length; i++){
          if(!fn(this[i])){
            return false
          }
        }
        return true
      };
      
    • some()

      // 手写some
      Array.prototype.some = function(fn){
        if(typeof fn !== 'function'){
          return new Error(`${fn} is not a function`)
        }
        for(let i = 0; i < this.length; i++){
          if(fn(this[i])){
            return true
          }
        }
        return false
      };
      
    • forEach()

      // 手写forEach
      Array.prototype.forEach = function(fn){
        if(typeof fn !== 'function'){
          return new Error(`${fn} is not a function`)
        }
        let res = [];
        for(let i = 0; i < this.length; i++){
          fn(this[i],i,this)
        }
      };
      
    • map()

      // 手写map
      Array.prototype.map = function(fn){
        if(typeof fn !== 'function'){
          return new Error(`${fn} is not a function`)
        }
        let res = [];
        for(let i = 0; i < this.length; i++){
          res.push(fn(this[i],i,this))
        }
        return res
      };
      
    • filter()

      // 手写filter
      Array.prototype.filter = function(fn){
        if(typeof fn !== 'function'){
          return new Error(`${fn} is not a function`)
        }
        let res = [];
        for(let i = 0; i < this.length; i++){
          fn(this[i]) && res.push(fn(this[i]))
        }
        return res
      };
      
    • find()

      // 手写find
      Array.prototype.filter = function(fn){
        if(typeof fn !== 'function'){
          return new Error(`${fn} is not a function`)
        }
        
        for(let i = 0; i < this.length; i++){
         if(fn(this[i])) {
           return this[i]
         }
        }
        return res
      };
      
  • 归并方法

    • reduce()

      arr.reduce(function(prev,cur,index,arr){
        //TODO
      },init)
      

      arr:原数组

      prev:上一次调用回调时的返回值,或者初始的init

      cur:当前正在处理的数组元素

      index:当前正在处理的数组元素的索引,如果有init,则为0,否则为1;

      init:初始值

      一般而言,reduce主要用来处理数组内部的数据间的关系,比如累加,数组项比较大小,数组去重等等

      ? 如果有初始值,相当于从初始值出发进行遍历,从数组的第一个值开始计算,如果没有初始值,相当于直接处理第一个数和第二个数

      2.基础用法
      // 累加
      let arr = [1,2,3,4,5];
      arr.reduce((pre,curr)=>{  
          return pre+curr
      })  // 15
      // 有初始值的累加
      let arr = [1,2,3,4,5];
      arr.reduce((pre,curr)=>{  
          return pre+curr
      },2) // 17,此时初始值就相当于是遍历的第一个值
      
      // 求数组最大值,此时只是数据内部的比较,不需要设置初始值
      let arr = [1,2,3,4,5];
      arr.reduce((pre,curr)=>{
          return Math.max(pre,curr)
      })
      
      // 简单数组去重
      let arr = [1,2,3,4,5,5,6,7];
      arr.reduce((pre,curr)=>{
          pre.indexOf(curr) === -1 && pre.push(curr)
          return pre
      },[]) // 初始化一个空数组,将处理之后的数据push到空数组中并返回
      
      // 对象数组去重
      let arr = [{key:1},{key:2},{key:3},{key:4},{key:5},{key:5},{key:6},{key:7}];
      let obj = {};
      arr.reduce((pre,curr)=>{
          obj[curr.key] ? '': obj[curr.key]=true && pre.push(curr)
          return pre
      },[]) // 根据数组对象中的唯一性属性进行判断,例子是key,借助obj对象来判断key值是否已经存在,如果不存在,则push,否则跳过
      
      // 手写reduce
      Array.prototype.reduce = function(fn, prev){
        for(let i =0; i < i.length; i++){
          if(typeof prev === 'undefined'){
            prev = fn(this[i],this[i+1],i+1, this);
            ++i;
          }else {
            prev = fn(prev, this[i],i, this)
          }
        }
        return prev
      }
      
    • reduceRight()

  • 排序方法

    • sort()

      默认按升序排列,会调用每个数组项的toString方法,然后比较得到的字符串,也接收一个比较函数

      // 快排方式
      const quickSort = (arr,s,e)=>{
          if(arr.length<=1) return 
          if(s>=e) return 
          let p = arr[s] 
          let i = s 
          let j = e 
          
          while(i!=j){
      
              while(arr[j]>=p&&i<j){
                  j-- 
              }
      
              while(arr[i]<=p&&i<j){
                  i++ 
              }
      
              if(i<j){
                  let temp = arr[i] 
                  arr[i] = arr[j] 
                  arr[j] = temp 
              }
          }
          arr[s] = arr[i] 
          arr[i] = p 
          console.log(s,e,p,arr[i] )
          quickSort(arr,s,i-1) 
          quickSort(arr,i+1,e) 
      }
      
    • reverse() // 直接将数组反序

      // 手写reverse
      Arrray.prototype.reverse = function(fn){
        for(let i = 0; i < this.length/2; i++){
          let temp = this[i];
          this[i] = this[this.length - i -1];
          this[this.length - i -1] = temp
        }
        return this
      }
      
  • 其他数组处理方法

    • join()

    • flat()

   // 手写flat
 function flat(arr,d=1){
  	return d > 0 ? arr.reduce((pre,curr)=>pre.concat(Array.isArray(curr)? flat(curr,d-1): 	curr),[]):arr.slice()
}
    // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
  • flatMap() // 首先使用映射函数映射每个元素,然后将结果压缩成一个新数组
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值