【8期】JavaScript ——老哥,缺对象不?

定义一个对象

声明形式

var obj = {
  key: value,
  key1: value1,
  ...
}

构造形式

var obj = new Object();
obj.key = value;
...

区别

构造形式需要逐个添加属性,声明形式可以一次性添加多个属性。

 


对象类型

tips:Javascript的7中基本语言类型是:string,number,boolean,null,undefined,symbol,object.

内置对象

JS中的一些对象子类型,实际上只是一些内置函数,可以当作构造函数来使用,从而可以构造一个对呀子类型的新对象。

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error

对这段描述不太理解可以看一下这个例子:

var str = "I'm a string"
typeof str; // "string"
str instanceof String; // false
str.length;//13
str.charAt(3) //"m"

var strObj = new String("Im a string");
typeof strObj; // "object"
strObj instanceof String; // true

其实str这种创建方式,str只是一个字面量而不是对象,并且是一个不可变的值,那么为什么我们平时直接用这种方式也能获取到str的长度、访问其中某个字符呢?

自变量必须转换为对象才能进行一些操作,这里引擎会在必要时自动将字面量转换成String对象,因此可以访问String对象的属性和方法。

对于数值的字面量也是如此。

对于Object,Array,Function,RegExp,无论是文字形式还是构造形式,都是对象,不是字面量。

 


访问内容

存储在对象容齐内部的是属性的名称,就像一个指针,引用这些值真正的存储位置。

.key【属性访问】

var obj = {
  a: 2,
  b:7
}
obj.a; //2

[key]【键访问】

obj["b"]; // 7

可计算属性名

var pre = 'foo';
var obj = {
  [pre+'x']: "x",
  [pre+'y']: "y
}

obj["foox"];//x

区别

“.”操作符要求属性名满足标识符的命名规范。

注意:属性名永远是字符串,其他值若非字符串会被自动转换成字符串

var obj = {}
obj[3] = "3";
obj[obj] = "baz"

obj["3"] ; // "3"
obj["[object obj]"]; // "baz"

 


属性描述符

 

查看属性描述符:Object.getOwnPropertyDescriptor(obj, 属性)。

1.writable

writable决定是否可以修改属性的值。

var obj = {}
Object.defineProperty(obj, "a", {
  value: 2,
  writable: false,
  configurable:true,
  enumerable:true
})
obj.a = 3;
obj.a; //2,修改属性值失败,严格模式下还会抛出TypeError,表示无法修改一个不可写的属性。

2.configurable

只要属性是可配置的,就可以使用defineProperty()来修改属性描述符

var obj = {a:2}
obj.a = 3;
obj.a; //3
delete obj.a;
obj.a;//undefined
Object.defineProperty(obj, "a", {
  value: 4,
  writable: true,
  configurable:false,
  enumerable:true
})
obj.a;//4
obj.a = 5;//5
obj.a;//5
Object.defineProperty(obj, "a", {
  value: 6,
  writable: true,
  configurable:true,
  enumerable:true
}) // TypeError,把configurable改成false是单向操作,无法撤销


delete obj.a; //静默失败,因为属性是不可配置的
obj.a;//5

因此,把configurable改成false是单向操作,无法撤销。除了无法修改,还会禁止删除这个属性。

 

3.enumerable

是否可枚举,用户定义的普通属性默认都是true

[[Get]] 获取属性值

var obj = { a:2 }
obj.a;//2

以上代码是一次属性访问,我们看起来好像是在obj中查找名字为a的属性。

实际上,obj.a在obj上是实现了[[Get]]操作,像函数调用[[Get]]()。对象默认的内置[[Get]]操作首先在对象中查找是否有名称相同的属性,如果找到则返回这个属性的值。

否则,按照[[Get]]算法的定义会执行遍历可能存在的[[Prototype]]原型链,如果都未能找到,则返回undefined。

 

[[Put]]

设置属性值,实际行为如下,

如果对象中已经存在这个属性,[[Put]]会检查如下内容:

  1. 属性是否访问描述符?是且存在setter就调用setter
  2. 属性的数据描述符中writable是否为false?是,非严格模式下静默失败,严格模式下TypeError
  3. 如果都不是,将该值设为属性的值

 

Getter和Setter

当给一个属性定义setter或者getter时,这个属性会被定义为“访问描述符”(与“数据描述符”相对)。对于访问描述符,JS会忽略他们的value和writable特性,关心的是set和get(还有configurable,enumerable)特性。

