面向对象

一、认识对象

在 JavaScript 中,对象是一种用于表示数据和行为的数据结构。它可以用来表示各种数据类型,如数字、字符串、数组、函数等。一个对象由若干属性组成,这些属性可以是任何类型的值。

  1. 什么是对象

    • 对象的定义:对象的定义是通过一个花括号内的键值对来表示的。键是属性的名称,值可以是任何类型的值。例如:

      var person = {
      name: 'Tom',
      age: 30,
      // 不符合js标识符命名规范的属性需要用引号包裹
      'nick-name': 'TT',
      // 对象中最后一个键值对后面可以不写逗号
      address: {
      street: '123 Main St',
      city: 'Anytown',
      state: 'CA'
      }
      }
      

      在上面的例子中,person 是一个对象,它具有三个属性:name、age 和 address。这些属性分别用加引号的键表示。

    • 对象访问:对象的属性可以使用点符号和方括号来访问。例如:

      console.log(person.name);
      // 如果属性名不符合JS标识符命名规范,则必须用方括号的写法来访问
      console.log(person['nick-name']);
      // 如果属性名以变量形式存储,则必须使用方括号形式
      var key = 'age'
      console.log(person[key])
      
    • 对象属性值修改:直接使用赋值运算符重新对某属性赋值即可更改属性。例如:

      person.name = 'Amy'
      console.log(person.name)
      
    • 对象属性值创建: 如果对象本身没有某个属性值,则用点语法赋值时,这个属 性会被创建出来。例如:

      person.sex = '男'
      console.log(person)
      
    • 对象属性值删除:可以使用 delete 运算符来删除对象的属性。例如:

      delete person.address.city;
      console.log(person)
      
  2. 对象的方法

    • 对象的方法定义:如果某个属性值是函数、则这个属性就是对象的方法。例如:

      var person = {
      name: 'Tom',
      age: 30,
      say: function() {
      console.log('大家好,我是Tom')
      }
      }
      
    • 对象的方法调用:通过点语法可以调用对象方法

      person.say()
      
    • 方法和函数的区别:方法也是函数,只不过方法是对象的“函数属性”,它需要用对象打点调用

  3. 对象的遍历

    • 通过for...in...循环,例如
      for (var key in person) {
      console.log('属性:' + key + ';值为:' + person[key])
      }
      
    • 通过Object.keys获取所有键名数组,再通过键名进行遍历
      var keys = Object.keys(person)
      for (var i = 0; i < keys.length; i++) {
      console.log('属性:' + keys[i] + ';值为:' + person[keys[i]])
      }
      
  4. 对象的深浅克隆

    • 浅克隆

      var obj1 = {
      a: 1,
      b: 2,
      c: [44, 55, 66]
      };
      // 实现浅克隆
      var obj2 = {};
      for (var k in obj1) {
      // 每遍历一个k属性,就给obj2也添加一个同名的k属性
      // 值和obj1的k属性值相同
      obj2[k] = obj1[k];
      }
      // 为什么叫浅克隆呢?比如c属性的值是引用类型值,那么本质上obj1和obj2的c属性是内存中的同一个数组,并没有被克隆分开。
      obj1.c.push(77);
      // obj2的c属性这个数组也会被增加77数组
      console.log(obj2);
      // true,true就证明了数组是同一个对象
      console.log(obj1.c == obj2.c);
      
    • 深克隆

      var obj1 = {
      a: 1,
      b: 2,
      c: [
      33,
      44,
      {
       m: 55,
       n: 66,
       p: [77, 88]
      }
      ]
      };
      // 深克隆
      function deepClone(o) {
      // 要判断o是对象还是数组
      if (Array.isArray(o)) {
       // 数组
       var result = [];
       for (var i = 0; i < o.length; i++) {
           result.push(deepClone(o[i]));
       }
      } else if (typeof o == 'object') {
       // 对象
       var result = {};
       for (var k in o) {
           result[k] = deepClone(o[k]);
       }
      } else {
       // 基本类型值
       var result = o;
      }
      return result;
      }
      var obj2 = deepClone(obj1);
      console.log(obj2);
      // false
      console.log(obj1.c == obj2.c);      
      obj1.c.push(99);
      // obj2不变的,因为没有“藕断丝连”的现象
      console.log(obj2);
      obj1.c[2].p.push(999);
      // obj2不变的,因为没有“藕断丝连”的现象
      console.log(obj2);
      

