六.JavaScript第二座大山:面向对象(OOP)

一.对象类型数据结构的基本结构和操作

对象是一组属性的无序集合,任何一个对象都是由0到多组键值对(属性名:属性值)组成的,并且属性名不能重复。如下:

1.1对象字面量创建对象

      person = {
        name: "蔡徐坤",
        age: 23,
        weight: "60kg",
        height: "184cm",
        1'100分'
      };
      //1.获取属性,通过对象的属性名获取,属性名是数字或者字符串;
      console.log(person.name);//蔡徐坤
      console.log(person["age"]);//23
      
      //2.若果属性是数字,只能通过person[number]获取;
       console.log(person[1]);
       
      //3.若果对象属性不存在,不会报错,是undefined;
      console.log(person.hobby);//undefined
      
     //4.修改属性值,操作已有的属性名
     person.name='蔡小葵'
     //5.新增属性值,操作没有的属性名
     person.hobby="singing"
      
复制代码

1.2 构造函数创建对象

1.2.1 系统构造函数new Object()
      obj = new Object();
      console.log(obj);   //{}
      obj.name='蔡徐坤'
复制代码
1.2.2 自定义构造函数创建对象
      function Person(uname, age, sex) {
      //若果是执行构造函数,这一句就是给当前创建的实例对象设置私有属性和方法。
        this.name = uname;
        this.age = age;
        this.sex = sex;
      }
      var cxk = new Person("蔡徐坤", "23", "男");
      console.log(cxk.name);
复制代码

1.3 new Object()执行原理

当我们new Object()系统做了什么事情?

1.会在构造函数中自动创建一个空对象,这个对象就是当前类的实例;
2.把构造函数中的this赋值给空对象
3.设置原型链,空对象指向构造函数的原型对象
4.执行构造函数内部的代码(给空对象添加属性)
5.会在构造函数的最后自动添加return this; 返回原始值忽略,返回对象则正常处理

二.面向对象基础(面向对象和构造函数)

2.1 什么是面向对象?

  • 自然界的万事万物都可以看作是对象;
  • 根据这些对象的特点又将其划分为不同的 => “类”是对“对象”的一种细分,以及对公共部分的封装。
  • 类别中派生出来的具体的事务叫做类的“实例”。实例既有属于自己私有的东西,也有继承各个类别中的共有信息。
  • 面向对象编程,其实就是掌握“对象”、“类”、“实例”之间的关系、知识。例如:类的封装、继承、多态。

2.2 JS中的内置类

  1. JS中每一种数据类型都有自己对应的类别:

src=http___www.myexception.cn_img_2012_08_30_100900322.jpg&refer=http___www.myexception.jpg

  1. 每一个dom元素对象也有自己的类和祖先类(dir()查找)
  • div元素对象–>HTMLDivElement–>HTMLElement–>Element–>Node–>EventTarget–>Object
  • 元素集合–>HTMLCollection–>Object
  • 节点集合–>NodeList–>Object

2.3 js自定义类(构造函数)

  • 构造函数特点: 1.函数体内使用this关键字,代表了所要生成的对象实例。 2.生成对象,必须使用new 关键字实例化。
  • 不管是自定义类还是内置类,都是函数数据类型的;typeof Array是"function"
  • js是基于构造函数(constructor)和原型链(prototype)。
      function Person(myName, myAge) {
        this.name = myName;
        this.age = myAge;
      }
      Person.prototype = {
        say: function () {
          console.log("hello world");
        },
      };
      let obj1 = new Person("蔡徐坤", 23);
      obj1.say();
      let obj2 = new Person("毛不易", 29);
      obj2.say();
      console.log(obj1.say === obj2.say); // true
复制代码
  • 基于instanceof 可以检测当前对象是否属于某个类的实例,返回布尔值:实例 instanceof 类
  • 每一一个对象都有很多属性和方法,在自己堆内存中存储的都是自己的私有属性和方法,基于__proto__原型链查找类prototype原型上的都是共有的属性和方法。
  • 通过对象.hasOwnProperty(属性)检测是否为私有属性,是则返回true。
  • 判断某一个对象类中或原型中是否拥有某一个属性可以用“in”,只要类中或者原型对象中有, 就会返回true
  • isPrototypeOf用于判断 一个对象是否是另一个对象的原型

三.原型和原型链

  • 函数数据类型:普通函数、类
  • 对象数据类型:普通对象、数组对象、正则对象、日期对象、实例对象、函数对象、类的prototype

