this&原型链&继承

1.apply、call 、bind有什么作用,什么区别?

apply和call

apply和call都是为了改变某个函数运行时的上下文而存在的(就是为了改变函数内部this的指向);

如果使用apply或call方法,那么this指向他们的第一个参数,apply的第二个参数是一个参数数组,call的第二个及其以后的参数都是数组里的元素,需要全部列举出来。apply 和 call 的用法几乎相同, 唯一的差别在于参数传递方式不一样。 apply粗鲁些直接一个数组怼过来,而call则细腻些将数组里的元素一个一个传递进来

function(){console.log(this.a)}.call(undefined)

如果你传的 context 是 null 或者 undefined,那么 window 对象就是默认的 context(严格模式下默认 context 是 undefined)

他们常用用法:

  • 求数组中的最大和最小值

利用他们扩充作用域 从而借助Math来使用min和max的方法
注:由于没有对象调用这个方法,所以第一个参数可以写作null或者Math本身

var arr = [10,20,30,40,50]
var max = Math.max.apply(null,arr)  
var min = Math.min.call(Math,10,20,30,40,50)

(ps:这样看起来apply和call更像是寄生虫,让它的参数 能够使用本不属于它的技能=.=)

  • 数组之间的追加
var arr1 = [1,2,3]
var arr2 = [4,5,6]
var total = [].push.apply(arr1,arr2) //返回长度6因为this是arr1,所以追加到arr1上
//arr1 [1,2,3,4,5,6]
//arr2 [4,5,6]
image.png
  • 验证是否是数组(前提toString()方法没有被重写)
function isArray(obj){
  return Object.prototype.toString.call(obj) == '[object Array]'
}
isArray([])  //true
isArray('haha')  //false
  • 将类数组转化为数组
var trueArr = Array.prototype.slice.call(arrayLike)
  • 利用call和apply做继承
function Person(name,age){
   this.name = name
   this.age = age
   this.sayAge = function(){
      console.log(this.age)
  }
}
function Male(){
   Person.call(this,name,age)  //在子类构造函数中执行父类的构造函数
}
var haha = new Male('haha',5)
  • 使用log代理console.log
function log(){
  console.log.apply(console,arguments)
}
// 当然也有更方便的 var log = console.log()

关于bind()

也是改变函数体内this的指向

bind会创建一个新函数,称为绑定函数,当调用这个函数的时候,绑定函数会以创建它时传入bind()方法时的第一个参数作为this,传入bind()方法的第二个及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数

  • 参数的使用:
    call 是把第二个及以后的参数作为 fn 方法的实参传进去,而 fn1 方法的实参实则是在 bind 中参数的基础上再往后排。在下面的例子中,bind方法有参数'haha',然后加上fn1传递的参数'A', 'B', 'C',一起作为fn的参数顺序执行.
function fn(a, b, c) {
    console.log(a, b, c);
}
var fn1 = fn.bind(null, 'haha');

fn('A', 'B', 'C');            // A B C
fn1('A', 'B', 'C');           // haha A B
fn1('B', 'C');                // haha B C
fn.call(null, 'haha');      // haha undefined undefined
image.png

bind与apply、call最大的区别就是:bind不会立即调用,其他两个会立即调用,bind返回值是一个改变了上下文 this 后的函数.

综上:
三个使用的异同点
1.都是用来改变函数的this对象的指向的
2.第一个参数就是this要指向的对象
3.都可以利用后续参数传参
4.bind返回一个新函数,便于稍后调用,apply和call是立即调用


原型链相关问题

this是什么?

随着函数使用场合的不同,this的值会发生变化。但是有一个总的原则,那就是this指的是调用this所在函数的那个对象

2.以下代码输出什么?

var john = { 
  firstName: "John" 
}
function func() { 
  alert(this.firstName + ": hi!")
}
john.sayHi = func
john.sayHi()

输出: John: hi!
解析:函数作为对象方法调用,会使得 this 的值成为对象本身。john调用的这个函数,所以这里的this指代john对象.

3.下面代码输出什么,为什么

func() 
function func() { 
  alert(this)
}

输出: window对象
解析: 在函数被直接调用时this绑定到全局对象。在浏览器中,window 就是该全局对象

4.下面代码输出什么,为什么

document.addEventListener('click', function(e){
    console.log(this);
    setTimeout(function(){
        console.log(this);
    }, 200);
}, false);

输出:依次document window对象
解析:在事件处理程序中this代表事件源DOM对象,所以第一个为document,
setTimeout内的函数属于回调函数,理解为f1.call(null,f2),所以this指向window

5.下面代码输出什么,为什么

var john = { 
  firstName: "John" 
}

function func() { 
  alert( this.firstName )
}
func.call(john)

输出:John
解析: call中传入的第一个参数即this的指向

6.以下代码有什么问题,如何修改

var module= {
  bind: function(){
    $btn.on('click', function(){
      console.log(this) //this指向$btn
      this.showMsg();
    })
  },

  showMsg: function(){
    console.log('饥人谷');
  }
}

