对象是一个容器,封装了属性(property)和方法(method)
生成对象,需要一个模板.
JavaScript 语言使用构造函数(constructor)作为对象的模板。
所谓”构造函数”,就是专门用来生成实例对象的函数。
⚠️如果构造函数内部有return语句,而且return后面跟着一个对象,new命令会返回return语句指定的对象;否则,就会不管return语句,返回this对象。
JavaScript 引擎会先在内存里面,生成一个对象{ foo: 5 },然后把这个对象的内存地址赋值给变量obj。也就是说,变量obj是一个地址(reference)。后面如果要读取obj.foo,引擎先从obj拿到内存地址,然后再从该地址读出原始的对象,返回它的foo属性。
原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。
var obj = { foo: function () {} }; { foo: { [[value]]: 函数的地址 ... } } 引擎会将函数单独保存在内存中,然后再将函数的地址赋值给foo属性的value属性。 var f = function () { console.log(this.x); } var x = 1; var obj = { f: f, x: 2, }; // 单独执行 f() // 1 // obj 环境执行 obj.f() // 2 由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this就出现了。⚠️上面代码中,obj.foo就是一个值。JavaScript 引擎内部,obj和obj.foo储存在两个内存地址,称为地址一和地址二。obj.foo()这样调用时,是从地址一调用地址二,因此地址二的运行环境是地址一,this指向obj。但是,上面三种情况,都是直接取出地址二进行调用,这样的话,运行环境就是全局环境,因此this指向全局环境。
⚠️如果this所在的方法不在对象的第一层,这时this只是指向当前一层的对象,而不会继承更上面的层。
⚠️bind()方法每运行一次,就返回一个新函数。
//错误的写法 element.addEventListener('click', o.m.bind(o)); // ... element.removeEventListener('click', o.m.bind(o)); // 正确的写法 var listener = o.m.bind(o); element.addEventListener('click', listener); // ... element.removeEventListener('click', listener); //bind()可以结合call()方法使用 [1, 2, 3].slice(0, 1) // [1] // 等同于 :本质是在[1, 2, 3]上面调用Array.prototype.slice()方法 Array.prototype.slice.call([1, 2, 3], 0, 1) // [1] //等同于: call()实质上是调用Function.prototype.call()方法 var slice = Function.prototype.call.bind(Array.prototype.slice); slice([1, 2, 3], 0, 1) // [1]JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……
修改原型对象时,一般要同时修改constructor属性的指向。
类的数据类型就是函数
类的所有方法都定义在类的prototype属性上
⚠️目的:拦截该属性的存取行为
class MyClass { constructor() { // ... } get prop() { return 'getter'; } set prop(value) { console.log('setter: '+value); } } let inst = new MyClass(); inst.prop = 123; // setter: 123 inst.prop // 'getter'子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例。
// 父类 class Super { constructor(){ this.x = 1; this.y = 2; this.z = 100; this.xxx = function () { console.log('父类实例方法'); }; this.arr = [1,2,3]; } fff() { console.log(this); // 原型方法中的this 为 子类实例 console.log('父类共享方法'); }; static hhh() { console.log(this); // 静态方法中的this 为 子类 console.log('父类静态方法'); }; } /** Sub.prototype = { constructor: Sub toString() {}, }; */ // 子类 class Sub extends Super{ // 类似ES5 的构造函数Sub // 类似ES5 constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。 constructor(x, y) { super(); // 继承 父类的 实例属性和方法 ; 即 Super.prototype.constructor.call(this)。 // 实例属性 this.x = x; this.y = y; } // 实例属性 新写法 // 那么constructor方法还有什么用?1.调用super() 2.传入参数x,y ;新写法则无法接受 bar = 'hello'; // ES5 原型上的方法 sss() { console.log(this); // 原型方法中的this 为 实例 super.fff(); // 在子类普通方法中通过`super`调用父类的方法时,方法内部的`this`指向当前的子类实例。 console.log('子类共享方法'); } // 静态方法 static staticFun() { console.log(this === Sub); // 静态方法中的this 为 类 super.hhh(); // return 'hello'; } // 静态属性 static myStaticProp = 42; // 私有属性 #count = 0; // 私有方法 #sum() { return this.#count; } } const sub1 = new Sub(3,4); console.log(sub1); //{x: 3, y: 4, z: 100, #sum: ƒ, #count: 0,arr: (3) [1, 2, 3],bar: "hello"} console.log(sub1.z); sub1.xxx(); sub1.fff(); sub1.sss(); console.log(Sub.staticFun()); console.log(Sub.myStaticProp); Sub.hhh(); // 父类的静态方法,可以被子类继承。 const sub2 = new Sub(33,44); sub2.arr.push(55); console.log(sub1.arr); // [1,2,3] // 综上, 子类 继承了 父类的实例属性和方法 ,继承了 父类的共享属性和方法 ,还继承了 静态属性和方法 // 先将父类实例对象的属性和方法,加到`this`上面(所以必须先调用`super`方法),然后再用子类的构造函数修改`this`super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例,因此super()在这里相当于A.prototype.constructor.call(this)。
super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。 即ES5的Sub.prototype = Object.create(Super.prototype);
在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。实际上执行的是super.print.call(this)。
在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。
在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。
(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。
(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
class A { } class B { } // B 的实例继承 A 的实例 Object.setPrototypeOf(B.prototype, A.prototype); // B 继承 A 的静态属性 (比ES5多的,也是(1)的解释) Object.setPrototypeOf(B, A); const b = new B();⚠️这句话有意思:
子类(B)的原型对象(prototype属性)是父类的原型对象(prototype属性)的实例。
【子类(`B`)的原型对象(`prototype`属性)】是【父类的原型对象(`prototype`属性)的实例】。 B.prototype = Object.create(A.prototype); B.prototype.constructor = B // 等同于 子类(`B`)的原型对象(`prototype`属性)是 【父类的原型对象(`prototype`属性)】的实例。 B.prototype.__proto__ = A.prototype;