跳至主要內容

7. 一般对象

Harry Xiong大约 14 分钟Web 前端JavaScriptPromise 对象JS 异步async 与 await

一般对象

7.1 对象的分类

JS 中数据类型有 String Number Boolean Null Undefined Object 前五个为基本数据类型,Object 对象为引用

数据类型,值只要不是前 5 种,都是对象

//使用基本数据类型创建变量
var name = "孙悟空";
var gender = "男";
var age = 18;
//使用基本数据类型的创建数据,我们所创建的变量都是独立,不能成为一个整体

//使用对象创建变量
var obj = { name: "孙悟空", gender: "男", age: 18 };
//对象属于一个复合的数据类型,在对象中可以保存多个不同数据类型的属性

对象的分类:

  • 内建对象 由 es 标准中定义的对象,在任何的 ES 的实现中都可以使用,如 Math String Number Boolean Function Object 等

  • 宿主对象 由 js 的运算环境提供的对象,目前来讲主要指由浏览器提供的对象如 BOM DOM,如 console.log 中的 console 和 document.write 中的 document 就是一个对象

  • 自定义对象 由开发人员自己创建的对象

7.2 创建对象

7.2.1 构造函数创建对象

使用 new 关键字调用的函数,是构造函数 construstor,构造函数是专门用来创建对象的函数

var obj = new Object(); //通过构造函数的方式创建对象
console.log(typeof obj); //使用typeof检查一个对象时,会返回object
console.log(obj); //这里obj是一个空的对象,之间打印这个对象会返回Oject{}
  • 属性

在对象中保存的值称为属性,向对象中添加属性

语法:

对象.属性名=属性值 或 对象["属性名"]=属性值;

var obj = new Object();
obj.name = "孙悟空";
obj.gender = "男";
obj.age = 18;
console.log(obj); //在这里obj中有值会打印出Object{name:"孙悟空",gender:"男",age:18}
  • 读取对象中的属性

语法:

对象.属性名 或 对象["属性名"]

**注:**如果读取对象中没有的属性,不会报错而是会返回 undefined

  • 修改对象的属性值

语法:对象.属性名=新值 或 对象["属性名"]=新值

  • 删除对象的属性

语法:delete 对象.属性名 或 delete 对象["属性名"]

**注:**删除后的属性不再存在于整个对象中,如果再次读取会返回 undefined

  • 验证对象中是否有该属性

语法:"属性名" in 对象

**注:**属性名实质上时字符串,所以要加引号

  • 属性名

  • 对象的属性名不强制要求遵守标识符的规范,但是使用时尽量按照标识符的规范去做

  • 如果在创建属性名时用的是.或者[]方法中的一个,在调用和修改删除时也要用同样的方式调用

  • 要使用特殊的属性名,不能使用.的方式,需要用 对象["属性名"]=属性值的方式

    使用[]这种形式去操作属性更加的灵活,在

    []中甚至可以直接传递一个变量

    ,这样就能通过变量名来改变属性名,

    .只能跟去掉了""的字符串.的方式,不能使用变量

    var obj = new Object();
    var x = "name";
    
    obj["name"] = "孙悟空";
    obj["gender"] = "男";
    
    console.log(obj[x]); //打印结果为孙悟空
    x = "gender";
    console.log(obj[x]); //打印结果为男
    

    注意:

      • 如果[]中是一个变量就不要加引号,否则会认为是一个字符串
      • []里面除了字符串必须加引号,其他的可以不加引号,和基本数据类型相似
  • 属性值

JS 对象的属性值,可以是任意的数据类型,包括对象

var obj=new Object();
obj.test=obj2;
obj2=new Object();
Obj2.name="猪八戒";

console.log(obj.test);//打印出obj2对象
console.log(obj.test.name);//打印出猪八戒
//由此可以说明对象的属性值可以是以对象来构成无限嵌套关系

7.2.2 对象字面量创建对象

语法:{属性名:属性值,属性名:属性值....}

var obj = { name: "孙悟空", gender: "男", age: 18 };

注意:

  • 属性名和属性值之间的=要变成:

  • 对象字面量的属性名加引号也可以不加,建议不加。但是如果使用一些特殊非法的名字,必须加引号,汉字也 可以使用,对象字面量里面也可以嵌套使用对象字面量

对象字面量创建对象的简化操作

  • 在创建函数的时候可以省略:function,直接用函数名

  • 可以使用[]对属性名进行操作

var obj = {
  ["a" + "b"]: 123,

  /*
    say:function(){
        alert("hello");
    }
    */
  //方法的简写
  say() {
    alert("hello");
  },
};

7.2.3 枚举对象中的属性

for…in 语句与 for...of 语句能够枚举对象中的属性,对象中有几个属性,循环体就会执行几次 注:

    • for...of 语句只能遍历数组,不能遍历 JSON 对象
    • for...in 的性能很差,因为会遍历对象的原型对象
