this的指向及原型和原型链 【作为构造函数被new调用】
this在函数中的指向场景有4种:
1. 作为构造函数被new调用
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title></title> 6 </head> 7 <body> 8 <script> 9 console.log(this) 10 function Person () { 11 console.log(this) 12 } 13 var person = new Person(); 14 </script> 15 </body> 16 </html>
在代码里面首先是先输出此时的this指向,然后再实例化一个对象并在构造函数里面输出this的指向,此时输出结果是:
一开始默认我们的this指向的是window,但是当我们用new调用构造函数后,this的指向就变成了这个新对象,并且里面还有个默认的属性__proto__,实际上当我们在使用new调用构造函数时,会执行以下的操作:
1.创建一个新对象
2.构造函数的propotype指向当前我们这个新对象的__proto__
首先,需要先来了解一下什么是原型,原型(__proto__)是一个对象,其它的对象可以通过原型来实现属性的继承,并且大多数的对象的原型都是__proto__
例子:我们分别定义一个数组,一个对象和一个函数
1 var arr = ['1','2'] 2 console.log(arr) 3 var obj = { name: '小红'} 4 console.log(obj) 5 function fn () {} 6 console.log(fn)
输出结果:
可以看到输出的arr和obj都有一个__proto__(原型对象),而我们的定义的方法并没有原型对象。我们再将arr里面的原型对象展开:
可以看到这个对象里面的方法都是我们数组所包含的方法,但是我们的arr可以继承到__proto__(原型对象)里面的属性和方法,也就是我们经常这样来访问:arr.length(), 而funtion的原型是prototype,这是函数才有的属性,我们每个对象有的属性是__proto__,__proto__不是一个标准的名字,它的原名是 [ [ prototype ] ],只是脚本中并没有访问 [ [ prototype ] ]的方法,而在部分浏览器中才能支持这个属性。
在__proto__里面有个contructor构造器(是个函数),我们的arr是什么样子全靠这个构造器,这个函数里面有prototype,而它里面的所有又和我们proto里面是一样的。看下面两个截图:
左边的是arr的原型,右边是arr原型里面的展开,可以看到我们构造器里面的prototype里面的东西和左边proto展开的东西是一模一样的!
这可以说明一个点就是,大多情况下,__proto__可以理解为其构造器(是个函数)的原型(函数的原型prototype), 也就是说可以这样来看__proto__ === construter.prototype
再来看下原型的指向,它取决于对象创建时的实现方式:
① 字面量创建对象,也就是我们平时经常用的创建方式,var xxx = { ...... } , 此时它的原型就是__proto__,它等于其构造器的原型。
② 构造器创建对象(构造函数),var X = function () {} var x = new X() 这就是一个new出来的对象,此时它的指向会不一样,我们X的原型还是prototype(因为构造函数也是函数,只是多了一个new),而我们x的原型,虽然输出的结果多了个A,但是它的原型依旧是其构造器的原型,即x.__proto__ === X.prototype 也等于 x.__proto__ === x.constructor.prototype 只是x的构造器现在是我们自定义的X,但是并没有什么大变化
③通过Object.create 方式来创建对象(有点有趣的变化)
这儿展开后我们的b的__proto__的指向发生了变化,不再是其构造器的原型了,
但是变成了这个,也就是我们创建其的对象a
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
原型基本就讲到这边,然后是原型链的讲解,先看下下面的代码和输出:
1 var A = function() {} 2 var a = new A() 3 console.log(a.__proto__) 4 console.log(a.__proto__.__proto__) 5 console.log(a.__proto__.__proto__.__proto__)
好吧第一个应该都知道a的原型是等于其构造器的原型的 a.__proto__ === a.constructor.prototype === A.prototype,
而第二项也就是求A.prototype的原型,由于我们也可以将其看做一个对象,那么这个对象的原型依旧是其构造器的原型,它的构造器是Object() {....省略里面的属性和方法..} 那么控制台来输出看下是不是,以下两句是相等的,都会返回true,你们可以自己试一下
而第三项就是输出Object.prototype的原型了,但是Object已经是最底层的了,所以它没有原型,会输出null
这样就是我们所说的原型链了,一个对象的原型等于其构造器的原型,而其构造器的原型又等于其构造器的原型的原型,一直到最后为null,在js中万物都是对象,所以会新成一条由__proto__构成的链条,递归访问__proto__必须有个出口,且数值为null,当js引擎查找对象的属性的时候,是先查找对象本身是否存在该属性,如果不存在,则沿着原型链不断地进行查找,有就使用没有就为空,它不会查找自身的prototye,下面是个原型链查找的例子:
1 var arr = ['1','2','3'] 2 console.log(arr.unshift())
这边我们调用了unshift方法,其实是用的原型链上的Array对象里面的unshift方法,我们来是试下随便定义一个方法比如说,arr.showArray(),
此时控制台会报错
这是因为整条原型链上都找不到这个方法来调用,但是我们可以为它的原型链上加上这个方法,再来调用:
此刻再调用的话就能够访问到了
3.将新对象赋值给当前的this
4.执行构造函数(为这个新对象添加属性,继承其构造函数的原型上的所有属性和方法)
5.如果构造函数没有其它的返回对象,则默认返回这个新对象,否则则会被忽略