【前端 javascript 高级】 02 - 构造函数和原型+继承+ES5新增方法

1. 构造函数和原型

1.1 对象的三种创建方式--复习

  1. 对象字面量的方式 :
let obj = {
    name: '张三',
    age: 23,
    sayHi: function () {
      console.log('hi!');
    }
  }

  console.log(obj.name);
  obj.sayHi();
  1. 使用 new 关键字的方式创建对象 :
let object = new Object();
  object.name = '李四';
  object.age = 24;
  object.sayHello = function () {
    console.log('say hello');
  }

  object.sayHello();
  1. 使用构造函数创建对象:
function Star(name, age) {
    this.name = name;
    this.age = age;
    this.print = function () {
      console.log(name + age);
    }
  }

  let star = new Star('王五', 35);
  star.print();

1.2 静态成员和实例成员

实例成员
  1. 实例成员就是构造函数内部通过 this 添加的成员 如下列代码中 uname age sing 就是实例成员,实例成员只能通过实例化的对象来访问。
 // 实例成员就是构造函数内部通过 this 添加的成员
  function Star(name, age) {

    this.name = name;
    this.age = age;

    this.sayHello = function () {
      console.log('say hello');
    };
  }

  // 实例成员只能通过实例化的对象来访问
  let star = new Star('张三', 23);
  console.log(star.name);
  console.log(star.age);
  // 不可以通过构造函数来访问实例成员
  console.log(Star.age); // undefined

  // 静态成员是在构造函数本身上添加的成员,静态成员只能通过构造函数访问不能通过实例对象访问
  Star.sex = '男';

  console.log(star.sex); // undefined 实例对象调用静态成员 

  console.log(Star.sex); // 男  构造函数调用静态成员
静态成员
  1. 静态成员 在构造函数本身上添加的成员 如下列代码中 sex 就是静态成员,静态成员只能通过构造函数来访问。
function Star(uname, age) {
     this.uname = uname;
     this.age = age;
     this.sing = function() {
     console.log('我会唱歌');
    }
}
Star.sex = '男';
var ldh = new Star('刘德华', 18);
console.log(Star.sex);//静态成员只能通过构造函数来访问

1.3 构造函数的问题

  1. 构造函数方法很好用,但是存在浪费内存的问题。
公共的方法会各自占用一块内存

1.4 构造函数原型 prototype

  1. 构造函数通过原型分配的函数是所有对象所共享的。

  2. JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。注意这个 prototype 就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。

  3. 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。

  /**
   * 构造函数
   * */
  function Star(name, age) {
    this.name = name;
    this.age = age;
    /**
     * 还是会优先调用它
     *
     */
    /*this.sing = function () {
      console.log('我会唱歌吗?');
    };*/
  }

  console.dir(Star);

  // 在构造函数的原型对象里面 添加共享的方法
  // 原型是一个对象是每个构造函数中都会存在的一个对象
  // 原型的作用是: 共享方法
  // 一般情况下,我们的公共属性定义在构造函数中,但是公共的方法我们需要放到原型对象身上
  Star.prototype.sing = function () {
    console.log('我会唱歌');
  };

  let star = new Star('黎明', 23);
  let s = new Star('张华自', 23);
  console.log(star.sing() === s.sing()); // true

1.5 对象原型

对象实例都会有一个属性 __proto__(对象原型)指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有__proto__原型的存在。
__proto__对象原型和原型对象prototype 是等价的 __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype

原型对象和对象原型的关系

  function Star(name, age) {

    this.name = name;
    this.age = age;

  }
  /**
   * 在原型对象上添加一个共享方法
   */
  Star.prototype.sing = function () {
    console.log('我会唱歌');
  }

  let ldh = new Star('刘德华', 66);
  // 对比原型对象和对象原型
  console.log(ldh.__proto__ === Star.prototype); // true

1.6 constructor构造函数

对象原型( __proto__)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。
constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。

如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数如:

 function Star(uname, age) {
     this.uname = uname;
     this.age = age;
 }
 // 很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数
 Star.prototype = {
 // 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
   constructor: Star, // 手动设置指回原来的构造函数
   sing: function() {
     console.log('我会唱歌');
   },
   movie: function() {
     console.log('我会演电影');
   }
}
var zxy = new Star('张学友', 19);
console.log(zxy)

以上代码运行结果,设置constructor属性如图:

手动设置constructor属性指回原来的构造函数

如果未设置constructor属性,如图:

如果未设置constructor属性

1.7 原型链

  1. 每一个实例对象又有一个proto 属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有proto属性,这样一层一层往上找就形成了原型链。