二、函数上下文

1.什么是函数上下文

在了解函数上下文之前,我们先看一句话:首歌很好听。

我们单独看到这句话的时候会很懵逼,到底那首歌很好听呢,在这句话中这个字给我们的感觉前面是有其他话的,比如:我在听青花瓷,这首歌很好听。在这句话中,这个字就是这句话的上下文,它表示的意思要通过这句话的前言后语来判断。

那么在函数中我们可以使用this关键字来表示函数的上下文,函数中的this具体指代什么,必须通过调用函数时的“前言后语”来判断。

var person = {
  name: 'Tom',
  age: 30,
  say: function() {
    console.log('大家好,我是' + this.name)
  }
}
person.say()

在上面的代码中this表示了person这个对象,我们再来看下另一段代码:

var person = {
  name: 'Tom',
  age: 30,
  say: function() {
    console.log('大家好,我是' + this.name)
  }
}
var newSay = person.say
newSay()

在这段代码中函数调用形式变了,this不在是person对象,而是window对象,由此我们可以得出一个结论:函数的上下文由调用方式决定

var person = {
  a: 20,
  b: 30,
  cal: function() {
    // 判断下this的上下文
    console.log(this.a + this.b)
  }
}

重点:函数的上下文只有在函数被调用时才能被确定

2.如何判断函数上下文
  • 规则1:对象打点调用它的方法函数,则函数的上下文是这个打点的对象
    面试题1

    function fn() {
    console.log(this.a + this.b);
    }
    var obj = {
    a: 66,
    b: 33,
    fn: fn
    };
    obj.fn();
    

    面试题2

    var obj1 = {
    a: 1,
    b: 2,
    fn: function () {
    console.log(this.a + this.b)
    }
    };
    var obj2 = {
    a: 3,
    b: 4,
    fn: obj1.fn
    };
    obj2.fn();
    

    面试题3

    function outer() {
    var a = 11;
    var b = 22;
    return {
    a: 33,
    b: 44,
    fn: function () {
      console.log(this.a + this.b);
    }
    };
    }
    outer().fn();
    

    面试题4

    function fun() {
    console.log(this.a + this.b);
    }
    var obj = {
    a: 1,
    b: 2,
    c: [{
    a: 3,
    b: 4,
    c: fun
    }]
    };
    var a = 5;
    obj.c[0].c();
    
  • 规则2:圆括号直接调用函数,则函数的上下文是window对象
    面试题1

    var obj1 = {
    a: 1,
    b: 2,
    fn: function () {
    console.log(this.a + this.b);
    }
    };
    var a = 3;
    var b = 4;
    var fn = obj1.fn;
    fn();
    

    面试题2

    function fun() {
    return this.a + this.b;
    }
    var a = 1;
    var b = 2;
    var obj = {
    a: 3,
    b: fun(),
    fun: fun
    };
    var result = obj.fun();
    console.log(result);
    
  • 规则3:数组(类数组对象)枚举出函数进行调用,上下文是这个数组(类数组对象)
    类数组对象:所有键名为自然数序列(从0开始),且有length属性的对象,arguments对象是最常见的类数组对象,它是函数的实参列 表
    面试题1

    var arr = ['A' ,'B' ,'C', function () {
    console.log(this[0])
    }];
    arr[3]()
    

    面试题2

    function fun() {
    arguments[3]();
    }
    fun('A', 'B', 'C', function () {
    console.log(this[1]); 
    });
    
  • 规则4:定时器、延时器调用函数,上下文是window对象
    面试题1

    var obj = {
    a: 1,
    b: 2,
    fun: function () {
    console.log(this.a + this.b);
    }
    }
    var a = 3;
    var b = 4;
    setTimeout(obj.fun, 2000);
    

    面试题2

    var obj = {
    a: 1,
    b: 2,
    fun: function () {
    console.log(this.a + this.b);
    }
    }
    var a = 3;
    var b = 4;
    setTimeout(function() {
    obj.fun();
    }, 2000);
    
  • 规则5:IIFE(立即执行函数)中的函数,上下文是window对象

    (function() {
    })();
    

    面试题1

    var a = 1;
    var obj = {
    a: 2,
    fun: (function () {
    var a = this.a;
    return function () {
      console.log(a + this.a);
    }
    })()
    };
    obj.fun();
    
  • 规则6:事件处理函数的上下文是绑定事件的DOM元素

    DOM元素.onclick = function () {
    };
    