/*
语法:
for(var 变量 in/of 对象){
}
*/
var obj = { name: "孙悟空", gender: "男", age: 18 };
for (var i in obj) {
  console.log(obj[i]); //打印出属性值
}
/*
for…in语句
每次执行时,会将对象中的属性名赋值给变量
可以利用console.log(n);来获取属性值,n取得的属性值都会转化为字符串的形式,和前面的查找一个属性是否在字符串里面用in的情况不同
*/

for (var i of obj) {
  console.log(i); //打印出属性值
}
/*
for...of语句
每次执行时,会将数组中的值赋值给变量,和for...in语句有所差别
*/

7.3 原型类与原型继承

7.3.1 简述

在传统的基于 Class 的语言如 Java、C++ 中,继承的本质是扩展一个已有的 Class,并生成新的 Subclass。

由于这类语言严格区分类和实例,继承实际上是类型的扩展。但是,JavaScript 由于采用原型继承,我们无法直接扩展一个 Class,因为根本不存在 Class 这种类型。

但是办法还是有的。我们先回顾 Student 构造函数:

function Student(props) {
  this.name = props.name || "Unnamed";
}

Student.prototype.hello = function() {
  alert(`Hello, ${this.name}!`);
};

以及 Student 的原型链:

js-proto

现在,我们要基于 Student 扩展出 PrimaryStudent,可以先定义出 PrimaryStudent:

function PrimaryStudent(props) {
  // 调用Student构造函数,绑定this变量:
  Student.call(this, props);
  this.grade = props.grade || 1;
}

但是,调用了 Student 构造函数不等于继承了 StudentPrimaryStudent 创建的对象的原型是:

new PrimaryStudent()---- >
  PrimaryStudent.prototype---- >
  Object.prototype---- >
  null;

必须想办法把原型链修改为:

new PrimaryStudent()---- >
  PrimaryStudent.prototype---- >
  Student.prototype---- >
  Object.prototype---- >
  null;

这样,原型链对了,继承关系就对了。新的基于 PrimaryStudent 创建的对象不但能调用 PrimaryStudent.prototype 定义的方法,也可以调用 Student.prototype 定义的方法。

如果您想用最简单粗暴的方法这么干:

PrimaryStudent.prototype = Student.prototype;

是不行的!如果这样的话,PrimaryStudentStudent 共享一个原型对象,那还要定义 PrimaryStudent 干啥?

我们必须借助一个中间对象来实现正确的原型链,这个中间对象的原型要指向 Student.prototype。为了实现这一点,参考发明 JSON 的道格拉斯的代码,中间对象可以用一个空函数 F 来实现:

// PrimaryStudent构造函数:
function PrimaryStudent(props) {
  Student.call(this, props);
  this.grade = props.grade || 1;
}

// 空函数F:
function F() {}

// 把F的原型指向Student.prototype:
F.prototype = Student.prototype;

// 把PrimaryStudent的原型指向一个新的F对象,F对象的原型正好指向Student.prototype:
PrimaryStudent.prototype = new F();

// 把PrimaryStudent原型的构造函数修复为PrimaryStudent:
PrimaryStudent.prototype.constructor = PrimaryStudent;

// 继续在PrimaryStudent原型(就是new F()对象)上定义方法:
PrimaryStudent.prototype.getGrade = function() {
  return this.grade;
};

// 创建xiaoming:
const xiaoming = new PrimaryStudent({
  name: "小明",
  grade: 2,
});

xiaoming.name; // '小明'
xiaoming.grade; // 2

// 验证原型:
xiaoming.__proto__ === PrimaryStudent.prototype; // true
xiaoming.__proto__.__proto__ === Student.prototype; // true

// 验证继承关系:
xiaoming instanceof PrimaryStudent; // true
xiaoming instanceof Student; // true

用一张图来表示新的原型链:

js-proto-extend

注意,函数 F 仅用于桥接,我们仅创建了一个 new F() 实例,而且,没有改变原有的 Student 定义的原型链。

如果把继承这个动作用一个 inherits() 函数封装起来,还可以隐藏 F 的定义,并简化代码:

function inherits(Child, Parent) {
  const F = () => {};

  F.prototype = Parent.prototype;
  Child.prototype = new F();
  Child.prototype.constructor = Child;
}

这个 inherits() 函数可以复用:

function Student(props) {
  this.name = props.name || "Unnamed";
}

Student.prototype.hello = function() {
  alert(`Hello, ${this.name}!`);
};

function PrimaryStudent(props) {
  Student.call(this, props);
  this.grade = props.grade || 1;
}

// 实现原型继承链:
inherits(PrimaryStudent, Student);