原型链
 function Star(name, age) {
    this.name = name;
    this.age = age;
  }

  console.log(Star.prototype); // 构造函数中的原型对象 原型对象中有对象原型 __proto__ 指向 Object
  /**
   * Star.prototype.__proto__ 构造函数中原型对象 的对象原型 指向 Object 的原型对象
   * 我们Star原型对象里面
   */
  console.log(Star.prototype.__proto__ === Object.prototype);
  console.log(Object.prototype.__proto__); // null  Object的 原型对象的对象原型指向的是 null
  console.dir(Object);

1.8 构造函数实例和原型对象三角关系

1.构造函数的prototype属性指向了构造函数原型对象;

2.实例对象是由构造函数创建的,实例对象的__proto__属性指向了构造函数的原型对象;

3.构造函数的原型对象的constructor属性指向了构造函数,实例对象的原型的constructor属性也指向了构造函数。

构造函数实例和原型对象三角关系
  function Star(name, age) {

    this.name = name;
    this.age = age;
  }

  let star = new Star('张三', 23);

  // 1. 构造函数中的 prototype 指向了原型对象
  console.log(Star.prototype);

  // 2. 原型对象中constructor指向了构造函数
  console.log(Star.prototype.constructor);

  // 3. 构造函数指向对象实例 ,对象实例中的 __proto__(对象原型) 指向了原型对象
  console.log(star.__proto__);
  console.log(star.__proto__.constructor);

1.9 原型链和成员的查找机制

  1. 任何对象都有原型对象,也就是prototype 属性,任何原型对象也是一个对象,该对象就有proto 属性,这样一层一层往上找,就形成了一条链,我们称此为原型链;
  • 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
  • 如果没有就查找它的原型(也就是__proto__指向的 prototype原型对象)。
  • 如果还没有就查找原型对象的原型(Object的原型对象)。
  • 依此类推一直找到 Object 为止(null)。
  • __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
 /**
   * @param name
   * @param age
   * @constructor
   */
  function Star(name, age) {
    this.name = name;
    this.age = age;
  }

  Star.prototype.sing = function () {
    console.log('我会唱噶');
  }

  Star.height = 178;
  console.log(Star.height);
  let star = new Star('张三', 23);

  // star.sex = '男'; // 1.先在实例对象上查找
  // Star.prototype.sex = '男'; // 2. 再在原型对象上找
  Object.prototype.sex = '女'; // 3. 最后在Object的原型对象上找
  console.log(star.sex);
  star.sing();
  star.sex = '男';
  console.log(star.sex); // 男
  // 对象成员查找是根据原型链的机制进行查找
  console.log(Object.prototype); // 这里面有个toString方法
  console.log(Star.prototype);
  console.log(star.toString());

1.10 原型对象中this指向

  1. 构造函数中的this和原型对象的this,都指向我们new出来的实例对象:
 let that;

  /**
   * 原型对象的 this 指向问题
   * @param name
   * @param age
   * @constructor
   */
  function Star(name, age) {

    this.name = name;
    this.age = age;
    console.log("创建实例对象" + that === this);
  }


  Star.prototype.sing = function () {
    // 这里的this指向的是谁? 指向的是函数的调用者 当前的实例对象
    console.log('我会唱歌');
    console.log(this);
    console.log("创建实例对象" + that === this);
    that = this;
  }

  let star = new Star('张三', 23);
  console.log(star);
  star.sing();
  // 1. 在构造函数中,里面this指向的是对象实例

  // 2. 原型对象函数里面的this指向的是函数的调用者,但
  // 是不管是构造函数中的this还是原型对象中的this都是指向的实例对象

1.11 通过原型为数组扩展内置方法

console.log(Array.prototype); // 打印Array构造函数中的原型对象

  /**
   * 扩展求和函数
   */
  Array.prototype.sum = function () {
    let sum = 0;
    for (let i = 0; i < this.length; i++) {
      sum += this[i];
    }
    return sum;
  };

  let arr = [100, 200, 300, 400, 500];
  console.log(arr.sum());

  /**
   * 错误的追加方式
   */
  Array.prototype = function () {
    let sum = 0;
    for (let i = 0; i < this.length; i++) {
      sum += this[i];
    }
    return sum;
  };

  // 注意:在数组和字符串内置对象中不能给原型对象覆盖操作 Array.prototype = {} 只能是采取 Array.prototype.函数或者属性名 = function(){} 的方式

2. 继承