三、构造函数和类

构造函数是一种函数调用方式

1、如何创建一个构造函数

我们可以使用new操作符来创建构造函数:new 函数(),构造函数执行步骤:

  • 执行流程1:函数体内会自动创建出一个空白对象
  • 执行流程2:函数的上下文(this)会指向这个对象
  • 执行流程3:函数体内的语句会执行
  • 执行流程4:函数会自动返回上下文对象,即使函数没有return语句

我们来看下下面的代码:

function fun() {
  this.a = 3;
  this.b = 5;
}
var obj = new fun()
console.log(obj)
2、什么是构造函数
  • 用new调用一个函数,这个函数就被称为“构造函数”,任何函数都可以是构造函数,只需要用new调用它
  • 构造函数用来“构造新对象”,它内部的语句将为新对象添加若干属性和方法,完成对象的初始化
  • 构造函数必须用new关键字调用,否则不能正常工作,正因如此,开发者约定构造函数命名时首字母要大写
    function People(name, age, sex) {
    this.name = name
    this.age = age
    this.sex = sex
    }
    var xiaoming= new People('小明', 12, '男');
    var xiaohong= new People('小红', 10, '女');
    var xiaogang= new People('小刚', 13, '男');
    

思考上面代码,如果new调用构造函数,会是怎样的?

3、类与实例
  • 类只描述对象会拥有哪些属性和方法, 但是并不具体指明属性的值
  • 实例是具体的对象 类与实例

举个通俗易懂的例子,“狗”是类,“哈士奇”是实例、“萨摩耶”也是实例

四、原型和原型链

任何函数都有prototype(原型)属性,prototype属性值是个对象,默认拥有constructor(构造器)属性指回函数本身

function sum(a, b) {
  return a + b
}
console.log(sum.prototype)
console.log(typeof sum.prototype)
console.log(sum.prototype.constructor === sum)
  • 普通函数来说的prototype属性没有任何用处,而构造函数 的prototype属性非常有用
  • 构造函数的prototype属性是它的实例的原型

类与实例

function People(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}
// 实例化
var xiaoming = new People('小明', 12, '男');
// 测试三角关系是否存在
console.log(xiaoming.__proto__ === People.prototype);
1、原型链查找

实例打点访问它的原型的属性和方法

function People(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}
People.prototype.national = '中国'
var xiaoming = new People('小明', 12, '男');
console.log(xiaoming.national);

hasOwnProperty方法可以检查对象是否真正“自己拥有” 某属性或者方法

function People(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}
People.prototype.national = '中国'
var xiaoming = new People('小明', 12, '男');
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('age'); // true
xiaoming.hasOwnProperty('sex'); // true
xiaoming.hasOwnProperty('nationality');

in运算符只能检查某个属性或方法是否可以被对象访问,不能检查是否是自己的属性或方法

function People(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}
People.prototype.national = '中国'
var xiaoming = new People('小明', 12, '男');
'name' in xiaoming // true
'age' in xiaoming // true
'sex' in xiaoming // true
'nationality' in xiaoming // true
2、原型链的终点

类与实例

function People(name, age) {
  this.name = name;
  this.age = age;
}
var xiaoming = new People('小明', 12);

console.log(xiaoming.__proto__.__proto__ === Object.prototype);     // true
console.log(Object.prototype.__proto__);                            // null

console.log(Object.prototype.hasOwnProperty('hasOwnProperty'));     // true
console.log(Object.prototype.hasOwnProperty('toString'));           // true

五、继承