3.1 原型prototype(原型属性)

  • 1 . 每一个函数数据类型(只包括构造函数、普通函数)都具备prototype(原型属性),并且属性值是一个对象;
  • 2 . 在prototype对象中会存储当前类的公共属性和方法;
  • 3 . 在prototype的堆内存中,若果是浏览器默认为其开辟的堆内存,会存在一个内置的属性constructor构造函数,属性值就是当前类本身。即:
function Fn() {}
 f = new Fn();
 console.log(Fn === Fn.prototype.constructor);//true
复制代码

3.2 原型链__proto__

  • 每一个对象(普通对象、prototype、实例、函数等)都具备内置属性:__proto__(原型链属性),属性值是当前实例所属类的原型(即prototype这个堆)

  • (对象基类)Object.prototype也是一个对象,他也有__proto__,他是指向基类属性值为null。

//实例对象的原型链`__proto__`指向构造函数的Fn.prototype原型对象
console.log(f.__proto__ === Fn.prototype);//true
//构造函数的原型对象的`__proto__`指向Object内置类的原型对象,即可看成所有构造函数都是Object的实例对象
console.log(Fn.prototype.__proto__ === Object.prototype);//true
console.log(Array.prototype.__proto__ === Object.prototype);//true
console.log(Function.prototype.__proto__ === Object.prototype);//true
console.log(String.prototype.__proto__ === Object.prototype); //true
复制代码

3.3 原型链查找机制

它是一种基于__proto__向上查找的机制,当我们操作实例的某个属性或者方法的时候,首先找到自己空间中私有的属性或者方法:
1.找到了,则结束查找,使用自己的私有即可;
2.没有找到 则基于__proto__ 找所属类的prototype上的公有属性和方法,如果找到了就用这个公有的;
3.如果没找到,基于原型上的__proto__继续向上查找,一直找到Object:prototype的原型为止,如果在没有,操作的属性或者方法不存在。

标题.png

例题分析:
1.png

3.4 内置原型上扩展方法

1.内置类原型上默认会存有很多方法:Array.prototype、Object.prototype、String.prototype…这些方法实例都可以调用,但是我们也需要向原型上扩展方法,满足开发需求。

2.向原型上扩展方法,调用起来比较方便:

实例.方法();
方法执行的时候,方法中的this指向当前实例;
注意
1.名字不要重复
2.this是对象数据类型,但是会隐式调用valueOf方法,返回他的原始值
3.如果返回的结果依旧是当前类的实例,那么可以继续调用当前类的其他方法。链式调用

  1. 对于一个对象来说,他的属性和方法存在可枚举的特点:即在for…in…循环时是否可以遍历到,内置原型上自己扩展的方法是可枚举的。
(function () {
        console.log("函数已执行");
        function handleNum(num) {
          num = Number(num);
          return isNaN(num) ? 0 : num;
        }
        Number.prototype.plus = function plus(num) {
          handleNum(num);
          return this + num;
        };
        Number.prototype.minus = function minus(num) {
          handleNum(num);
          return this - num;
        };
      })();

      let n = 10;
      let m = n.plus(10).minus(5);
      console.log(m); //15,  只有n一直为数子才可以一直调用
复制代码
 let obj = {
        name: "蔡徐坤",
        age: 22,
      };
      Object.prototype.fun = function () {
        console.log("我是蔡徐坤");
      };
      for (let key in obj) {
        //在遍历对象上的属性和方法时,也会遍历原型上添置的属性和方法
        console.log(key); // name age fun
        //过滤掉原型上的属性和方法
        if (obj.hasOwnProperty(key)) {
          console.log(key); //name age
        }
      }
复制代码

3.5 重写内置new和Object.create方法

3.5.1 重写内置new

题1:编写_new实现内置的new Xxx()功能

 //构造函数
      function Dog(name) {
        this.name = name;
      }
//添加原型方法
      Dog.prototype.bark = function () {
        console.log("wangwang");
      };
      
