面向对象

现实世界面向对象:

对象:万事万物都是对象,每个对象都有自己的属性(共有属性,私有属性);
类:抽象对象的特性和功能,形成描述一类事物的抽象概念;在 js 中分为内置类和自定义类;
实例:类中的一个具体的个体。只要是类中的个体就会拥有这个类型全部的属性和特性。

每个人都是人类中的一个实例;
地球上的每个人都是对象,而此时对象属性就是大家都是生活在地球上
按照人类生活的陆地不同,将人类分为5大洲的人,如亚洲人、美洲人、非洲人、欧洲人…. 分类时是抽象了人类生存的陆地不同;
我们每个人就是亚洲人的一个实例,所以我们都有亚洲人的属性,我们都生活在亚洲这块陆地上

JS中的面向对象

面向对象的研究范畴:封装、类的继承和多态(重写、重载)

  1. 类:js中的类都是一个函数数据类型,都天生自带一个 prototype 属性,它的值是一个对象;
  2. prototype(原型):每个 prototype 对象都天生自带一个属性 constructor,这个属性的值指向当前类的构造函数本身;
  3. 对象(实例对象、prototype对象)都有一个 proto** **的属性,这个属性指向当前实例所属类的 prototype。

内置类:Array、String、Number、Function、Date 等
  • 内置类的原型:每个类都有自己的原型,存储这个类型公有的属性和方法的
  1. console.log(Array.prototype); // 数组的原型:存储的数组类型公有的属性和方法如果 push、pop、slice
  2. console.log(Date.prototype); // Date 的原型:存储 Date 类公有的属性和方法,如 getFullYear、getMonth、getHours
  3. console.log(String.prototype); // String 的原型:存储 String 类的公有属性和方法,如 charAt、charCodeAt、toUpperCase 等
  • 内置类的实例:
  1. var ary = new Array(1, 3, 5, 7);
  2. var date = new Date();
  3. ary.push(9);
  4. date.getFullYear();

思考?
  • 数组实例 ary 怎么找到 push 方法的?
  • Date 实例 date 怎么找到 getFullYear 方法的?

自定义类型:通过自定义构造函数
  • 定义一个构造函数就是创建一个类
  1. function Teacher(name, age, subject, from = '珠峰') {
  2. // 在构造函数中给实例添加私有属性
  3. // 构造函数中的 this 指向当前实例
  4. this.name = name;
  5. this.age = age;
  6. this.subject = subject;
  7. this.from = from;
  8. }
  • 给这个类的原型上添加这个类的公有属性
  1. Teacher.prototype.teach = function () {
  2. console.log(`${this.name} 老师教 ${this.subject} 课程`);
  3. };
  • 创建这个类型的实例:
  1. var t1 = new Teacher('马宾', 18, 'js');
  2. t1.name// 访问 t1 的私有属性 name
  3. t1.age; // 访问 t1 的私有属性 age
  4. t1.teach(); // 访问 t1 的公有方法 teach

如何检测私有属性?
  • hasOwnProperty() 检测一个属性是否是一个对象的私有属性
  1. console.log(t1.hasOwnProperty('name')); // true
  2. console.log(t1.hasOwnProperty('age')); // true
  3. console.log(t1.hasOwnProperty('teach')); // false

原型链:属性的查找机制;
  • 每个对象(实例对象、普通对象、函数对象),自身都有一个 proto** **属性,这个属性指向所属类的原型
  • 当我们 对象.属性名 时,浏览器会现在自己的私有属性找,如果私有属性有这个属性,就使用这个私有属性;如果没有,就根据对象的 proto** 找到所属类的原型(prototype),如果原型上也没有,就通过原型(prototype)对象的 __proto__ **继续向上查找;一直找到基类 Object 的原型对象,如果还没有就返回 undefined;

重写

子类改写父类上的属性或者方法

重载:根据不同的函数签名(函数的参数)自动调用不同的方法;JS 中没有真正意义的重载;

真正意义上的重载:

  1. function sum(int a, int b) {
  2. return a + + b;
  3. }
  4. function sum(char a, char b) {
  5. return Number(a) + Number(b)
  6. }
  7. sum(1, 2)
  8. sum('2', '3')
  • 但是因为 js 中的同名变量会互相覆盖,同名的函数,这个函数名只能代表最后一个函数,所以不会有真实意义上的重载;
  • 但是可以模拟:因为重载要的效果就是根据参数的不同情况作出不同的处理
  1. function sum(a, b) {
  2. if (typeof a === 'number' && typeof b === 'number') {
  3. return a + b;
  4. } else {
  5. return Number(a) + Number(b);
  6. }
  7. }

深入原型、原型链

原型链:对象.属性名 (obj.xxx) 先看私有属性是否有这样是一个属性,如果私有属性中没有,根据实例对象的 proto** 找到当前对象所属类的原型查找,如果原型上也没有,就通过原型对象 __proto__** 继续向上查找; 一直找到 Object.prototype 如果也没有就返回 undefined

  1. function Fn() {
  2. this.name = 'Hi';
  3. }

