持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情
JavaScript构造函数是指通过new关键字调用的函数且不能有返回值,他和普通函数有什么区别呢
- 构造函数可以通过new和实例进行调用,普通函数就只能通过函数名和实例进行调用
- 构造函数不能有返回值,普通函数可以有返回值
- 构造函数里面可以使用this定义变量,指向的当前使用new关键字调用出来的实例对象,普通函数的this是指向的调用者
- 构造函数是用于初始化对象的,而普通函数不能用于初始化
new 运算符
构造函数必须使用new关键字,那么new关键字都替我们做了那些事情
- 执行函数;
- 自动创建一个空对象;
- 把创建的对象指向另外一个对象;
- 把空对象和函数里的this 衔接起来;(this指向实例化对象)
- 隐式返还this;
手写new 运算符
// 构造函数
function Fn() {
this.name = '张三';
this.age = function() {
console.log('11111');
}
};
Fn.prototype.hello = function() {
console.log('hello');
}
//new 运算符:
/* new实例化之后创建一个空对象,mynew的时候由于需要知道mynew的哪一个构造函数所以传递 */
/**
* @description: 仿写new
* @param {*} constructor new的哪一个构造函数
* @param {*} ...data 里面还会去传递参数但是具体传递的那些不知道,所以采用剩余参数
* @return {object} obj
*/
function mynew(constructor, ...data) {
// 1.先创建一个空对象
let obj = {};
// 2.改变当前的this指向,把this指向指向到空对象
constructor.call(obj, ...data);
// 3.把当前的constructor原型赋值给obj原型
obj.__proto__ = constructor.prototype;
// 4.返回
return obj;
};
// 使用
let tab = mynew(Fn);
tab.age();
tab.hello();
</script>
构造函数继承
其实在构造函数中还有一个更为重要的东西,就是构造函数继承,如果一个构造函数需要使用到另一个构造函数上的属性和方法,重新编写,少的属性还好,但是如果属性一多,修改就会增加我们的工作量,这个时候我们可以使用到继承。
构造函数的方法,写在构造函数内部,每次new都会在内存中开辟出一块内存存放该方法,我们直接把他挂载到原型上,这样只会开辟出一块内存,实现多实例对象共用一块,极大的减少了内存的消耗
构造函数属性的继承
// 父
function Dod(name, age) {
// 这些带this的都会变成实例化对象
this.name = name;
this.age = age;
// 这么做虽然会继承方法,但是极其消耗性能,每一次调用都会创建一个内存来执行方法(在堆区存储)
this.num = function() {
console.log(121);
}
}
Dod.prototype.hobby = function() {
alert(`我叫${this.name},今年${this.age}`);
}
// 子
function Son(name, age) {
// ES5中三种方法继承其他的构造函数内容,但是继承不了prototype上的方法
Dod.call(this, name, age);
// Dod.apply(this,[name,age]);
// Dod.bind(this)(name,age);
}
let obj1 = new Dod('若水', '20');
let obj2 = new Son('biubiubiu', '99');
// 调用构造函数方法
obj1.hobby();
console.log(obj2, obj1);
// 给构造函数添加默认属性
Dod.height = '175cm';
// 使用
console.log(Dod.height);
上面这种方法只能继承属性不能继承原型上的方法,下面我们来讲如何实现方法的继承
构造函数方法的继承
// 父
function Dad(name, age) {
this.name = name;
this.age = age;
}
Dad.prototype.hobby = function () {
alert(`我今年${this.age}`);
};
// 子
function Son(name, age) {
// 继承父级
Dad.call(this, name, age);
};
Son.prototype = Dad.prototype;
console.log(Dad.prototype == Son.prototype);
// 创建方法
let obj = new Dad('啾啾', '23');
let obj1 = new Son('若水', '22');
// 修改Son的原型方法
Son.prototype.hobby = () => {
alert(`啦啦啦,我修改了,你抓不到我呀!`);
};
// 使用 这时会发现他们俩个弹出的是一样的,因为共用了一个原型链的原因
obj.hobby();
obj1.hobby();
上面子的构造函数继承父级的prototype上面的方法,也就是说子的构造函数的原型等于父级的原型,但是这样会引发一个问题,就是你修改子的方法,父级方法也会修改,因为他们俩个现在已经共用了一个原型,下面就讲如何解决
// 父
function Dad(name, age) {
this.name = name;
this.age = age;
}
Dad.prototype.hobby = function () {
alert(`我今年${this.age}`);
};
// 子
function Son(name, age) {
// 继承父级
Dad.call(this, name, age);
};
// 共用原型链会出现问题,所以这里使用了一个解决方法,也叫组合继承
// 组合继承
// 第一步:创建一个中转站,然后用于父和子之间的原型链中转,切断子类和父类的原型联系
function Link() { };
// 第二步:把当前父的构造函数原型赋值给Link原型
Link.prototype = Dad.prototype;
// 第三步:创建一个实例化对象赋值给当前的子构造函数原型
/* 原理: 实例化后的对象会新开辟一个内存地址,切断实例化对象和构造函数的联系*/
Son.prototype = new Link();
// 第四步:prototype原型上有一个预定义属性constructor,constructor属性用于返回创建该对象的函数,也就是我们常说的构造函数,我们把子的prototype指向了创建的构造函数为Link的函数,我们找不到的属性都会去Link原型上找,现在我们的子prototype的constructor是Link,所以我们要更改过来
Son.prototype.constructor = Son;
// 创建方法
let obj = new Dad('啾啾', '23');
let obj1 = new Son('若水', '22');
console.log(obj);
// 利用中转的方法进行继承,在次修改无问题
Son.prototype.hobby = () => {
alert(`啦啦啦,我修改了,你抓不到我呀!`);
};
// 使用 完美
obj.hobby();
obj1.hobby();
上述我们利用了构造函数的特性实现了组合继承,完美解决原型链共用的问题
坚持努力,无惧未来!