(七)-前端-原型链

7.原型及原型链

原型

- 函数都带有一个prototype 属性,这是属性是指向构造函数的原型对象,这个对象包含所有实例共享的属性和方法。

- 原型对象都有一个constructor 属性,这个属性指向所关联的构造函数。

- 每个对象都有一个__proto__ 属性[非标准的方法],这个属性指向构造函数的原型 prototype

原型链

- 当访问实例对象的某个属性时,会先在这个对象本身的属性上查找,如果没有找到,则会 通过 proto 属性去原型上查找,如果还没有 找到则会在构造函数的原型的__ proto__中查 找, 这样一层层向上查找就会形成一个作用域链,称为原型链

原型相关习题

![img](file:///C:/Users/admin/AppData/Local/Temp/msohtmlclip1/01/clip_image006.png)

![img](file:///C:/Users/admin/AppData/Local/Temp/msohtmlclip1/01/clip_image007.png)

Object.create****的作用:

let obj = {a:123};
let o = Object.create(obj);
//该函数返回了一个新的空对象,但是该空对象的__proto__是指向了obj这个参数
// 手写Object.create
function create(proto) {
  function F() {}
  F.prototype = proto;
 
  return new F();
}

new****的执行过程是怎么回事?

new操作符做了这些事:

· 它创建了一个全新的对象

· 它会被执行[[Prototype]](也就是__proto__)链接

· 它使this指向新创建的对象

· 通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上

· 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用

//模拟new

function objectFactory() {

 const obj = new Object();

 const Constructor = [].shift.call(arguments);

 

 obj.__proto__ = Constructor.prototype;

 

 const ret = Constructor.apply(obj, arguments);

 

 return typeof ret === "object" ? ret : obj;

}

 

call,apply,bind****三者的区别?

**
** apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数fun.apply(thisArg, [argsArray])

apply 和 call 基本类似,他们的区别只是传入的参数不同。

apply 和 call 的区别是 call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。

bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

call做了什么:

将函数设为对象的属性

执行&删除这个函数

指定this到函数并传入给定参数执行函数

如果不传入参数,默认指向为 window

//实现一个call方法:

Function.prototype.myCall = function(context) {

 //此处没有考虑context非object情况

 context.fn = this;

 let args = [];

 for (let i = 1, len = arguments.length; i < len; i++) {

  args.push(arguments[i]);

 }

 context.fn(...args);

 let result = context.fn(...args);

 delete context.fn;

 return result;

};
/ 模拟 apply
Function.prototype.myapply = function(context, arr) {
  var context = Object(context) || window;
  context.fn = this;
 
  var result;
  if (!arr) {
    result = context.fn();
  } else {
    var args = [];
    for (var i = 0, len = arr.length; i < len; i++) {
      args.push("arr[" + i + "]");
    }
    result = eval("context.fn(" + args + ")");
  }
 
  delete context.fn;
  return result;
};
 
 
实现bind要做什么
返回一个函数,绑定this,传递预置参数
bind返回的函数可以作为构造函数使用。故作为构造函数时应使得this失效,但是传入的参数依然有效
 
// mdn的实现
if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }
 
    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          // this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
          return fToBind.apply(this instanceof fBound
                 ? this
                 : oThis,
                 // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };
 
    // 维护原型关系
    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    // 下行的代码使fBound.prototype是fNOP的实例,因此
    // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
    fBound.prototype = new fNOP();
 
    return fBound;
  };
}
 
 
// 简单版
    Function.prototype.myBind = function(context,...arg){
        // this --->  fn
        var _this = this;
        return function(...ary){
            // _this(...arg)
            return _this.apply(context,arg.concat(ary))
        }
    }

实现类的继承

类的继承在几年前是重点内容,有n种继承方式各有优劣,es6普及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深入理解的去看红宝书即可,我们目前只实现一种最理想的继承方式。

function Parent(name) {
    this.parent = name
}
Parent.prototype.say = function() {
    console.log(`${this.parent}: 你打篮球的样子像kunkun`)
}
function Child(name, parent) {
    // 将父类的构造函数绑定在子类上
    Parent.call(this, parent)
    this.child = name
}
/** 
 1. 这一步不用Child.prototype =Parent.prototype的原因是怕共享内存,修改父类原型对象就会影响子类
 2. 不用Child.prototype = new Parent()的原因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性
3. Object.create是创建了父类原型的副本,与父类原型完全隔离
*/
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function() {
    console.log(`${this.parent}好,我是练习时长两年半的${this.child}`);
}
// 注意记得把子类的构造指向子类本身
Child.prototype.constructor = Child;
var parent = new Parent('father');
parent.say() // father: 你打篮球的样子像kunkun
var child = new Child('cxk', 'father');
child.say() // father好,我是练习时长两年半的cxk

谈谈你对this指向的理解

this 的指向,始终坚持一个原理:this 永远指向最后调用它的那个对象

改变 this 的指向我总结有以下几种方法:

· 使用 ES6 的箭头函数

· 在函数内部使用 _this = this

· 使用 apply、call、bind

· new 实例化一个对象

全局作用域下的this指向window

如果给元素的事件行为绑定函数,那么函数中的this指向当前被绑定的那个元素

函数中的this,要看函数执行前有没有 . , 有 . 的话,点前面是谁,this就指向谁,如果没有点,指向window

自执行函数中的this永远指向window

定时器中函数的this指向window

构造函数中的this指向当前的实例

call、apply、bind可以改变函数的this指向

箭头函数中没有this,如果输出this,就会输出箭头函数定义时所在的作用域中的this

posted @ 2020-12-23 14:33  HelloBytes  阅读(181)  评论(0编辑  收藏  举报