修改:

var module= {
  bind: function(){
    var _this = this
    $btn.on('click', function(){
      console.log(_this) //this指向module
      _this.showMsg();
    })
  },

  showMsg: function(){
    console.log('饥人谷');
  }
}

7.有如下代码,解释Person、 prototype、___proto____、p、constructor之间的关联。

function Person(name){
    this.name = name;
}
Person.prototype.sayName = function(){
    console.log('My name is :' + this.name);
}
var p = new Person("若愚")
p.sayName();

关系:

Person.prototype.constructor === Person
p.__pro__ === Person.prototype
原型图 .png

8.上例中,对象 p可以这样调用 p.toString()。toString是哪里来的? 画出原型图?并解释什么是原型链。

原型图2.png
  • 解析: p是Person构造函数的实例,p首先会从自身上找有没有toString()方法,没有的话会沿着__proto__属性找到Person.prototype对象,在这个对象上继续找有没有这个方法,如果还是没有会继续沿着Person.prototype.__proto__属性逐级往上找,直到Object.prototype为止,如果还是没有该方法,则返回null,期间找到了就调用该方法.
    p最后沿着原型链在Object.prototype中找到了toString()方法,所以就可以调用该方法,这也叫做继承方法.
  • 原型链:
    每个构造函数都有一个原型对象,原型对象都包含指向其构造函数的指针constructor,而实例都包含一个指向这个原型对象的__proto__指针。如果我们让这个原型对象等于另一个类的实例,则此时这个原型对象也将包含一个指向另一个原型对象的指针__proto__,以此为依据,层层递进,就构成了实例与原型的链条,成为原型链。
  • 原型链作用
    在访问对象的属性方法时,如果在对象本身中没有找到,则会去沿着原型链逐级向上查找,期间找到则返回该属性方法,如果这个原型链都无法找到该属性方法,则返回undefined。原型链一般实现为一个链表,这样就可以按照一定的顺序来查找,原型链是实现继承的主要方法。

注意:

当new一个类的时候,经历的步骤:

1.创建类的实例,把一个空的对象的__proto__属性设置为F.prototype(F为该类)
2.初始化实例,函数F被传入参数并调用,关键this被设定为该实例
3.返回实例对象

归纳:

1.我们通过构造函数定义了类,类自动获得属性prototype
2.每个类的实例都会有一个内部属性__proto__,指向类的prototype原型
3.一切函数都是由Function类创建的,所以Function.prototype === 被创建的函数.__proto__
4.一切函数的原型对象都是由 Object 类创建的,所以Object.prototype === 一切函数.prototype.__proto__

image.png

9.问题: 对String做扩展,实现如下方式获取字符串中频率最高的字符

var str = 'ahbbccdeddddfg';
var ch = str.getMostOften();
console.log(ch); //d , 因为d 出现了5次

实现:

String.prototype.getMostOften = function(){
            var obj = {}
            for(var i=0;i<this.length;i++){
                var item = this.substr(i,1)  //substr(start,length) 截取字符串,start截取开始位置,length截取长度
                //此时item分别等于a h b b c c d e d d d d f g
                if(!obj[item]){
                    obj[item] = 1  //如果obj对象中没有item属性,则设置item: 1;
                }else{
                    obj[item]++ //如果有该属性item属性对应的值加1
                }
            }
            var num = 0,max = null
            for(key in obj){  //遍历obj中的属性
                console.log(key)        //a h b c d e f g
                if(obj[key] > num){    //即某个属性对应的值大于num
                    num = obj[key]     //最大值
                    max = key          //最大值的属性
                }  
            }
            return max
        }

        var str = 'ahbbccdeddddfg';
        var ch = str.getMostOften();
        console.log(ch);

10.问题: instanceOf有什么作用?内部逻辑是如何实现的?

  • 作用:判断一个对象是否是某个类的实例,是的话返回true,不是的话返回false
    举例:
[1,2,3] instanceof Array //true
[1,2,3] instanceof Object //true
  • 实现:a instanceof b的判断规则是:沿着a的__proto__这条线来找,同时沿着b的prototype这条线来找,如果两条线逐级向上查找能找到同一个引用,返回true,证明A是B类型的实例,否则返回false。代码如下
function InstanceOf(a,b){
    var proto=a.__proto__;
   do{
    if(proto === b.prototype){
        return true;
    }else{
        proto=proto.__proto__;
    }
   }
   while(proto)    
    return false;
}

继承相关问题

11.问题: 继承有什么作用?

继承是指一个对象直接使用另一对象的属性和方法。通过继承,可以使子类共享父类的属性和方法,可以覆盖(重写)和扩展父类的属性和方法,减少内存的使用,提高代码复用性。

12.问题: 下面两种写法有什么区别?

//方法1
function People(name, sex){
    this.name = name;
    this.sex = sex;
    this.printName = function(){
        console.log(this.name);
    }
}
var p1 = new People('饥人谷', 2)

