• 原型,原型链,实现call函数
  • 发布于 1个月前
  • 88 热度
    0 评论
原型
定义
原型是function函数的一个属性,这个属性是一个对象。它定义了构造函数制造出来的实例的公共祖先。通过该构造函数产生的实例对象,可以继承来着原型的属性和方法,原来在构造函数中直接赋给对象实例的值,可以直接赋值给它们的原型。
.利用原型特点,可以提取共有方法。(属性一般不共有)
.实例对象如何查看原型 隐式属性__proto__
.实例对象如何查看对象的构造函数constructor
.function函数如何查看原型prototype

此图为构造函数 实例对象 原型之间的关系图

图1.构造函数_实例对象_原型之间的关系
注意:不要被实例对象点constructor获取构造函数所影响,就说constructor是实例对象的属性,记住constructor属性是在原型上的,它是通过原型链的方式,在原型上找到constructor属性,而不是实例对象上。

实例对象可以对原型进行增删改吗?
答:都不可行,只有访问才行。做增删改相当于在实例对象上做增删改。

原型链
当谈到继承时,JavaScript只有一种结构:对象。每个对象都有一个私有属性([[Prototype]])指向另一个名为原型(prototype)的对象。原型对象也有自己的原型,层层向上直到一个对象的原型为null。根据定义,null没有原型,并作为这个原型链(property chain)中的最后一个环节。

原型的作用(继承)
在我们实例化对象的时候,总有一些写死的值(不一定是常量)和方法,这些写死的值和方法,我都需要再创建实例对象的时候再初始化一边。其实这些代码,对于实例化对象来说是代码的冗余。一般情况下,方法都是写在原型上,属性基本不写在原型上。原因,方法是为了减少代码的冗余,属性是为了防止共享被修改。如果要在原型上加属性,一般情况下都会是通过属性描述符的去添加,并且是不可删除,不可枚举,不可修改的值。

当实例对象上没有对应的属性或者方法的时候,会向其原型链上找。一个对象可以访问其原型链上的任意属性的形式方法叫做原型链继承。
  function Person (name) {
    this.name = name;
    // this.getName = function () {
    //   return this.name
    // }
  }
  Person.prototype.getName = function () {
    return this.name
  }

  let p1 = new Person('wzg');
  console.log(p1.name); // wzg
  console.log(p1.getName()); // wzg
call,apply,bind的作用
call
Function 实例的 call() 方法会以给定的 this 值和逐个提供的参数调用该函数。
1.可以调用函数
2.改变函数中 this 的指向。
3.继承

作用1:
   function fn1 () {
    console.log('打印');
  }
  fn1();
  fn1.call();
两者执行函数没有区别

作用2:
调用call没有第二个参数
 function greet () {
    console.log(this.animal, "的睡眠时间一般在", this.sleepDuration, "之间");
  }
  const obj = {
    animal: "猫",
    sleepDuration: "12 到 16 小时",
  };
  /* greet函数中的this,指向obj对象 */
  greet.call(obj); // 猫 的睡眠时间一般在 12 到 16 小时 之间


改变greet函数中的this指向,换句话说是greet函数中的this指的就是obj对象。我们通过debugger调试代码,发现greet执行时,其中this对象和obj对象相同。

.有第二个参数或者更多参数
  let dog = {
    name: '小狗',
    eat (food, status) {
      console.log(this.name + `吃${food}${status}`);
    }
  }
  dog.eat('肉', '很开心');//小狗吃肉很开心
  let cat = {
    name: '小猫',
  }
  dog.eat.call(cat, '鱼', '很开心');//小猫吃鱼很开心
apply 调用
传递参数的区别,apply通过数组的方式
dog.eat.apply(cat, ['鱼', '很开心']);
bind 调用
传递参数没区别,只是函数没有立即执行,返回一个函数,可在需要调用的时候调用。
let fn = dog.eat.bind(cat, '鱼', '很开心');
fn();
继承(盗用构造函数)
 function Animal (name) {
    this.name = name;
    this.eat = function (food) {
      console.log(`${name}喜欢吃${food}`);
    }
  }
  function Cat (name) {
    Animal.call(this, name);
  }
  let cat = new Cat('汤姆');
  cat.eat('鱼');

运行到Animal.call(this, name)这段代码的时候,Animal构造函数里面的this,就是实例化对象cat,这样做就实现了,Cat构造函数继承了Animal中的一些属性和方法(仅仅是this点上的一些属性和方法。)

实现call函数
第一版:
Function.prototype.myCall = function (ctx, ...args) {
    console.log(this);
    console.log(ctx);
    ctx['key'] = this;
    var result = ctx['key'](...args);
    return result;
  }

  function Person (name, age) {
    this.name = name;
    this.age = age;
  }
  let p1 = {
    key: 10
  };
  Person.myCall(p1, 'wzg', '18')
  console.log(p1);
执行Person.myCall,myCall函数中的this指向Person,那么只需要执行this(),就可以调用Person函数,但是this()这样执行,Person函数中的this就指向了Window。我们的目的是要把Person中的this指向传入的ctx。有一个办法可以做,就是通过ctx去调用这个方法,但是ctx中没有该方法,那么可以临时创建一个:ctx['key'] = this;,然后调用cat[key](...args);就可以解决。但是这样做会有一个问题,实例化对象中就多了一个key属性,那么我们可以通过delete ctx['key'];进行删除,但是又有一个问题,实例化对象中原本就有key属性,那么就会被删除。要解决这个问题,Symbol就可以派上用场了。

第二版:
  Function.prototype.myCall = function (ctx, ...args) {
    console.log(this);
    console.log(ctx);
    let key = Symbol('key');
    Object.defineProperty(ctx, key, {
      value: this,
      configurable: true
    })
    /* configurable 在没有设置的情况下,默认值是false */
    var result = ctx[key](...args);
    /* 删除了对应的属性,enumerable是否设置了是否可以枚举已经没有意义了,Symbol类型本身就不能被枚举 */
    delete ctx[key];
    return result;
  }
   // 堆代码 duidaima.com
  function Person (name, age) {
    this.name = name;
    this.age = age;
  }
  // debugger
  let p1 = {};
  let p2 = {};
  Person.myCall(p1, 'wzg', '18')
  Person.call(p2, 'wzg', '18')
  console.log(p1);
  console.log(p2);
  console.log(Object.keys(p1));
这里用了属性描述符,设置属性是不可以修改,可以删除,不能枚举的。这样做的目的是防止for-in循环出key属性,但是key属性又是一个Symbol类型的,本身就不会被for-in循环出来。所以直接赋值也是一种更加简洁的写法

第三版:
  Function.prototype.myCall = function (ctx, ...args) {
    console.log(this);
    console.log(ctx);
    let key = Symbol('key');
    ctx[key] = this;
    var result = ctx[key](...args);
    delete ctx[key];
    return result;
  }

  function Person (name, age) {
    this.name = name;
    this.age = age;
  }
  // debugger
  let p1 = {};
  let p2 = {};
  Person.myCall(p1, 'wzg', '18')
  Person.call(p2, 'wzg', '18')
  console.log(p1);
  console.log(p2);
  console.log(Object.keys(p1));
注意:myCall函数还有一些细节没考虑,比如传入的ctx的类型判断,可以再写一个第四版
用户评论