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();
上述我们利用了构造函数的特性实现了组合继承,完美解决原型链共用的问题
坚持努力,无惧未来!