1. 原型上添加公有属性的方法:

  • 1.1 直接给原型添加方法:
  1. Fn.prototype.say = 'hello';
  2. Fn.prototype.title = 'world';
  3. Fn.prototype.greeting = function () {
  4. console.log('hi~');
  5. };
  • 1.2 通过实例对象的 proto**
    因为实例的 __proto
    ** 指向当前实例所属类的原型对象,所以可以通过修改实例的 proto__ 方式来修改原型对象;
  1. let f1 = new Fn();
  2. f1.__proto__.say = 'hello';
  3. f1.__proto__.hello = 'world';
  4. f1.__proto__.greeting = function () {
  5. console.log('hi~')
  6. };
  • 1.3 修改原型对象的指向:
  1. Fn.prototype = {
  2. say: 'hell',
  3. hello: 'world',
  4. greeting: function () {
  5. console.log('hi~')
  6. }
  7. };
  8. console.log(Fn.constructor); // Object
  9. Fn.prototype.constructor = Fn;
  10. // 当需要批量给原型增加属性或者方法时,我们需要把一个新的对象赋值给类的原型时,此时要给这个对象增加一个 constructor 属性

用 constructor 检测数据类型:

  1. new Fn().constructor === Fn; // true
  2. [].constructor === Array; // true
  • 但是有一个问题,constructor 很容易被修改,导致检测结果不准确
  1. Array.prototype.constructor = 123;
  2. console.log([1, 2, 3].constructor === Array); // false

面向过程和面向对象比较

  • 面向过程
  1. var name = '张三';
  2. var age = 26;
  3. function eat() {
  4. console.log('张三eat')
  5. }
  6. function drink() {
  7. console.log('张三drink')
  8. }
  9. function sleep() {
  10. console.log('张三sleep')
  11. }
  12. eat();
  13. drink();
  14. sleep();
  • 面向对象
  1. function People(name, age) {
  2. this.name = name;
  3. this.age = age;
  4. }
  5. People.prototype.eat = function () {
  6. console.log(this.name + 'eat')
  7. };
  8. People.prototype.drink = function () {
  9. console.log(this.name + 'drink')
  10. };
  11. People.prototype.sleep = function () {
  12. console.log(this.name + 'sleep')
  13. };
  14. // 描述一个主题:
  15. let lisi = new People('李四', 20);
  16. lisi.eat();
  17. lisi.drink();
  18. lisi.sleep();
  19. let wangwu = new People('王五', 35);
  20. wangwu.eat();
  21. wangwu.drink();

这样我们发现当我们以后再描述一个人的时候,我们把这个问题划分到解决描述人的问题,需要 People 这个类来完成。只需要 new 一下 People 这个类就可以得到一个对象,而且这个对象自带了 eat、drink、sleep 方法。

面向对象选项卡

  • HTML代码
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>选项卡</title>
  6. <style>
  7. * {
  8. margin: 0;
  9. padding: 0;
  10. }
  11. ul, li {
  12. list-style: none;
  13. }
  14. .wrapper {
  15. margin: 30px auto;
  16. width: 606px;
  17. height: 400px;
  18. border: 1px solid #000;
  19. }
  20. .header {
  21. border: 1px solid #000;
  22. }
  23. .header li {
  24. float: left;
  25. width: 200px;
  26. height: 40px;
  27. line-height: 40px;
  28. text-align: center;
  29. cursor: pointer;
  30. -webkit-user-select: none; /*禁止用户选中*/
  31. }
  32. .header li:nth-child(2) {
  33. border-left: 1px solid #000;
  34. border-right: 1px solid #000;
  35. }
  36. li.active {
  37. background: yellow;
  38. }
  39. .clearfix:after {
  40. display: block;
  41. content: '';
  42. visibility: hidden;
  43. clear: both;
  44. }
  45. .wrapper div {
  46. text-align: center;
  47. height: 356px;
  48. line-height: 356px;
  49. display: none;
  50. }
  51. div.active {
  52. display: block;
  53. }
  54. </style>
  55. </head>
  56. <body>
  57. <div class="wrapper" id="tab1">
  58. <ul class="header clearfix" id="header">
  59. <li class="active">
  60. 视频
  61. </li>
  62. <li>
  63. 秒拍
  64. </li>
  65. <li>
  66. 综艺
  67. </li>
  68. </ul>
  69. <div class="active">
  70. 程序员惨遭亲妈拍卖
  71. </div>
  72. <div>
  73. 预防脱发秘籍(黑芝麻糊)
  74. </div>
  75. <div>
  76. 蔡徐坤-我们很爷们儿
  77. </div>
  78. </div>
  79. <div class="wrapper" id="tab2">
  80. <ul class="header clearfix">
  81. <li class="active">
  82. 视频2
  83. </li>
  84. <li>
  85. 秒拍2
  86. </li>
  87. <li>
  88. 综艺3
  89. </li>
  90. </ul>
  91. <div class="active">
  92. 程序员惨遭亲妈拍卖2
  93. </div>
  94. <div>
  95. 预防脱发秘籍(黑芝麻糊)2
  96. </div>
  97. <div>
  98. 蔡徐坤-我们很爷们儿2
  99. </div>
  100. </div>
  101. <script src="js/5-选项卡插件.js"></script>
  102. </body>
  103. </html>
  • JS代码
  1. // 1. 创建选项卡类
  2. function Tab(options) {
  3. // 1. 参数合法校验
  4. if (!options || !options.el) {
  5. console.error('缺少el元素');
  6. return;
  7. }
  8. // 2. 将传进来的参数对象保存到实例上
  9. this._options = options;
  10. // 3. 执行获取元素
  11. this.queryEle();
  12. // 4. 执行绑定事件
  13. this.bindEvent();
  14. }
  15. // 为 Tab 增加公用的获取元素的方法
  16. Tab.prototype.queryEle = function () {
  17. // 1. 从 this 中的 options 中的 el 获取最外层元素
  18. const container = document.querySelector(this._options.el);
  19. // 2. 获取选项卡头,并挂载到实例上
  20. this.headerList = container.querySelectorAll('.header > li');
  21. // 3. 获取所有的卡片并挂载到实例上
  22. this.cardList = container.querySelectorAll('div');
  23. };
  24. // 为 Tab 增加公用的绑定事件的元素
  25. Tab.prototype.bindEvent = function () {
  26. const HEADER_LIST = this.headerList; // 用一个常量缓存 headerList
  27. // 变量 headerList 给每个 li 绑定点击事件
  28. for (let i = 0; i < HEADER_LIST.length; i++) {
  29. HEADER_LIST[i].onclick = () => {
  30. // 这里使用箭头函数,是因为我们希望这里的 this 是 Tab 的实例,如果不使用箭头函数,点击事件函数中的 this 就是选项卡头了
  31. this.clearClass();
  32. this.addClass(i);
  33. }
  34. }
  35. };
  36. // 为 Tab 类增加移除类名的方法
  37. Tab.prototype.clearClass = function () {
  38. const HEADER_LIST = this.headerList;
  39. const CARD_LIST = this.cardList;
  40. for (let i = 0; i < HEADER_LIST.length; i++) {
  41. HEADER_LIST[i].className = '';
  42. CARD_LIST[i].className = '';
  43. }
  44. };
  45. // 为 Tab 类添加类名的方法
  46. Tab.prototype.addClass = function (index) {
  47. this.headerList[index].className = 'active';
  48. this.cardList[index].className = 'active';
  49. };
  50. new Tab({
  51. el: '#tab1'
  52. });
  53. new Tab({
  54. el: '#tab2'
  55. });

