js进阶---原型的深入理解,改变this的三种指向05

87 阅读4分钟

原型的深入理解

  1. 每个函数都有是函数数据类型的实例,实例就会有__proto__属性,指向所属类的原型
  2. Object基类也是一个函数,所以Object.__proto__也指向Function.prototype
  3. 函数的实例有__proto__prototype两个属性
  4. Function基类自身也是个函数,所以他自身也有一个__proto__,指向所属类的原型,Function.prototype

原型拓展

一个方法重复使用时,可以直接将其定义为原型上的公有属性,例如

function Fn(name,age){
    this.name=name;
    this.age=age;
}
Fn.prototype.say=function(){
   alert('我的名字是:'+this.name+'----我的年龄是:'+this.age);
}
var f1=new Fn("阿杰",18);
f1.say();

封装一个push

Array.prototype.myPush=function(){
    for(var i=0;i<arguments.length;i++){
        // this[this.length]=arguments[i];
        // this.splice(this.length,0,arguments[i]);
        this.splice(this.length,1,arguments[i]);
    }
    return this.length;
}
var ary=[1,2,3];
console.log(ary.myPush(4,5,6,7));//返回数组长度7
console.log(ary);//新数组[1,2,3,4,5,6,7]

链式调用

实现链式写法:保证每次函数执行完毕之后的返回结果是当前类的实例

//想要实现一个需求:
//var ary=[5,8,2,1,10] ;想要让这个数组先排序,然后再倒序,然后再往里面添加一个10,然后再删除第一项;
var ary=[5,8,2,1,10];
ary.sort(function(a,b){return a-b}).reverse().push(10);//因为push返回值是10所以不能继续
ary.shift();
console.log(ary);//[8,5,2,1,10]

原型重定向

手动更改原型的指向,没有constructor,所以会沿着原型链查找,找到Object.prototype的constructor===>Object 所以手动更改的原型需要手动加一个constructor属性

function Fn(){
  this.x=100;
}
Fn.prototype.getX=function(){
  return this.x;
}
var f1=new Fn();
Fn.prototype={
   //consturctor:Fn,
   getY:function(){
      return this.x
   }
};
var f2=new Fn();
console.log(f1.getX());//100
console.log(f2.getX()); // 报错
console.log(f1.constructor); //Fn
console.log(f2.constructor); // Object

封装检测是不是公有的

Object.prototype.myHasOwnProperty=function(item){return item in this&&!this.hasOwnProperty(item)?true:false;}
[1,2,3].myHasOwnProperty("length");//false
//hasOwnProperty检测的是是不是私有的
[1,2,3].hasOwnProperty("length");//true

new的时候先执行()之前的,如new fn(),先执行new fn然后在执行()

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();//2
getName(); //4
Foo().getName();//1
getName();//1
new Foo.getName();//{}  2
new Foo().getName();// {}  3先算new Foo再算.getName
new new (Foo().getName());//{} 3

继承

原型继承:

A 类和B 类,B类想要继承A类的私有和公有属性,我们可以用原型继承:让B的prototype(原型)等于 A的实例

function A(){
    this.a="a";
}
A.prototype.getA=function(){
    console.log(this.a);
}
function B(){
    this.b="b";
}
B.prototype=new A();
var b=new B();
console.log(b);//"b"

中间类继承

  • 继承公有属性

某些实例并不属于某类,但是想用其原型上的方法,我们可以手动的去更改实例的__proto__,让它指向这个类的原型,就可以使用其原型上的方法

function fn(){
    arguments.__proto__=Array.prototype;
    arguments.sort(function(a,b){
        return a-b;
    })
    return arguments;
}
fn(8,2,1,9);//Arguments(4) [1, 2, 8, 9, callee: ƒ, Symbol(Symbol.iterator): ƒ]

call继承

让B的实例包含A,B的私有属性

function A(){
    this.a="a";
    this.x=100;
}
function B(){
    //此时的this是b1
    A.call(this);
    this.b="b";
    this.y=200;
}
var b1=new B();
console.log(b1);//{a:"a",x:100,b:"b",y:200}

寄生组合继承

有A类、B类两个类,让B类继承A类的私有和公有

function A(){
    this.a="a";
    this.x=100;
}
A.prototype.getA=function(){
    console.log("A");
}
function B(){
    A.call(this);
    this.b="b";
    this.y=200;
}
var obj=Object.create(A.prototype);
//创建一个对象,让他的__proto__指向A.prototype
B.prototype=obj;
var b1=new B();
console.log(b1.getA);

改变this的三种方式

this不能手动更改,会报错

function fn(){
    this=100;
    console.log(this);
}
fn();//报错,因为this不能手动更改Invalid left-hand side in assignment

call,bind,apply这三个方法存在Function的原型上,只要是函数都可以使用,(Object基类也可以)

call

  • call里面的第一个参数是this的指向
  • 后面的其他当成参数传进去
  • 严格模式下:第一个参数为null,this就是null ;不传或者undefinedthis就是undefined
  • 非严格模式下:第一个参数不传或者null、undefined,this都是window
function fn(){
    console.log(this);//obj{}
    console.log(arguments);//Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
}
var obj={};
fn.call(obj,1,2,3);
call执行过程
  1. 通过原型链找到Function.prototype上的call方法
  2. 找到之后,让fn这个方法执行
  3. 执行的时候,内部需要把fn函数里面的this进行改变,改成第一个参数

apply

  • 与call大致一样,只是除了第一个值代表this指向以外,后面的值是以数组或类数组进行传参的
function fn(){
    console.log(this);//obj{}
    console.log(arguments);//Arguments [Array(3), callee: ƒ, Symbol(Symbol.iterator): ƒ]
}
var obj={};
fn.call(obj,[1,2,3]);

bind

  • bind只是预处理this指向的,但是它是预处理this的指向,函数调用后,只是预处理了里面的this,要想让fn执行,我们需要再次调用它的返回值
  let obj = { name: 1 };
  function fn(x,y) {
        console.log(this,x,y);
  }
  var res=fn.bind(obj,1,2);//只处理不执行函数
  res();//需要再执行一下

封装call

基础版

Function.prototype.myCall=function(obj,...ary){
    // var res=null;
    // if(obj){
    //     obj.this=this;
    //     res=obj.this(...ary);
    //     delete obj.this;
    // }else{
    //     res=this(...ary);
    //     delete obj.this;
    // }
    // return res;
    obj=obj||window;
    obj.this=this;
    res=obj.this(...ary);
    delete obj.this;
    return res;//返回fn执行返回的结果
}
var obj={name:"li"};
function fn(x,y){
    console.log(this);
    console.log(arguments);
    return x+y;
}
fn.myCall(obj,1,2);

进阶版

var A=function(){
    console.log(this)
};
// A.call(100);
Function.prototype.mycall=function(index,...ary){
    index=index||window;
    index instanceof Object||index instanceof Function?null:index=Object(index);
    var key=Symbol("key"),res;
    index[key]=this;
    res=index[key](...ary);
    delete index[key];
    return res;

}
A.mycall(100);

封装bind

var A=function(x,y){
    console.log(this);
    return x+y;
};
Function.prototype.mybind=function(...ary){
    let that=this;
    return function(){
        that.call(...ary);
    }
}
document.body.onclick=A.mybind(100,10,20);