//方法2
function Person(name, sex){
    this.name = name;
    this.sex = sex;
}

Person.prototype.printName = function(){
    console.log(this.name);
}
var p1 = new Person('若愚', 27);
  • 方法1是将printName()作为构造函数的方法,在实例化的时候,每次都要创建printName(),作为新对象的私有属性,这样每个实例都要重复一遍,会浪费内存
  • 方法2是将printName()作为构造函数原型上的方法,在实例化的时候,p1可以调用,其它的实例也可以来调用,是作为共享的方法,可以优化内存空间

13.问题: Object.create 有什么作用?兼容性如何?

  • 作用:
    Object.create() 方法创建一个拥有指定原型和若干指定属性的对象
    Object.create()接收两个参数,作用是创建接收到的第一个参数的副本,第二个参数是可选的、额外传入副本里的属性,以第二个参数指定的任何属性都会传入副本中并覆盖已有的同名属性,但原型对象上的同名属性不会被改变。
    如例:
var person = {
    name: 'tom',
    friends: ['a', 'b', 'c']
}

var anotherPerson = Object.create(person, {   //将anotherPerson中的__proto__替换成person
    name: {                    //第二个参数是额外传入的属性,会覆盖之前的同名属性
        value: 'tony'
    }
})

console.log(anotherPerson.name) //tony
console.log(person.name) //tom
  • 兼容性:各大浏览器的最新版本(包括IE9)都部署了这个方法
    解决低版本iE浏览器问题:
if(!Object.create){
   Object.create = function(obj){
       function fn(){}
       fn.prototype = obj       //prototype即方法 fn继承了obj.prototype的方法
       return new fn()
   }
}

14.问题: hasOwnProperty有什么作用? 如何使用?

  • 作用: 判断属性和方法时自己的还是父类的,可以用hasOwnProperty,可以判断是对象自定义属性而不是原型链上的属性,返回布尔值
    hasOwnProperty是javascript中唯一一个处理属性但是不查找原型链的函数
  • 使用: 实例.hasOwnProperty('属性名'),返回true则属性存在于实例中,false则属性存在于原型中。
var a = {
                name: 'a'
            };
a.hasOwnProperty('name');//true
a.hasOwnProperty('toString');//false
a.hasOwnProperty('valueOf');//false

15.问题如下代码中call的作用是什么?

function Person(name, sex){
    this.name = name;
    this.sex = sex;
}
function Male(name, sex, age){
    Person.call(this, name, sex);    //这里的 call 有什么作用
    this.age = age;
}
  • 作用: 绑定this指向,属性获取
    Male对象属性的获取是通过构造函数Person在其内部的执行,我们在一个类中执行另外一个类的构造函数,就可以把属性复制到自己内部,但是我们需要把环境改到自己的作用域内,就需要借助call,将this指向Male类的实例. 这样在执行Person.call(this, name, sex)会赋予实例这些属性.

16.问题: 补全代码,实现继承

function Person(name, sex){
    this.name = name
    this.sex = sex
}

Person.prototype.getName = function(){
   console.log('My name is' + this.name)
};    

function Male(name, sex, age){
  Person.call(this,name,sex)                        //获取父类Person的属性,Person构造函数中的this指向实例,传入的参数this也指向实例
  this.age = age                                           //添加age属性
}

Male.prototype = Object.create(Person.prototype)          //获取父类的方法
Male.prototype.constructor = Male             //将constructor有person类重新绑定到Male类

Male.prototype.getAge = function(){
    console.log('My age is' + this.age)
};

var ruoyu = new Male('haha', '男', 27);
ruoyu.getName();

总结:

1.获取父类属性
方法: 在子类的构造函数中,执行父类的构造函数,借助call绑定this指向实例

function 子类(){
   父类.call(this,arguments)     //this指向子类的实例,执行一遍后获取到父类的属性
}

2.获取父类的方法
方法一般都设置在类的prototype中,所以从这点出发
实现: 借助Object.create()方法,将子类.prototype.__proto__ = 父类.prototype

子类.prototype = Object.create(父类.prototype)   //设置完后,此时 子类.prototype.constructor为父类,需纠正
子类.prototype.constructor = 子类
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容

  • 一、this 相关问题 知乎上关于this的解答 this 的值到底是什么?一次说清楚 this 的工作原理在js...
    66dong66阅读 531评论 0 0
  • 1. apply、call 、bind有什么作用,什么区别? call ,apply的作用:调用一个函数,传入函数...
    Rising_suns阅读 364评论 0 0
  • this 相关问题 问题1: apply、call 有什么作用,什么区别 Javascript的每个Functio...
    Maggie_77阅读 570评论 0 0
  • this相关问题 apply、call 、bind的作用以及区别 call、apply和bind方法的用法以及区别...
    _Dot912阅读 616评论 0 5
  • 1: apply、call 、bind有什么作用,什么区别 call 和 apply 都是为了改变某个函数运行时的...
    高进哥哥阅读 238评论 0 0