/*
需求: 通过 _new Fn()执行构造函数创造一个实例对象
               @params
                      Func: 要创建的实例对象的所属类(构造函数)
                   ...args: 传给Func函数的实参(ES6中的剩余运算符)
*/
      function _new(Func, ...args) {
           console.log("进入new函数");
       //1.创建一个空对象,这个空对象是当前类的一个实例(对象.__proto__===类.prototype)
          /* 
            let obj = {};
            obj.__proto__ = Func.prototype;这个方法ie不兼容__proto__
          */
        let obj = Object.create(Func.prototype); 

        //2.把类当作普通函数执行(this指向的是实例对象)
        let result = Func.call(obj, ...args);
           /*
            ...args为ES6中的展开运算符,将args数组展开成每一项。
            call方法将Func函数中的this强行修改为第一个参数obj。
           */
        //3.返回值问题
           /*
            函数没有返回值或返回值为基本类型(没啥用),仍然返回创建的实例;
            返回值为引用类型值,它将把创建的实例覆盖掉并且返回自己写的这个结果。
           */
        if (
             (result !== null && typeof result === "object") ||
             typeof result === "function"
        ) {
          return result;
        }
        return obj;
      }

      let sanmao = _new(Dog, "三毛");

复制代码
            //   function _new(Func,...args){  }等价于
               function _new(Func){
                 let args=[];
                 for(let i=1;i<arguments.length;i++){
                   args.push(arguments[i])
                 }
                }
复制代码
3.5.2 Object.create()方法

Object.create(xxx)创建一个空对象,会把当前的xxx作为这个对象的原型链指向===相当于创建某个类的空实例

Object.create = function create(prototype){
	function Fn(){};
	Fn.prototype = prototype;    // 重定向函数Fn的原型
	return new Fn;
}
复制代码

Object.create原始方法

 var obj = Object.create(null);
      console.log("打印111", obj); 
      //创建一个空实例并让它的原型链__proto__指向null,里面没有__proto__了,原型链断了

      obj.__proto__ = Array.prototype;
      console.log(obj);
      console.log(obj.slice); // => undefined
      console.log(obj.__proto__.slice); // => 找得到
      
/*
  按理说应该能调用数组的方法
  但是,var obj = Object.create(null)之后,obj咋啥也没有了
  上面这个神操作将它原本的原型链机制破坏掉了,而接下来obj.__proto__ = Array.prototype时只是将__proto__当作一个私有属性啦,
  所以根本没法向上查找,当然找不到原本Array.prototype上的方法咯。
 */
复制代码

面试题:Array.push方法

Array.prototype.push = function push(val) {
  //this当前操作push的实例
        this[this.length-1+1] = val; // 向数组末尾添加元素,在最大原始索引(length-1)上+1
        this.length++; // 数组长度增加
        return this.length; // 返回数组长度
      };
复制代码
      let obj = {
        2: 3,
        3: 4,
        length: 2,
        push: Array.prototype.push,
      };
      obj.push(1);
      /*
      this[this.length] = val;  //obj[2]=1
      this.length++;       //length=3
      */
      obj.push(22);
      /*
      this[this.length] = val;  //obj[3]=22
      this.length++;       //length=4
      */
      console.log(obj);
      /*
       {
        2: 1,
        3: 22,
        length: 4,
        push: Array.prototype.push,
      };
      */
复制代码

面试题:a=?能打印ok
1.a是对象

      a = {
        i: 0,
        //给当前对象重写私有方法valueOf,这样就不会在原型上找内置方法
        valueOf() {
          return ++this.i;
        },
      };
      if (a == 1 && a == 2 && a == 3) {
        console.log("ok");
      }
复制代码

2.a是数组

  let a = [1, 2, 3];
      a.toString = a.shift;
      //每次判断都会调用toString方法,让他重定向为shift,每次判断会删除第一项,并返回删除项
   if (a == 1 && a == 2 && a == 3) {
        console.log("ok");
      }
复制代码

2.利用数据劫持

      //数据劫持(劫持对象中的某个属性),在每次获取a值的时候,我们把它劫持到,返回我们需要的值
      Object.defineProperty(window, "a", {
        get() {
          //在每一次获取window.a的时候触发执行;
          //返回啥值,本次获取的值就是啥
        },
        set(val) {
          //在每一次给window.a赋值的时候执行
        },
      });
复制代码
      var i = 0;
      Object.defineProperty(window, "a", {
        get() {
          return ++i;
        },
      });
      //每次判断都会调用toString方法,让他重定向为shift,每次判断会删除第一项,并返回删除项
      if (a == 1 && a == 2 && a == 3) {
        console.log("ok");
      }
复制代码

3.6 原型对象重写

