推广 热搜: csgo  vue  angelababy  2023  gps  新车  htc  落地  app  p2p 

this、call、apply、bind、继承、原型链

   2023-06-23 网络整理佚名1880
核心提示:call、apply、bindcall(b),函数a内部如果是要用到this。call、apply、bindcall继承(构造函数继承)用了等号,先给自身赋值,所以自身赋值成功了也不会继续去原型链查找。但是实例化都会调用两次构造函数,new和call但是,不能做到函数复用,每一个实例都要写一份,而且写了一个就是写死了,也不能获取B类的内部属性A),而不是像那样改写原型链,构造函数是B

本文来自我的0.前言

这些都是高级js基础所必需的。 有时候你可能一下子记不住是什么,时不时回顾一下基础知识,增强自己的硬实力。

1.this 1.1this指向

谁打电话最后指向谁

我们先简单回顾一下,this指针只有以下几种类型:

新关键字

指向new创建的对象

function F() {
        this.name = 1
}
var f = new F()

调用、申请、绑定

指向传递给函数的第一个参数。 a.call(b),如果在函数 a 中使用它。 那么this this 指向b

对象方法

对象内部的方法指向对象本身

 var obj = {
                value: 5,
                printThis: function () {
                    console.log(this);
                }
            };

按值传递

指向全局

 var obj = {
                value: 5,
                printThis: function () {
                    console.log(this);
                }
            };
var f = obj.printThis
f()

如果出现上述规则的累积,则优先级会从1下降到4,并按照优先级最高的规则来判断其方向。

5. 箭头函数

指向箭头函数定义的外部上下文

var obj = {
                value: 5,
                printThis: function () {
                        return function(){ console.log(this)}
                   }
            };
obj.printThis()//window
 var obj = {
                value: 5,
                printThis: function () {
                                      return () => console.log(this)
                                 }
            };
obj.printThis()()//obj

2. 调用、申请、绑定

前两者是一样的,只是参数表达式不同。 bind代表静态的前两个,需要手动调用

a.call(b,args) 让函数a的执行上下文指向b,即即使b的属性没有函数,也可以像ba(args)一样调用

方法大家都知道,我们不妨自己实现一下这三个:

2.1 调用实现

再看概念,b没有方法,即没有ba。 如果你想要这种效果,那么使用这三个函数来改变执行上下文。 所以我们可以想,如果我们自己实现一个的话,很可能就是把a的方法强行添加到b中,然后让它去调用:

Function.prototype.mycall = function(){
    var ctx = arguments[0]||window||global//获取上下文,call的第一个参数
    var len = arguments.length
    var hash = new Date().getTime()//避免名字重复
    ctx[hash] = this//将this缓存,this就是那个想在另一个上下文利用的函数
    var result
    if(len === 1){
        result = ctx[hash]()//如果后面没有其他参数直接运行
    } else{
        var i = 1
        var args = []
        for(;i',')
        result = eval('ctx[hash](' + args + ')')//将参数传递进去调用
    }
    delete ctx[hash]//删除临时增加的属性
    return result
}

apply也是如此,少了array这一步,就更简单了。 接下来我们看看如何实现bind:

Function.prototype.mybind = function(){
    var ctx = arguments[0]||window||global
    var f = this
    var args1 = []
    if(arguments.length>1){//预先填入的参数
        var i = 1
        for(;i < arguments.length;i++){
            args1.push(arguments[i])
        }
    }
    return function(){
        var args2 = Array.prototype.slice.call(arguments)//call和apply我们都可以实现,这里就不再重复
        return f.apply(ctx,args1.concat(args2))//将预先填入的参数和执行时的参数合并
    }
}

另外,需要注意的是,函数绑定后,无论以后如何使用call、apply、bind,this点都不会改变,它是第一次bind的上下文

3.从调用到继承

首先,js没有严格的子类父类,继承的实现依靠原型链来达到类似于所谓类的效果。

3.1 调用继承(构造函数继承)

我们希望G继承F,或者说开发的时候,因为G有很多属性继承F而我们想偷懒,那么可以这样