// 绑定其他方法到PrimaryStudent原型:
PrimaryStudent.prototype.getGrade = function() {
  return this.grade;
};

原型继承总结

JavaScript 的原型继承实现方式就是:

  1. 定义新的构造函数,并在内部用 call() 调用希望“继承”的构造函数,并绑定 this

  2. 借助中间函数 F 实现原型链继承,最好通过封装的 inherits 函数完成;

  3. 继续在新的构造函数的原型上定义新方法。

7.3.2 定义类

function Person() {
  this.name = "张三";
  this.age = 20;
}
var p = new Person();
alert(p.name);

构造函数和原型链里面定义

// 声明的构造方法

  function Person(){
    this.name = "张三";
    this.age=20;
    this.run = function()){
    alert(this.name+"在运动");
   }
 }

 // 原型链的属性和方法

Person.prototype.sex="男";
Person.prototype.work=function(){
 alert(xx)
}
var p = new Person();
p.work();

静态方法

Person.getInfo = function() {};

7.3.3 原型继承

对象冒充继承

  // 要实现Web类 继承 Person类 原型链+对象冒充的组合继承模式

  function Person(){
 this.name = "张三";
    this.age=20;
    this.run = function()){
      alert(this.name+"在运动");
    }
  }

  // 原型链的属性和方法

  Person.prototype.sex="男";
  Person.prototype.work=function(){
    alert(xx)
  }

  // 要实现Web类 继承 Person类

  function Web(){
    Person.call(this); //对象冒充继承
  }
  var w = new Web();
  w.run();//执行父类Person的run,对象冒充可以继承构造函数里面的属性和方法
  w.work();// 对象冒充可以继承构造函数的属性和方法 但是没办法继承原型链的属性和方法(prototype)

关于 call:

function add(c, d) {
  return this.a + this.b + c + d;
}

const obj = { a: 1, b: 2 };
console.error(add.call(obj, 3, 4)); // 10

大统上的说法就是,call 改变了 this 的指向。但是实际发生的过程,可以看成下面的样子。

const o = {
  a: 1,
  b: 2,
  add: function(c, d) {
    return this.a + this.b + c + d;
  },
};

给 o 对象添加一个 add 属性,这个时候 this 就指向了 o,

o.add(5,7)得到的结果和 add.call(o, 5, 6)相同。

原型链继承方法

function Person(){
    this.name = "张三";
    this.age=20;
    this.run = function()){
      alert(this.name+"在运动");
    }
  }

  // 原型链的属性和方法
  Person.prototype.sex="男";
  Person.prototype.work=function(){
    alert(xx)
  }

  // web原型链方式继承 person
  function web(){

  }

  web.prototype= new person(); // 原型链继承
  web.work();  // 可以工作,可以继承原型链属性方法

原型链实现继承的问题————无法给父类传参

 function Person(){
    this.name = "张三";
    this.age=20;
    this.run = function()){
      alert(this.name+"在运动");
    }
  }

  // 原型链的属性和方法
  Person.prototype.sex="男";
  Person.prototype.work=function(){
    alert(xx)
  }

  var p = new person('李四', 20);
  p.run(); // 没问题

  // 继承,无法给父类传参
  function Web(name,age){

  }

  Web.prototype= new Person();
  var w = new Web('sss', 20);
  w.run();// 父类会alert出来“undefiend在运动”
  // 实例化子类时候没法给父类传参

原型链+构造函数的组合继承模式

  function Person(){
    this.name = "张三";
    this.age=20;
    this.run = function()){
      alert(this.name+"在运动");
    }
  }

  // 原型链的属性和方法

  Person.prototype.sex="男";
  Person.prototype.work=function(){
    alert(xx)
  }

  var p = new person('李四', 20);
  p.run(); // 没问题
  // 继承,无法给父类传参

  function Web(name,age){
    Person.call(this,name,age); // 对象冒充继承 可以继承构造函数里面的属性和方法 实例化子类可以给父类传参
  }

  Web.prototype = new Person();// 实例化
  var w = new Web('sss', 20);
  w.run();// 父类会alert出来“undefiend在运动”

  // 实例化子类时候没法给父类传参

原型链+对象冒充的另一种写法

function Person(){
    this.name = "张三";
    this.age=20;
    this.run = function()){
      alert(this.name+"在运动");
    }
  }

  // 原型链的属性和方法
  Person.prototype.sex="男";
  Person.prototype.work=function(){
    alert(xx)
  }

  var p = new person('李四', 20);
  p.run(); // 没问题
  // 继承,无法给父类传参

  function Web(name,age){
    Person.call(this,name,age); // 对象冒充继承 可以继承构造函数里面的属性和方法 实例化子类可以给父类传参
  }

  Web.prototype = Person.prototype; // 和方法7中不同的是这里!!!
  var w = new Web('sss', 20);
  w.run();// 父类会alert出来“undefiend在运动”
  // 实例化子类时候没法给父类传参