2.1 call()

  1. call() 可以调用函数;

  2. call() 可以修改 this 的指向,使用call() 的时候 参数一是修改后的 this 指向,参数2,参数3..使用逗号隔开连接。

  // 1.call() 可以调用函数
  function fn(x, y) {
    console.log('call()可以调用函数和改变函数的this指向');
    console.log(this);
    console.log(x + y);
  }

  // fn.call();
  let o = {
    name: 'andy'
  }
  // 2.call() 可以改变函数的this的指向
  fn.call(o, 1, 2);

2.2 子构造函数继承父构造函数中的属性

  1. 先定义一个父构造函数;

  2. 再定义一个子构造函数;

  3. 子构造函数继承父构造函数的属性(使用call方法)。

 /**
   * 1. 父构造函数
   * @param name
   * @param age
   * @constructor
   */
  function Father(name, age) {
    this.name = name;
    this.age = age;
  }

  /**
   * 2. 子构造函数
   * @param name
   * @param age
   * @constructor
   */
  function Son(name, age) {
    /**
     * 这里的意思是将子构造函数的this、指向了父构造函数的this
     */
    Father.call(this, name, age);

  }

  let son = new Son('李四', 24);
  console.log(son);

2.3 借用原型对象继承方法

  1. 先定义一个父构造函数;

  2. 再定义一个子构造函数;

  3. 子构造函数继承父构造函数的方法(使用 call 方法)。

  /**
   * 1. 父构造函数
   * @param name
   * @param age
   * @constructor
   */
  function Father(name, age) {
    this.name = name;
    this.age = age;
  }

  /**
   *
   * 在父类的原型对象中添加一个共享方法
   * */
  Father.prototype.money = function () {
    console.log('100000');
  }

  /**
   * 2. 子构造函数
   * @param name
   * @param age
   * @constructor
   */
  function Son(name, age) {
    /**
     * 这里的意思是将子构造函数的this、指向了父构造函数的this
     */
    Father.call(this, name, age);
  }

  // 如果使用Son的原型对象 直接指向 Father的原型对象将会出现问题
  // Son.prototype = Father.prototype;
  // 让Son的原型对象指向 Father 的实例对象
  Son.prototype = new Father();

  /**
   * 此时再Son构造函数中添加一个方法
   * @type {Son}
   */
  Son.prototype.exam = function () {
    console.log('我要考试');
  }

  let son = new Son('李四', 24);
  console.log(son);

  let father = new Father();
  console.log(father);

如上代码结果如图:

如上代码结果

3. ES5新增方法

3.1 数组方法forEach遍历数组

 arr.forEach(function(value, index, array) {
       //参数一是:数组元素
       //参数二是:数组元素的索引
       //参数三是:当前的数组
 })
  //相当于数组遍历的 for循环 没有返回值
演示案例 :
  /**
   * 数组方法 foreach,遍历数组的值,并求所有值的和
   */
  let arr = [1, 2, 3, 4, 5, 6, 6, 7, 8, 90];
  let sum = 0;
  arr.forEach((value, index, array) => {
    console.log('每一個数组元素' + value);
    console.log('每一个数组元素的索引' + index);
    console.log('数组本身' + array);
    sum += value;
  });

  console.log(sum);

3.2 数组方法filter过滤数组

var arr = [12, 66, 4, 88, 3, 7];
  var newArr = arr.filter(function(value, index,array) {
     //参数一是:数组元素
     //参数二是:数组元素的索引
     //参数三是:当前的数组
     return value >= 20;
  });
  console.log(newArr);//[66,88] //返回值是一个新数组

3.3 数组方法some

some 查找数组中是否有满足条件的元素 
 var arr = [10, 30, 4];
 var flag = arr.some(function(value,index,array) {
    //参数一是:数组元素
     //参数二是:数组元素的索引
     //参数三是:当前的数组
     return value < 3;
  });
console.log(flag);//false返回值是布尔值,只要查找到满足条件的一个元素就立马终止循环

3.4 案例 : 筛选商品案例

ES5数组新增方法实现商品查询
页面结构

id 产品名称 价格
页面样式
table {
      width: 400px;
      border: 1px solid #000;
      border-collapse: collapse;
      margin: 0 auto;
    }

    td,
    th {
      border: 1px solid #000;
      text-align: center;
    }

    input {
      width: 50px;
    }

    .search {
      width: 600px;
      margin: 20px auto;
    }
自定义数据
 // 利用新增数组方法操作数据
  let data = [{
    id: 1,
    pname: '小米',
    price: 3999
  }, {
    id: 2,
    pname: 'oppo',
    price: 999
  }, {
    id: 3,
    pname: '荣耀',
    price: 1299
  }, {
    id: 4,
    pname: '华为',
    price: 1999
  }, {
    id: 5,
    pname: '华为p40',
    price: 6999
  }, {
    id: 6,
    pname: '华为',
    price: 1999
  }];