浏览器默认为构造函数创建一个原型对象,我们可以通过Fn.prototype={....}改写原型,但是一个完整的原型应该包含constructor函数,因此,改写原型的时候要创建一个函数 Fn.constructor=Fn

let obj={...}
obj=Object.assign(Fn.prototype,obj)//重定向之前把原始原型和新对象合并一下
Fn.prototype=obj
复制代码

面试题:

function Fn(n, m) {
        n = n || 0;
        m = m || 0;
        this.x = n;
        this.y = m;
        this.getX = function () {
          console.log(this.x);
        };
        return n + m;
      }
      Fn.prototype.sum = function () {
        console.log(this.x + this.y);
      };
      Fn.prototype = {
        getX: function () {
          this.x += 1;
          console.log(this.x);
        },
        getY: function () {
          this.y -= 1;
          console.log(this.y);
        },
      };
      let f1 = new Fn(10, 20);
      let f2 = new Fn();
      console.log(f1.getX === f2.getX);
      console.log(f1.getY === f2.getY);
      console.log(f1.__proto__.getY === Fn.prototype.getY);
      console.log(Fn.prototype.getX === f2.getX);
      console.log(f1.constructor);
      f1.getX();
      Fn.prototype.getX();
      f2.getY();
      Fn.prototype.getY();
      f1.sum();
/*
false true true false
ƒ Object() { [native code] }
10 NaN -1 NaN
/*
复制代码

无标题www.png

3.7 原型和原型链的一些题目

    1. 写出下面代码执行输出的结果
     function C1(name) {
        if (name) {
          this.name = name;
        }
      }
      function C2(name) {
        this.name = name;
      }
      function C3(name) {
        this.name = name || "join";
      }
      C1.prototype.name = "Tom";
      C2.prototype.name = "Tom";
      C3.prototype.name = "Tom";
      console.log(new C1().name + new C2().name + new C3().name); //Tomundefinedjoin
      /*
      new C1().name==>//没有传值,不会执行if里面的,所以没有私有属性name,去原型找name,输出tom;
      new C2().name==>//没有传值,私有属性name=undefined,输出undefined;
      new C3().name==>//没有传值,私有属性name=undefined||join,输出join;
      */
复制代码
  1. 写出下面代码执行输出的结果
      function Foo() {
        getName = function () {
          console.log(1);
        };
        return this;
      }
      Foo.getName = function () {
        console.log(2);
      };
      Foo.prototype.getName = function () {
        console.log(3);
      };
      var getName = function () {
        console.log(4);
      };
      function getName() {
        console.log(5);
      }
      Foo.getName();
      getName();
      Foo().getName();
      getName();
      new Foo.getName();
      new Foo().getName();
      new new Foo().getName();
复制代码

无23aaa题.png

6.画图计算下面的结果

      function fun() {
        this.a = 0;
        this.b = function () {
          alert(this.a);
        };
      }
      fun.prototype = {
        b: function () {
          this.a = 20;
          alert(this.a);
        },
        c: function () {
          this.a = 30;
          alert(this.a);
        },
      };
      var my_fun = new fun();
      my_fun.b();
      my_fun.c();
复制代码

111111111111111111111111111ssss标题.png

3.8 JavaScript-对象三角恋关系

  • 1.每个”构造函数”中都有一个默认的属性, 叫做prototype,prototype属性保存着一个对象, 这个对象我们称之为”原型对象“;
  • 2.每个”原型对象“中都有一个默认的属性, 叫做constructor,constructor指向当前原型对象对应的那个”构造函数“;
  • 3.通过构造函数创建出来的对象我们称之为”实例对象“,每个”实例对象“中都有一个默认的属性, 叫做__proto__, __proto__指向创建它的那个构造函数的”原型对象“;

三者关系001.webp

四.Function和Object内置类的相爱相杀

  • 我们先研究一下数组arr = [10,20,30];
  • 数组也是对象,所以它内部除了”项”和length以外,还有原型链__proto__,指向的是Array内置数组类的原型prototype.

172467f0444a78d7 .png

  • 数组类是一个函数,他是Function基类的一个实例对象,作为一个对象,也有__proto__原型链,那Array作为一个类也是一个function,所以有prototype原型对象属性,所以Array的__proto__指向的是Function.prototype.
  • 同样,函数类也有原型和原型链,那函数Function的prototype自然也指向Function.prototype原型对象,而函数Function也属于Function自己的实例,所以函数Function的__proto__也指向Function.prototype.(注意:Function.prototype是一个函数)。

