11、重写bind
<script>
// 再看之前,需要理解一个东西,就是bind到底干了什么,他和apply的区别是什么
// 懂了这些后,就可以理清基本的函数实现逻辑勒
// 首先,bind返回的是一个函数,这个函数执行的时候要实现改变this指向的效果,也要传参,而这个效果apply刚好可以实现
// 也就是说,bind实际上返回了一个函数,这个函数里是用say调用了apply方法
Function.prototype.myBind = function (obj) {
debugger
let self = this;
// 这里很有意思。因为arguments是类数组,无法使用数组的方法,所以用数组原型的的slice再点call
let args = Array.prototype.slice.call(arguments, 1);
let fNOP = function () { };
let fBound = function () {
// 这里是在干嘛知道么,这就是叭arguments类数组转换成数组,slice不加其他参数就是不做增删
// 注意这里的arguments是fBound的形参
let bindArgs = Array.prototype.slice.call(arguments);
// 在手写的 bind 函数实现中,this instanceof fNOP ? this : obj 这部分代码是非常关键的,它确保了 bind 函数的正确行为,特别是在涉及到 new 操作符创建新实例时。
// this instanceof fNOP 检查当前函数(经过 bind 包装后的函数)是否是作为构造函数被调用(即通过 new 关键字)。这是通过判断上下文 this 是否是 fNOP 的一个实例来检测的。
// 如果上述条件为真(即使用 new 操作符调用),则 this 应指向新创建的对象,即当前函数应用于新对象。
// 如果条件为假(即不是通过 new 调用),则 this 应指向传给 bind 的对象 obj。
// 这么做的目的是判断函数是通过 bind 返回的绑定函数通常调用,还是被用作构造函数创建新对象。这个判断保证了当绑定函数作为构造器使用时,绑定的 this 对象不会错误地指向原先指定的 obj,而是新创建的对象实例。
// 这里还是要return的,是两层函数的嵌套,say方法如果带了返回值的话,是出不来的,这里还要return一下才
return self.apply(this instanceof fNOP ? this : obj, args.concat(bindArgs));
}
// 这里我的猜测是say方法可能是构造方法,它会执行内部的constructor语句
// 所以这里的目的是拿一个空函数,但是把this也就是say这个函数的原型给到空函数
// 这样的话我们在new的时候不会执行say的内部代码,但是可以继承say的原型链
// 这里估计和寄生组合式继承有关系,注意看
// 20240417再看明白了创建空函数的意义是什么,是为了实现原型链继承,而不是实例继承
// 这步操作将fBound(绑定的函数)的原型设为fNOP的实例。该实例自然继承自fNOP.prototype,即原始函数的原型。这样,通过new fBound()创建的任何新对象不仅会继承从fBound传来的属性和方法,也会继承原函数的原型链上的属性和方法。
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
const say = function (obj, arr) {
console.log(this)
console.log(obj, arr)
return 'this is return!'
}
say.myBind({ name: 'yuan' }, 4, 6)(7,8)
//const fn = new (say.myBind({ name: 'yuan' }, 4, 6))(7, 8);
// 这样的话我们在实例化对象的是就能继承say函数以及myBind函数的所有实例属性和方法了
</script>
12、原型链
<script>
// 1、prototype和constructor
// prototype指向函数的原型对象(这是一个默认的Object空对象),这是一个显式原型属性,只有函数(对象也是函数)才拥有该属性。constructor指向原型对象的构造函数看插图1。
// 可以思考一下的打印结果,它们分别指向谁,结果看插图2
function Foo() { };
console.log(Foo.prototype);
console.log(Foo.prototype.constructor);
console.log(Foo.__proto__);
console.log(Foo.prototype.__proto__);
// 2、__proto__
// 每个对象都有_proto_,它是隐式原型属性,指向了创建该对象的构造函数原型。由于js中是没有类的概念,而为了实现继承,
// 通过 _proto_ 将对象和原型联系起来组成原型链,就可以让对象访问到不属于自己的属性。值得注意的是Foo、Function和
// Object都是函数,都是由顶层构造函数Function函数创建的,所以它们的_proto_都指向Function.prototype。看插图3
// 3、原型链图:插图4
// 4、终极原型链图:插图5
// 这里需要注意的是,所有的函数都是Function创建的出来的,所有的原型对象都是Object实例化来的;
// Function函数自己创建了自己,Function创建了Object对象
// 4、对象在寻找字段或者方法的时候,首先会从自身找,自身找不到就会顺着隐式原型链去找
</script>
13、构造函数继承
<script>
// 这里Animal就是构造函数的形式
function Animal(name) {
this.name = name;
this.getName = function () {
return this.name;
}
}
function Dog(name) {
Animal.call(this, name)
}
Dog.prototype = new Animal();
debugger
let dog1 = new Dog('jjwu');
console.log(dog1);
let dog2 = new Dog('hx');
console.log(dog2);
// 注意:借用构造函数实现继承解决了原型链继承的2个问题:引用类型共享问
// 题以及传参问题。但是由于方法必须定义在构造函数中,所以会导致每次创建子类
// 实例都会创建一遍方法。
</script>
14、组合继承
<script>
function Animal(name) {
this.name = name;
this.colors = ['black', 'white'];
}
Animal.prototype.getName = function () {
return this.name;
}
function Dog(name, age) {
Animal.call(this, name);
this.age = age;
}
Dog.prototype = new Animal();
// 这里我们可以打印一下Dog.prototype.constructor,看看它到底指向谁,我们发现它指向了Animal构造函数,我当时很奇怪。后来才知道,原来实例化对象本身没有constructor属性,也就是说Dog.prototype没有constructor属性,实际上Dog.prototype.constructor是通过Dog.prototype.__proto__访问到Animal.prototype属性,这个属性里是有的,也能解释为啥它是指向Animal构造函数本身的
Dog.prototype.constructor = Dog;
let dog1 = new Dog('奶昔', 2);
debugger
dog1.colors.push('brown');
let dog2 = new Dog('哈赤', 1);
debugger
console.log(dog2);
// 组合继承结合了原型链和调用构造函数,将两者的优点集中了起来。基本的思路是使用原型
// 链继承原型上的属性和方法,而通过调用构造函数继承实例属性。这样既可以把方法定义在原
// 型上以实现重用,又可以让每个实例都有自己的属性。
// 注意:组合继承已经相对完善了,但还是存在问题,它的问题就是调用了 2 次父类构造函数,
// 第一次是在 new Animal(),第二次是在 Animal.call() 这里,这个问题会在寄生组合式
// 继承里解决。
</script>
15、class实现继承
<script>
class Animal {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
class Dog extends Animal {
constructor(name, age) {
super(name)
this.age = age
}
}
const dog1 = new Dog('jjwu1',18);
const dog2 = new Dog('jjwu2',20);
debugger
// 这里解释一下什么是class
// 1.定义一个类
class Person {
//静态属性,使用关键字修饰,是私有属性 并不是公共属性
//可以是值类型,也可以是引用数据类型
static weight = '50KG';
// 2.constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
// 一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
// 构造器,里面声明实例属性
constructor(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
// 3.实例方法,不需要任何关键字修饰
// 其实是存在于Person.prototype中,可供所有的实例调用。
sayName() {
console.log('my name is', this.name);
}
// 4.通过static关键字来定义静态属性和静态方法。也可以在外侧添加静态属性;
// 静态属性和静态方法是定义在类【构造函数】上的,所以可以通过类【构造函数】直接访问。
// 在静态方法中,this指向当前类【构造函数】
static sayWeight() {
console.log(this.weight);
}
}
//实例化
let tom = new Person('tom', 20, '男')
console.log(tom);
tom.sayName();
// 通过class类来调用静态属性和方法
console.log(Person.weight);
Person.sayWeight();//静态方法由类去调用
// 注意:静态方法可以由类名调用或者创建实例调用(不推荐)
</script>