7.4 JSON

JSON 全称为 JavaScript Object Notation(javaScript 对象表示法),JS 中的对象只有 JS 才能够解析并识别,其它语言都不能识别,所以如果需要做数据传输,就需要一个所有语言都能够识别的通用数据类型

JSON 就是一个特殊格式的字符串,这个字符串可以被任意的语言识别,并且可以转换为任意语言中的对象,JSON 在开发中主要用来数据的交互,JSON 和 JS 中对象的格式一致,只不过 JSON 字符串中的属性名和属性值必须用双引号括起来,除此之外内部结构的语法和 JS 中的语法一致

7.4.1 JSON 分类

  • 对象{}

  • 数组[]

**注意:**无论是对象还是数组都必须使用字面量形式的,并且在最外层都需要用引号包裹让整体成为字符串

7.4.2 属性值

在 JSON 中的属性值只允许以下六种

  • 字符串

  • 数值

  • 布尔值

  • null

  • 对象

  • 数组

**注:**undefined,symbol 值和函数都不能作为属性值,对象形式如果有这三种类型的值会在转换为 JSON 的时候被过滤掉,数组形式如果有会被转换为 null 传入 JSON 中

7.4.3 JSON 与对象的转换

JS 为我们提供了一个工具类对象 JSON,这个对象可以帮助我们将一个 JSON 转换为 JS 对象,也可以将一个 JS 对象转换为 JSON

  • JSON-->JS 对象

    通过 JSON.parse()函数可以将以 JSON 字符串转换为 JS 对象,该函数需要一个 JSON 字符串作为参数,会将 JSON 字符串转换为 JS 对象并返回

    var json = '{"name":"孙悟空","age":"18"}';
    var obj = JSON.parse(json);
    console.log(obj.name); //"孙悟空"
    
  • JS 对象-->JSON

    通过 JSON.stringify()函数可以将一个 JS 对象转换为 JSON 字符串,该函数需要一个 JS 对象作为参数,会返回一个 JSON 字符串

    var json = { name: "孙悟空", age: "18" };
    var json = JSON.stringify(obj);
    console, log(json); //'{"name":"孙悟空","age":"18"}'
    

    确认一个对象是否为空对象

    var obj = {};
    var str = JSON.stringify(obj).replace(/\s|\r\n/g, "");
    if (str.length === 2) {
      console.log("这是一个空对象");
    }
    

**注意:**JSON 这个类对象在 IE7 及以下的浏览器中不支持,所以在这些浏览器中调用时会报错

兼容方法

  • 使用 eval()函数来替代 JSON 的类对象

    /*
        eval()函数
            1.可以用来执行一段字符串形式的JS代码,并将指向结果返回
            2.如果使用eval()函数执行的字符串中含有{},该函数会将{}当做代码块,如果不希望其被当成代码块         解析,则需要用()将字符串包裹
    */
    var str="alert('hello')";
    eval(str);//会出现弹框
    
    var json='{"name":"孙悟空","age":"18"}';
    var obj=eval("("+json+")");
    console.log(obj);//{name:"孙悟空",age:"18"}
    

    注意:

    eval()函数的功能很强大.可以直接执行一个字符串中的 JS 代码,但是在开发中尽量不要使用该函数,因为该函数不仅性能较差,而且存在非常大的安全隐患,容易被植入恶意代码造成损失

  • 直接用外部引用的 JSON

7.5 栈内存与堆内存

在讲内存之前先将讲数据,在 js 中的数据分为两种,一种是引用型数据,一种是值类型数据

引用型数据只有 object,而值类型数据有 number,boolean,string,undefined,null。值类型数据在比较时,只比较是否长的一样,而引用型数据在比较时是比较内存地址

关于栈内存与堆内存

  • js 中的变量都是保存到栈内存中

  • 基本数据类型的值在栈内存中存储,值与值之间是独立存在,修改一个变量不会影响其他变量

  • 对象是保存到堆内存中的

    每创建一个新的对象,就会在堆内存中开辟一个新的空间,而变量保存的是对象的

    内存地址(对象的引用),如果两个变量保存的是同一个对象引用,当一个通过一个变量修改属性时,另一个也

    会受到影响

    var obj = { name: "孙悟空", gender: "男", age: 18 };
    var obj2 = obj;
    
    console.log(obj.age); //打印结果为18js
    
    obj2.age = 19;
    console.log(obj.age); //打印结果为19
    

    注意:

      • 如果只是让 obj2=null,obj 并不会变化,因为这是让 obj2 的地址改变,而不是改变地址里面的值
      • 当比较两个基本数据类型的值时,就是比较值,而比较两个引用数据类型时,比较的是对象的地址,如果两个对 象是一模一样的,但是地址不同,也会返回 false