666.png

接下来该对象基类出现了:

Object作为一个函数类,可以看成是Function的实例对象,它的原型链__proto__自然是指向所属类的构造函数的原型Function.prototype,而作为构造函数,它的原型prototype肯定也是指向的自己的prototype对象。

4日日日日.png

那此时就出现了一个奇怪的现象,Array的prototype的__proto__毫无疑问指向的是Object.Prototype,可Function.prototype的__proto__也指向的是对象类Object.prototype,而Object本身作为一个函数类它的__proto__原型链指向的是Function.prototype.

每个函数都是Function的实例
所以Function自己也是自己的实例
Function instanceof Function //true

函数类的原型链指向函数类自己的原型
Function.__proto__ === Function.prototype //true

Object作为一个类(一个函数),它是Function的一个实例;
Function虽然是一个函数类,但它也是一个对象,所以他也是Object的一个实例;
Object.__proto__.__proto__ === Object.prototype //true
复制代码

在js中的任何实例(任何值【除了值类型的值】)最后都可以基于自己的__proto__找到Object.prototype,也就是所有的值都是Object的实例=>万物皆对象。

总结一下(便于理解):

关于Function:

1.JavaScript中函数是引用类型(对象类型), 既然是对象,所以也是通过构造函数创建出来的,”所有函数”都是通过Function构造函数创建出来的对象.

2.JavaScript中只要是”函数”就有prototype属性,”Function函数”的prototype属性指向”Function原型对象”,很奇怪它是一个函数。

3.JavaScript中只要”原型对象”就有constructor属性,指向所属类;”Function原型对象”的constructor指向它对应的构造函数。

4.Person构造函数是Function构造函数的实例对象, 所以也有__proto__属性,Person构造函数的__proto__属性指向”Function原型对象”

关于Object:

  1. JavaScript中还有一个系统提供的构造函数叫做Object,只要是函数都是”Function构造函数”的实例对象,所以Object作为基类也是Function的实例对象。

2.只要是对象就有__proto__属性, 所以”Object构造函数”也有__proto__属性, “Object构造函数”的__proto__属性指向创建它那个构造函数的”原型对象”

3.只要是构造函数都有一个默认的属性, 叫做prototype, prototype属性保存着一个对象, 这个对象我们称之为”原型对象”

4.只要是原型对象都有一个默认的属性, 叫做constructor,constructor指向当前原型对象对应的那个”构造函数”


function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
        }
let obj1 = new Person("蔡徐坤", 22);
console.log(Function);// ƒ Function() { [native code] }不报错,系统提供了一个构造函数
console.log(Function.prototype);// ƒ () { [native code] }
console.log(Function.prototype.constructor);//ƒ Function() { [native code] }
console.log(Function === Function.prototype.constructor); // true
console.log(Person.__proto__);//ƒ Function() { [native code] }
console.log(Person.__proto__ === Function.prototype); // true
console.log(Function.__proto__);//ƒ Function() { [native code] }
console.log(Function.__proto__ === Function.prototype); // true

 console.log(Object);
 console.log(Object.__proto__);
 console.log(Object.__proto__ === Function.prototype); // true
 console.log(Object.prototype);
 console.log(Object.prototype.constructor);
 console.log(Object.prototype.constructor === Object); // true
 console.log(Object.prototype.__proto__); // null
复制代码

函数对象关系:

1.Function函数是所有函数的祖先函数,所有函数都是Function构造函数的实例对象

2.所有的构造函数都有一个prototype属性, 所有prototype属性都指向自己的原型对象

3.所有的原型对象都有一个constructor属性, 所有constructor属性都指向自己的构造函数

4.所有函数都是对象, 包括Function构造函数

5.所有对象都有__proto__属性

6.普通对象的__proto__属性指向创建它的那个构造函数对应的”原型对象”

7.所有对象的__proto__属性最终都会指向”Object原型对象”

8.”Object原型对象”的__proto__属性指向NULL

      function Person(myName, myAge) {
        this.name = myName;
        this.age = myAge;
      }
      let obj1 = new Person("蔡徐坤", 22);

      console.log(Function.prototype.__proto__); //Object
      console.log(Person.prototype.__proto__); //Object
      console.log(Function.prototype.__proto__ === Person.prototype.__proto__); //true
      console.log(Function.prototype.__proto__ === Object.prototype); //true
      console.log(Person.prototype.__proto__ === Object.prototype); //true
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享