选项卡插件封装

  • 插件分装需要简化构造函数
  • 确保是由new操作符调用
  1. // 1. 创建选项卡类
  2. function Tab(options) {
  3. // 1. 确保如何是通过 new 操作调用
  4. if (!(this instanceof Tab)) {
  5. console.error('Tab is a constructor which should be call with new');
  6. return;
  7. }
  8. // 2. 参数合法校验
  9. if (!options || !options.el) {
  10. console.error('缺少el元素');
  11. return;
  12. }
  13. // 3. 将传进来的参数对象保存到实例上
  14. this._options = options;
  15. // 4. 执行初始化
  16. this.init();
  17. }
  18. // 为 tab 增加 init 方法:
  19. Tab.prototype.init = function () {
  20. this.queryEle();
  21. this.bindEvent();
  22. };
  23. // 为 Tab 增加公用的获取元素的方法
  24. Tab.prototype.queryEle = function () {
  25. // 1. 从 this 中的 options 中的 el 获取最外层元素
  26. const container = document.querySelector(this._options.el);
  27. // 2. 获取选项卡头,并挂载到实例上
  28. this.headerList = container.querySelectorAll('.header > li');
  29. // 3. 获取所有的卡片并挂载到实例上
  30. this.cardList = container.querySelectorAll('div');
  31. };
  32. // 为Tab增加公用的绑定事件的元素
  33. Tab.prototype.bindEvent = function () {
  34. const HEADER_LIST = this.headerList; // 用一个常量缓存 headerList
  35. // 变量 headerList 给每个 li 绑定点击事件
  36. for (let i = 0; i < HEADER_LIST.length; i++) {
  37. HEADER_LIST[i].onclick = () => {
  38. // 这里使用箭头函数,是因为我们希望这里的 this 是 Tab 的实例,如果不使用箭头函数,点击事件函数中的 this 就是选项卡头了
  39. this.clearClass();
  40. this.addClass(i);
  41. }
  42. }
  43. };
  44. // 为 Tab 类增加移除类名的方法
  45. Tab.prototype.clearClass = function () {
  46. const HEADER_LIST = this.headerList;
  47. const CARD_LIST = this.cardList;
  48. for (let i = 0; i < HEADER_LIST.length; i++) {
  49. HEADER_LIST[i].className = '';
  50. CARD_LIST[i].className = '';
  51. }
  52. };
  53. // 为 Tab 类添加类名的方法
  54. Tab.prototype.addClass = function (index) {
  55. this.headerList[index].className = 'active';
  56. this.cardList[index].className = 'active';
  57. };
  58. new Tab({
  59. el: '#tab1'
  60. });
  61. new Tab({
  62. el: '#tab2'
  63. });