1、定义
  • 继承描述了两个类之间的”is a kind of“关系
  • ”父类“:超类、基类;”子类“:派生类
  • 子类丰富了父类,让类描述的更具体、更细化
2、实现继承
  • 子类必须拥有父类的全部属性和方法,同时子类还应该能定义自己特有的属性和方法
  • 使用JavaScript特有的原型链特性来实现继承,是普遍的做法
// 父类,人类
function People(name, age, sex) {
  this.name = name;
  this.age = age;
  this.sex = sex;
}
People.prototype.sayHello = function () {
  console.log('你好,我是' + this.name + '我今年' + this.age + '岁了');
};
People.prototype.sleep = function () {
  console.log(this.name + '开始睡觉,zzzzz');
};
// 子类,学生类
function Student(name, age, sex, scholl, studentNumber) {
  this.name = name;
  this.age = age;
  this.sex = sex;
  this.scholl = scholl;
  this.studentNumber = studentNumber;
}
// 关键语句,实现继承
Student.prototype = new People();

Student.prototype.study = function () {
  console.log(this.name + '正在学习');
}
Student.prototype.exam = function () {
  console.log(this.name + '正在考试,加油!');
}
// 重写、复写(override)父类的sayHello
Student.prototype.sayHello = function () {
  console.log('敬礼!我是' + this.name + '我今年' + this.age + '岁了');
}
// 实例化
var hanmeimei = new Student('韩梅梅', 9, '女', '慕课小学', 100556);
hanmeimei.study();
hanmeimei.sayHello();
hanmeimei.sleep();
var laozhang = new People('老张', 66, '男');
laozhang.sayHello();

六、面向对象

  • 定义:定义不同的类、上类的实例工作
  • 优点:程序编写更清晰,代码结构更严密,使代码更健壮更便于维护
  • 使用场合:需要封装和复用性场合

案例:页面上做100个红绿灯,点击红灯就变黄,点击黄灯就变绿,点击绿灯就变回红灯,图片资源下载地址

<div id="box"></div>
#box img{
  width: 80px;
}
// 定义红绿灯类
function TrafficLight() {
  // 颜色属性,一开始都是红色
  // 红色1、黄色2、绿色3
  this.color = 1;
  // 调用自己的初始化方法
  this.init();
  // 绑定监听
  this.bindEvent();
}
// 初始化方法
TrafficLight.prototype.init = function() {
  // 创建自己的DOM
  this.dom = document.createElement('img');
  // 设置src属性
  this.dom.src = 'images/' + this.color + '.jpg';
  box.appendChild(this.dom);
};
// 绑定监听
TrafficLight.prototype.bindEvent = function() {
  // 备份上下文,这里的this指的是JS的实例
  var self = this;
  // 当自己的dom被点击的时候
  this.dom.onclick = function () {
      // 当被点击的时候,调用自己的changeColor方法
      self.changeColor();
  };
}
// 改变颜色
TrafficLight.prototype.changeColor = function () {
  // 改变自己的color属性,从而有一种“自治”的感觉,自己管理自己,不干扰别的红绿灯
  this.color ++;
  if (this.color == 4) {
      this.color = 1;
  }
  // 光color属性变化没有用,还要更改自己的dom的src属性
  this.dom.src = 'images/' + this.color + '.jpg';
};
// 得到盒子
var box = document.getElementById('box');
// 实例化100个
var count = 100;
while(count--){
  new TrafficLight();
}

七、JS内置对象

Math对象
// 最大值
console.log(Math.max(44, 55, 33, 22));
// 最小值
console.log(Math.min(44, 55, 33, 22)); 
// 四舍五入
console.log(Math.round(3.49));
console.log(Math.round(3.51));
Date对象
var d = new Date();

console.log('日期', d.getDate());
console.log('星期', d.getDay());
console.log('年份', d.getFullYear());
console.log('月份', d.getMonth() + 1);
console.log('小时', d.getHours());
console.log('分钟', d.getMinutes());
console.log('秒数', d.getSeconds());
Copyright © 高笑石 (2021 - present) all right reserved,powered by Gitbook文件修订时间: 2023-05-19 12:32:08

results matching ""

    No results matching ""