function F(name,age){
  this.name = name 
  this.age = age
}
function G(name,age,a) {
  F.call(this,...arguments)
  this.a = a
}
var g = new G('a',12,1) //G {name: "a", age: 12, a: 1}

该方法的特点是子类可以向父类构造函数传递参数。 但是,无法获取 F 原型的属性。

另外该方法也写在内部

这个.f = (){}

也注定无法实现函数复用,每个实例都有一个函数,浪费内存。

3.2 继承

如果想让子类获取父类的属性,如果通过原型实现继承,那么父类的实例就是子类的原型:

function F(){
  this.a = [1,2,3,4]
  this.b = 2
}
var f = new F()
function G(){}
G.prototype = f
var g = new G()
var h = new G()
g.a //[1,2,3,4]
g.b //2
//对于引用类型,如果我们修改g.a(不是用=赋值,用=不会操作到原型链)
g.a.push(123)
g.a//[1,2,3,4,123]
//而且其他的实例也会变化
h.a //[1,2,3,4,123]
g.b = 666 //只是在实例里面对b属性进行改写,不会影响原形链

可以看出,对于父类的引用类型,一个值为引用类型的属性被重写后,子类所有实例继承的属性都会发生变化。 最主要的是子类可以改变父类。 但=赋值操作相当于直接在实例上重写。 因为属性搜索是基于原型链搜索的,所以先搜索自身,然后再搜索原型链,直到找到为止。 等号用于先给自己赋值,所以如果赋值成功,就不会继续搜索原型链。

因为它们都有各自的缺陷,所以有一种组合继承,就是把构造函数继承和继承混合在一起,把方法写在父类上,这是一种比较常见的方法。 但是实例化会调用构造函数两次,new和call

3.继承(原型继承)

这样就可以在两者之间添加一个中间F类,这样子类就不会污染父类了。 子类A继承自父类B,中间可以为他定义属性

function A() {}  
function B() {}  
A.prototype = Object.create(B.prototype,{father:{value:[1,2,3]}});
//Object.create的hack
Object.create =Object.create|| function (o) {
    var F = function () {};
    F.prototype = o;
    return new F();
}
//其实create函数内部的原理就是这样子,看回去上面的A和B,这些操作相当于
var F = function () {};
F.prototype = B.prototype;//原型被重写,a.__proto__.constructor是B而不是F
A.prototype = new F()
//create方法,第二个参数类似于defineProperty,而且定义的属性可以自行配置,默认是不可以重新赋值
var a = new A()
a.father //[1,2,3]
a.father = 1
a.father //[1,2,3]

当你不需要使用构造函数,只想查看子类父类的继承关系时,这基本上是完美的选择

3.4 寄生继承

使用封装继承过程的函数来实现继承。 不需要再定义一个子类,直接在函数中写子类的方法即可。

function createobj (obj) {
    var temp = Object.create(obj)
    temp.f = function () {
        console.log('this is father')
    }
    return temp
}
function B() {}  
var b = createobj (B.prototype)
b.f() //this is father

但是无法实现函数复用,每个实例都要写,写一个就写死,无法获取B类的内部属性

3.5 寄生成分遗传

对于以上仅靠。 继承,A. 原型对象被重写,它的构造函数是B,而不是中间量F。对于这种中间类F来说,它是没有意义的,只有依靠中间原型对象,我们才能使用更完美的寄生组合继承来进行:

function A() {}  
function B() {}  
var prototype = Object.create(B.prototype)//创建
prototype.constructor = A//增强
A.prototype = prototype//指定,这下a.__proto__.constructor 就是A了
var a = new A()

不需要创建中间类F,构造函数A确实创建了一个(a..==A),而不是像那样重写原型链,构造函数是B

附上原型链图:(注意端点为空,中间都是正常的新结构,无需重写)

 
标签: bind 构造函数
反对 0举报 0 收藏 0 打赏 0评论 0
 
更多>同类资讯
推荐图文
推荐资讯
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报
Powered By DESTOON