var obj = {
  get a(){
    return 2;
  }
}
Object.defineProperty(
  obj,
  "b",
  {
    get: function(){ return this.a *3 },
    enumerable: true
  }
 )
obj.a;//2
obj.b;//6
obj.a =3; //会被忽略
obj.a;//2
var obj = {
  get a(){
    return this._a_;
  },
  set a(val){
    this._a_ = val * 2;
  }
}
obj.a =2;
obj.a; //4

 


不变性

常量对象

writable:false, configurable:false

禁止扩展 Object.preventExtensions()

var obj = { a:2 }
Object.preventExtensions(obj);
obj.b=3; //严格模式抛出错误
obj.b;//undefined,

密封 Object.seal()

实际上会调用Object.preventExtensions()同时把所有现有属性标记为configurable:false。因此,密封后不能添加新属性,不能重新配置或者删除现有属性(可以修改属性的值)

冻结 Object.freeze()

实际上会调用Object.seal()并且把所有“数据访问”属性标记为writable:false,无法修改他们的值。(只影响本身以及直接属性,不影响引用的其他对象)

深度冻结

现在这个对象上调用Object.freeze(),然后遍历它引用的对象并在这些对象上调用Object.freeze()。注意无意中冻结其他共享对象。

 


存在性

枚举

  • 属性 in obj:操作符会检查对象及其原型链,无论是否可枚举
  • obj.hasOwnProperty(属性):无论是否可枚举【只查找直接包含属性】
  • 不可枚举属性不会出现在for ...in...循环中
  • obj.propertyIsEnumerable(属性) //true/false  :可枚举【只查找直接包含属性】
  • Object.keys(obj):返回数组,包含所有可枚举属性【只查找直接包含属性】
  • Object.getOwnPropertyNames():返回数组,包含所有属性,无论是否可枚举【只查找直接包含属性】

 

遍历

for...in...:遍历对象可枚举属性名(包括原型链)

for ... of..: 遍历对象属性值【对象本身定义了迭代器】

var arr = [1,2,3]
for (var v of arr){
  console.log(v)
}
//1
//2
//3

在数组上for...in...不太好,因为这种枚举不仅会包含所有的数值索引,还会包含所有可枚举属性。最好只在对象上使用for/in循环。数组用传统的for循环。

 


复制对象

 

浅拷贝与深拷贝

浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用,如果拷贝后的对象发生变化,原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝。对于基本数据类型的拷贝,并没有深浅拷贝的区别。

function func(){}
var obj = {
  c: true
}
var array = [];
var myObj = {
  a:2,
  b:obj,//引用而非复本
  c:array, //引用而非复本
  d:func
}
array.push(obj, myObj)

浅拷贝

只复制引用,而未复制真正的值。复制的新对象中会复制a的值,但是新对象中bcd三个属性只是三个引用,和旧对象中bcd引用的对象是一样的。

function shallowClone(source) {
    var target = {};
    for(var i in source) {
        if (source.hasOwnProperty(i)) {
            target[i] = source[i];
        }
    }

    return target;
}

Object.assign(目标对象,源对象1,源对象2……): 拷贝可枚举(enumerable)的自有键。

var newObj = Object.assign({}, myObj);
newObj.a//2
newObj.b === obj; //true
newObj.c === array; //true
newObj.d === func; //true

深拷贝

对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。

除了复制myObj以外,还会复制array、obj。那么这里array又引用了obj和myobj,所以又要复制myObj,就产生了循环引用导致死循环。

目前实现深拷贝的方法不多,主要是两种:

1.利用 JSON 对象中的 parse 和 stringify(对象中含有一个函数时(很常见),就不能用这个方法进行深拷贝)

const cloneArray = JSON.parse(JSON.stringify(originArray));

2.利用递归来实现每一层都重新创建对象并赋值。

function deepClone(source){
  const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
  for(let keys in source){ // 遍历目标
    if(source.hasOwnProperty(keys)){
      if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下
        targetObj[keys] = source[keys].constructor === Array ? [] : {};
        targetObj[keys] = deepClone(source[keys]);
      }else{ // 如果不是,就直接赋值
        targetObj[keys] = source[keys];
      }
    } 
  }
  return targetObj;
}

JavaScript 中数组和对象自带的拷贝方法都是“首层浅拷贝”

concat 连接两个或者更多的数组,但是它不会修改已存在的数组,而是返回一个新数组。只是对数组的第一层进行深拷贝。

slice 只是对数组的第一层进行深拷贝。

... 实现的是对象第一层的深拷贝。后面的只是拷贝的引用值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咩咩羊10

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值