操作逻辑

// 1. 获取相应的元素
  let tbody = document.querySelector('tbody');
  setData(data);

  function setData(myData) {

    // 先清空原来的数据
    tbody.innerHTML = '';

    // 2. 将数据渲染到页面中
    myData.forEach((value) => {
      // 创建行元素
      let tr = document.createElement('tr');
      tr.innerHTML = '' + value.id + '' + value.pname + '' + value.price + '';
      tbody.appendChild(tr);
    });
  }


  // 3. 根据价格查询商品
  // 搜索按钮
  let search_price = document.querySelector('.search-price');
  // 开始输入框
  let start = document.querySelector('.start');
  // 结束输入框
  let end = document.querySelector('.end');
  search_price.addEventListener('click', () => {
    let newData = data.filter((value) => {
      return value.price >= start.value && value.price <= end.value;
    });
    setData(newData);
  });

  // 4. 根据商品名称查找商品

  // 如果查询的是数组中唯一的元素,用some方法更加合适,因为它找到这个元素就不再进行循环效率更高

  let product = document.querySelector('.product');
  let search_pro = document.querySelector('.search-pro');
  search_pro.addEventListener('click', () => {
    let arr = [];
    data.some((value) => {
      /**
       * 找到之后就会终止循环
       */
      if (value.pname === product.value) {
        console.log(value);// 可以实现打印
        arr.push(value);
        return true; //
      }
    })

    // 将拿到的数据渲染到页面中
    setData(arr);
  });

3.5 some和forEach区别

  1. 如果查询数组中唯一的元素, 用some方法更合适,在some 里面 遇到return true 就是终止遍历 迭代效率更高

  2. forEach里面 return不会终止迭代。

let arr = ['red', 'green', 'blue', 'pink'];
  // 在forEach中return不会终止循环

  /**
   * value 满足条件的值
   *
   * index 满足条件的索引
   *
   * array 满足条件的新数组
   */
  arr.forEach((value, index, array) => {
    if (value === 'green') {
      console.log(value);
      console.log('元素已经找到');
      return true;
    }
    console.log(666);
  });

  // some的效率会更高因为在第一次找到元素之后就会终止循环
  arr.some((value, index, array) => {
    if (value === 'green') {
      console.log(value);
      console.log('元素已经找到');
      return true;
    }
    console.log(666);
  });

3.6 trim方法去除字符串两端的空格

  1. trim() 方法清除的是字符串两端的空格,不清楚中间的空格。



  /**
   * trim 去除字符串两端的空白字符
   */
  let input = document.querySelector('input');
  let btn = document.querySelector('button');
  let div = document.querySelector('div');

  btn.addEventListener('click', () => {
    let strInput = input.value.trim();
    // 首先判断一下文本框的内容是否为空
    if (strInput === '') {
      alert('输入框不能为空!');
    } else {
      // 将文本框的内容设置给 div
      div.innerHTML = strInput;
      console.log(strInput);
    }
  });

3.7 Object.keys() 获取对象的属性名

  1. Object.keys(对象) 获取到当前对象中的属性名 ,返回值是一个数组:
 var obj = {
     id: 1,
     pname: '小米',
     price: 1999,
     num: 2000
};
var result = Object.keys(obj)
console.log(result)//[id,pname,price,num]

3.8 Object.defineProperty

/**
   *
   * @type {{id: number, pname: string, price: number}}
   *
   * Object.defineProperty(obj , prop,descriptor)
   *
   * 1. obj 对象
   *
   * 2. prop 需要设置的属性
   *
   * 3. 一个属性配置对象
   *
   *  3.1 value: 设置属性的值,默认值是undefined
   *
   *  3.2 writable: 是否可以被重写
   *
   *  3.3 enumerable:目标属性是否可以被枚举。 true | false 默认false
   *
   *  3.4 configurable:目标属性是否可以被删除或是否可以在此修改 true | false 默认是false
   */
  let obj = {
    id: 1,
    pname: '小米',
    price: 36669
  };

  Object.defineProperty(obj, 'num', {
    value: 30000,
    writable: false, // 是否可以被重写
    enumerable: false // 默认值是false
  });

  Object.defineProperty(obj, 'pname', {
    writable: false,// 是否可以被重写
    configurable: false // 是否可以被删除 设置为false时则不再允许删除这个属性,不允许修改第三个参数中的特性
  });
  obj.pname = '华为';
  console.log(obj);
  console.log(Object.keys(obj));

  delete obj.pname  // 删除 pname 属性

  console.log(obj);

你可能感兴趣的:(【前端 javascript 高级】 02 - 构造函数和原型+继承+ES5新增方法)