名词解释
什么是构造函数,与普通函数的区别
函数的定义方式有声明式定义、函数表达式和new function()方式。构造函数是一种特殊的函数,主要用来初始化对象,通常和new一起使用。构造函数的首字母是大写的。
如果用new来初始化一个构造函数的实例,会执行如下操作。
- 创建一个新的对象
- 新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性
- 构造函数的this指向当前的新对象
- 执行构造函数的代码
- 如果构造函数返回非空,就返回该对象,否则返回刚创建的新对象
原型
-
Js规定每一个构造函数都有一个prototype属性,这个属性是一个对象(称为原型对象),它的好处是在这个原型对象内定义的属性可以被实例共享。可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
-
每一个对象都有一个"__ proto __"属性,这个属性就指向的是原型对象。
-
每一个原型对象中有一个constructor属性,这个属性指向的是构造函数本身。constructor主要是记录该对象引用于哪一个构造函数,它可以让原型对象重新指向原来的构造函数。
-
构造函数、原型对象、实例之间的关系: 每个构造函数都有一个原型对象,原型有 一个属性指回构造函数,而实例有一个内部指针指向原型。
- 访问:在通过对象访问属性时,先搜索实例本身的属性,之后如果没有找到该属性,则会搜索原型对象中的属性,在原型对象中找到后,就返回该值。所以是采用两步搜索的规则。
原型链
如果原型对象是另一个构造函数的实例,那么原型对象本身内部就指向另一个原型,另一个原型内部也有一个指针指向另一个构造函数,这就形成了原型链。
例子:
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
每一个对象中都有一个默认的原型:Object,所有的对象都继承了Object的属性,所以即使自定义了一个构造函数,那也会有一个原型链。
一些方法判断原型与继承关系
只要对象实例间接或者直接指向原型,那么都是有继承关系的,下面都可以返回true
- 采用instanceof
console.log(instance instanceof Object) // true
console.log(instance instanceof SubType) // true
console.log(instance instanceof SuperType) // true
- 采用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 的函数只在乎是谁调用了它,跟在哪里进行的函数声明没有关系。
关于使用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关键字