Js中的原型链

39 阅读5分钟

名词解释

什么是构造函数,与普通函数的区别

函数的定义方式有声明式定义、函数表达式和new function()方式。构造函数是一种特殊的函数,主要用来初始化对象,通常和new一起使用。构造函数的首字母是大写的。

如果用new来初始化一个构造函数的实例,会执行如下操作。

  1. 创建一个新的对象
  2. 新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性
  3. 构造函数的this指向当前的新对象
  4. 执行构造函数的代码
  5. 如果构造函数返回非空,就返回该对象,否则返回刚创建的新对象

原型

  • Js规定每一个构造函数都有一个prototype属性,这个属性是一个对象(称为原型对象),它的好处是在这个原型对象内定义的属性可以被实例共享。可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。

  • 每一个对象都有一个"__ proto __"属性,这个属性就指向的是原型对象。

  • 每一个原型对象中有一个constructor属性,这个属性指向的是构造函数本身。constructor主要是记录该对象引用于哪一个构造函数,它可以让原型对象重新指向原来的构造函数。

  • 构造函数、原型对象、实例之间的关系: 每个构造函数都有一个原型对象,原型有 一个属性指回构造函数,而实例有一个内部指针指向原型。

image.png

  • 访问:在通过对象访问属性时,先搜索实例本身的属性,之后如果没有找到该属性,则会搜索原型对象中的属性,在原型对象中找到后,就返回该值。所以是采用两步搜索的规则。

原型链

如果原型对象是另一个构造函数的实例,那么原型对象本身内部就指向另一个原型,另一个原型内部也有一个指针指向另一个构造函数,这就形成了原型链。

例子:

function SuperType() {
      this.property = true;
}

SuperType.prototype.getSuperValue = function() {
  return this.property;

};

function SubType() {
  this.subproperty = false;
}

// 继承SuperType  
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function () {
  return this.subproperty;
};

let instance = new SubType();
console.log(instance.getSuperValue()); // true

image.png

每一个对象中都有一个默认的原型:Object,所有的对象都继承了Object的属性,所以即使自定义了一个构造函数,那也会有一个原型链。

image.png

一些方法判断原型与继承关系

只要对象实例间接或者直接指向原型,那么都是有继承关系的,下面都可以返回true

  1. 采用instanceof
console.log(instance instanceof Object) // true
console.log(instance instanceof SubType) // true
console.log(instance instanceof SuperType) // true
  1. 采用isPrototypeOf()
console.log(Object.prototype.isPrototypeOf(instance)); // true 
console.log(SuperType.prototype.isPrototypeOf(instance)); // true 
console.log(SubType.prototype.isPrototypeOf(instance)); // true

this 指向的问题

总结

this 永远指向 调用 包含 自己(this本身) 的 函数 对应的对象

也就是说,包含 this 的函数只在乎是谁调用了它,跟在哪里进行的函数声明没有关系。

image.png

关于使用bind(), apply(), call()来改变this指向的说明

var name = 'rose', age = 20;
var a = {
    name :'lucy',
    curAge : this.age,
    toString: function(){
        console.log(`${this.name}'s age is ${this.age}.`);
    } 
}

a.curAge; // 20
a.toString(); // lucy's age is undefined.

这段代码中,curAge的this指向的是window,后面的toString属性中的this指向的是当前的所属对象a。

var name = 'rose', age = 20;
var a = {
    name :'lucy',
    curAge : this.age,
    toString: function(){
        console.log(`${this.name}'s age is ${this.age}.`);
    } 
}
var kk = {
    name: 'kk',
    age: 18
}

a.toString.apply(kk) // kk's age is 18.
a.toString.call(kk) // kk's age is 18.
a.toString.bind(kk) // 返回的是ƒ (){ console.log(`${this.name}'s age is ${this.age}.`); }

由此得出结论,bind 返回的是一个新的函数,你必须调用它才会被执行,这三者都可以改变this的默认指向。 1.

fun.call(thisArg, arg1, arg2, ...)  
fun.apply(thisArg, [arg1, arg2, ...])
fun.bind(thisArg, arg1, arg2, ...)

call() 经常做继承;
apply() 经常跟数组有关系;
bind() 不立即调用函数,如果有的函数我们不需要立即调用,但是又想改变这个函数内部 this 指向,此时用 bind(),如事件绑定。

箭头函数的this指向

上面是普通函数的this使用规则,而箭头函数的this又有点不同。它解决了普通匿名函数this指向不确定的情况。

箭头函数体内的this对象,是定义该函数时所在的作用域所指向的对象,而不是使用时所在的作用域指向的对象。

例子:

var name = 'rose', age = 20;
var a = {
    name :'lucy',
    curAge : this.age,
    toString: () => {
        console.log(`${this.name}'s age is ${this.age}.`);
    } 
}
a.toString();//rose's age is 20.

这边toString是一个箭头函数,它的作用域是a,a所指向的对象是外层的window,所以这里this指向的是window。
如果想要改变这个this,那么可以给箭头函数再套一层作用域:

var name = 'rose', age = 20;
var a = {
    name :'lucy',
    curAge : this.age,
    toString: function() {
        var k = () => {
        console.log(`${this.name}'s age is ${this.age}.`);}
        return k;
    } 
}
a.toString()();// lucy's age is undefined.

这个时候箭头函数的作用域是toString函数,这个函数指向的对象就是a。

const obj = { 
    num: 10, 
    hello: function () { 
    console.log(this); // this指向obj 
    setTimeout(() => { console.log(this); // this指向obj ,一般setTimeout里面的回调函数的this指向的是window
        }); 
    } 
 } 
 obj.hello();

关于箭头函数的一些特点:

  • 箭头函数的this指向永远不变,bind、apply、call都不能改变
  • 箭头函数不能作为构造函数使用,会报错,具体是因为构造函数new一个实例的过程中,会涉及到this指向当前新对象的过程,但是箭头函数没有自己的this
  • 箭头函数没有自己的arguments,可以通过rest传参
  • 箭头函数没有原型prototype
  • 箭头函数不能用作Generator函数,不能使用yeild关键字