new
new被调用后做了三件事情:
- 让实例可以访问到私有属性
- 让实例可以访问构造函数原型(constructor.prototype)所在原型链上的属性
- 如果构造函数返回的结果不是引用数据类型,返回实例(一般构造函数都没有return,所以都是返回实例)
/**
*
* 使用new调用构造函数会经历如下过程:
* 1. 创建一个对象
* 2. 将构造函数的作用域赋给新对象
* 3. 执行构造函数中的代码
* 4. 返回新对象
*
* @returns
*/
function myNew() {
let obj = new Object(); // 寄生对象
let Constructor = Array.prototype.shift.call(arguments); // 第一项参数,即构造函数
obj.__proto__ = Constructor.prototype; // 核心代码,确定实例与构造函数的关系,这样obj就可以访问到构造函数原型中的属性
let res = Constructor.apply(obj, arguments); // 执行构造函数中的内容,返回res。注意在这个地方将this绑定到了obj上
return typeof res === 'object' ? res : obj; // 如果返回结果不是对象,就返回一个空对象
}
function Person(name, age) {
this.name = name;
this.age = age;
}
let person = myNew(Person, '张三', 15);
call和apply
/**
* 实现整体步骤:
* 1. 将函数设为对象的属性
* 2. 执行该函数
* 3. 删除该函数
*
* 另外:
* 1. apply可以接收数组(或类数组对象)作为第二个参数,...会将数组展开传给要执行的函数
* 2. 当this为null时,默认为window
* 3. 返回拥有指定this和参数的函数的执行结果
*/
Function.prototype.call = function (context, ...args) {
var context = context || window; // 传入绑定的上下文对象
context.fn = this; // this 指向函数绑定的对象,函数对象也是对象,这里 this 就是调用时的函数
var result = eval('context.fn(...args)'); //eval() 函数会将传入的字符串当做 JS 代码进行执行,result 接收结果并返回
delete context.fn // 为了不影响绑定的对象
return result;
}
bind
bind 和 call/apply 的区别在于:
- call/apply 是调用函数,函数立即执行,返回结果;bind 不调用函数,返回函数
- 参数不同,bind 只有 context 一个对象参数,call 和 apply 除了需要对象,还要传入函数的参数(因为要立即执行),apply 传数组
bind 注意点:
- 对于普通函数,绑定this指向
- 对于构造函数,要保证原函数的原型对象上的属性不能丢失
- bind()创建的函数没有prototype ( 箭头函数也没有)
/**
* 特点:
* 1. 返回一个函数
* 2. 可以传入参数
* 3. 执行返回的函数时依然能传入参数
* 4. bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效
*/
Function.prototype.bind = function (context, ...args) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this;
var fbound = function () {
// 绑定 this 指向
// 当作为构造函数时,this 指向实例,self 指向绑定函数,因为下面一句 `fbound.prototype = this.prototype;`,已经修改了 fbound.prototype 为 绑定函数的 prototype,此时结果为 true,当结果为 true 的时候,this 指向实例。
// 当作为普通函数时,this 指向 window,self 指向绑定函数,此时结果为 false,当结果为 false 的时候,this 指向绑定的 context。
self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)));
// Array.prototype.slice.call() 将具有length属性的对象转成数组, ES6 可以用 ... 或 Array.from()
}
fbound = Object.create(this.prototype); // create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__,保证原函数的原型对象上的属性不丢失
return fbound;
}
原型链继承
/**
* es5继承 Student继承Person 组合继承
* 父类构造函数会执行两次,如果构造函数比较复杂就造成浪费
*/
function Person(name, gender) {
this.name = name;
this.gender = gender;
}
Person.prototype.sayName = function () {
console.log(this.name)
}
function Student(name, gender, grade) {
// 执行一次父类
Person.call(this, name, gender);
this.grade = grade;
}
Student.prototype = new Person(); // 又执行一次
Student.prototype.constructor = Student;
const student = new Student('张三', '男', '大三');
student.sayName();
console.log(student);
ES6 继承
/**
* es6继承 Student继承Person
*/
class Person {
constructor(name, gender) {
this.name = name;
this.gender = gender;
}
sayName() {
console.log(this.name);
}
}
class Student extends Person {
constructor(name, gender, grade) {
super(name, gender);
super.test = 1; // 当不调用super时,super为this,最终test在Student上
this.grade = grade;
}
}
const student = new Student('张三', '男', '大三');
student.sayName();
console.log(student);