JS对象模型
JS对象模型
JavaScript是一种基于原型(prototype)的面向对象语言,而不是基于类的面向对象语言。他基于原型、原型链来实现面向对象。
C++、Java有类Class和实例Instance的概念,类是一类事物的抽象,而实例则是类的实体。
定义类
字面式声明方式
var obj = { property_1: value_1, // property_# 可以是一个标识符...
2: value_2, // 或一个数字...
["property" +3]: value_3, // 或一个可计算的key名...
// ...,
"property n": value_n }; // 或一个字符串
let a = 1;
let b = 'xyz';
let c = [1,2,3];
let d = x => x + 1;
var obj = {
'a':a, // 引号不省略明确使用该字符串为属性名
b:b, // 引号可以省,但依然转换为字符串作为属性名
[b]:100, // 计算b的值然后在转换为字符串作为属性名
1:200, // 将1转换为字符串
c, // c作为属性名,值为数组[1,2,3]
d // d作为属性名,值为函数
}
console.log(obj);
console.log(obj.d(1000))
for (let k in obj) {
console.log(typeof k, k, obj[k])
}
这种方法也称作字面值创建对象。Js 1.2开始支持。
对象的键key只能是字符串类型,最后都会被转换成字符串。
ES6之前——构造器
- 定义一个函数(构造器)对象,函数名首字母大写。
- 使用this定义属性
- 使用new和构造器创建一个新对象
// 定义类,构造器
function Point(x, y) {
this.x = x;
this.y = y;
this.show = () => {console.log(this,this.x,this.y)};
console.log('Point~~~~~~~~');
}
console.log(Point);
var p1 = new Point(4, 5); // 千万记得new
console.log(p1);
console.log('------------------------');
// 继承
function Point3D(x,y,z) {
Point.call(this,x,y); // "继承"
this.z = z;
console.log('Point3D~~~~~~~~');
}
console.log(Point3D);
var p2 = new Point3D(14,15,16);
console.log(p2);
p2.show();
new构建一个新的通用对象,new操作符会将新对象的this值传递给Point3D构造器函数,函数为这个对象创建z属性。
从上句话知道,new后得到一个对象,使用这个对象的this来调用构造器,那么如果执行”基类”的构造器方法呢?
使用Point3D的this来执行Point对象的构造器,所以使用call方法,传入子类的this
最终,构造完成,将对象赋给P2
注意:如果不使用new关键字,就是一次普通的函数调用,this不代表实例。
ES6中的class
从ES6开始,新提供了class关键字,使得创建对象更加简单、清晰
- 类定义使用class关键字。创建的本质上还是函数,是一个特殊的函数。
- 一个类只能拥有一个名叫constructor的构造器方法。如果没有显式的定义一个构造方法,则会添加一个默认的constructor方法
- 继承使用extends关键字
- 一个构造器可以使用super关键字来调用一个父类的构造函数
- 类没有私有属性
// 基类定义
class Point {
constructor(x,y) /*构造器*/ {
this.x = x;
this.y = y;
}
show() /*方法*/ {
console.log(this,this.x,this.y);
}
}
let p1 = new Point(10,11)
p1.show()
// 继承
class Point3D extends Point {
constructor (x,y,z) {
super(x,y);
this.z = z;
}
}
let p2 = new Point3D(20,21,22);
p2.show()
重写方法
子类Point3D的show方法,需要重写
// 基类定义
class Point {
constructor(x,y) /*构造器*/ {
this.x = x;
this.y = y;
}
show() /*方法*/ {
console.log(this,this.x,this.y);
}
}
let p1 = new Point(10,11)
p1.show()
// 继承
class Point3D extends Point {
constructor (x,y,z) {
super(x,y);
this.z = z;
}
show(){ // 重写
console.log(this,this.x,this.y, this.z);
}
}
let p2 = new Point3D(20,21,22);
p2.show();
子类中直接重写父类的方法即可,如果需要使用父类的方法,使用super.method()的方式调用
使用箭头函数重写上面的方法
// 基类定义
// 基类定义
class Point {
constructor(x,y) /*构造器*/ {
this.x = x;
this.y = y;
//this.show = function () {console.log(this,this.x,this.y)};
this.show = () => console.log('Point');
}
}
// 继承
class Point3D extends Point {
constructor (x,y,z) {
super(x,y);
this.z = z;
this.show = () => console.log('Point3D');
}
}
let p2 = new Point3D(20,21,22);
p2.show(); // Point3D
从运行结果来看,箭头函数也支持子类的覆盖
// 基类定义
class Point {
constructor(x,y) /*构造器*/ {
this.x = x;
this.y = y;
this.show = () => console.log('Point');
}
// show() /*方法*/ {
// console.log(this,this.x,this.y);
// }
}
// 继承
class Point3D extends Point {
constructor (x,y,z) {
super(x,y);
this.z = z;
//this.show = () => console.log('Point3D');
}
show(){ // 重写
console.log('Point3D');
}
}
let p2 = new Point3D(20,21,22);
p2.show(); // Point
上例优先使用了父类的属性show
// 基类定义
class Point {
constructor(x,y) /*构造器*/ {
this.x = x;
this.y = y;
//this.show = () => console.log('Point');
}
show() /*方法*/ {
console.log(this,this.x,this.y);
}
}
// 继承
class Point3D extends Point {
constructor (x,y,z) {
super(x,y);
this.z = z;
this.show = () => console.log('Point3D');
}
}
let p2 = new Point3D(20,21,22);
p2.show(); // Point3D
优先使用了子类的属性
总结:
子类父类使用同一种方式定义属性或方法,子类覆盖父类的。
访问同名属性或方法时,优先使用属性。
静态属性和方法
在方法或属性钱加上static,就是静态方法了
class Add {
static mm = 100
constructor(x, y) {
this.x = x;
this.y = y;
}
static print(){
console.log(this.x); // ? this是什么
}
}
var add = new Add(40, 50);
console.log(Add);
Add.print();
console.log(Add.mm)
//add.print();
add.constructor.print(); // 实例可以通过constructor访问静态方法
静态方法中的this绑定的是Add类,而不是实例
注意:静态的概念和python的静态不同,相当于python的类变量
this 的问题
let tom = {
name: 'tom',
getName:function () {
console.log(this === globalThis, this.name);
}
}
tom.getName() // false tom
上例tom这个标识符指向一个字面定义的对象实例,this指向实例自身。
let jerry = {
name: 'jerry'
}
jerry.getName() // TypeError: jerry.getName is not a function
很显然getName不是jerry的属性或方法。那如果这样写呢?
let tom = {
name: 'tom',
getName:function () {
console.log(this === globalThis, this.name);
}
}
tom.getName() // false tom
let jerry = {
name: 'jerry',
getName:function () {
console.log(this === globalThis, this.name);
}
}
jerry.getName() // false jerry
上面代码很容易理解,tom和jerry各自调用自己的方法,用的自己的this,各自打印自己的名字
globalThis.name = 'magedu'
const getName = function () {
console.log(this === globalThis, this.name);
}
let tom = {
name: 'tom',
getName
}
let jerry = {
name: 'jerry',
getName
}
getName() // true magedu
tom.getName() // false tom
jerry.getName() // false jerry
从上面的代码运行结果说明什么?
function定义的方法,this和使用时对象有关,也就是动态绑定
globalThis.name = 'magedu'
const getName = function () {
console.log(1, this === globalThis, this.name);
return function () {
console.log(2, this === globalThis, this.name);
}
}
let tom = {
name: 'tom',
getName
}
let jerry = {
name: 'jerry',
getName
}
getName()() //true magedu true magedu
tom.getName()() // false tom true magedu
jerry.getName()() //false jerry true magedu
为什么 tom.getName()() 第2处this是globalThis,为什么?
因为 tom.getName() 是动态绑定了实例tom,但是返回了一个匿名函数,假设为fn,然后 fn() ,这就是普通函数调用,它的this就是globalThis。
想让它也返回实例的name怎么解决呢?
处理方法
1、显示传入
globalThis.name = 'magedu'
const getName = function () {
console.log(1, this === globalThis, this.name);
return function (that) {
console.log(2, this === globalThis, that.name);
}
}
let tom = {
name: 'tom',
getName
}
let jerry = {
name: 'jerry',
getName
}
tom.getName()(tom)
jerry.getName()(jerry)
主动传入对象,避开this问题
2、apply、call方法
globalThis.name = 'magedu'
const getName = function () {
console.log(1, this === globalThis, this.name);
return function (x,y) {
console.log(2, this === globalThis, this.name,x,y);
}
}
let tom = {
name: 'tom',
getName
}
let jerry = {
name: 'jerry',
getName
}
tom.getName().call(tom,3,4)
jerry.getName().apply(jerry,[4,5]) # apply 传其他参数需要使用数组
apply、call方法都是函数对象的方法,第一参数都是传入对象引入的。
apply传其他参数需要使用数组
call传其他参数需要使用可变参数收集
3、bind方法
globalThis.name = 'magedu'
const getName = function () {
console.log(1, this === globalThis, this.name);
return function (x,y) {
console.log(2, this === globalThis, this.name,x,y);
}
}
let tom = {
name: 'tom',
getName
}
let jerry = {
name: 'jerry',
getName
}
tom.getName().bind()(3,6) //注意,bind方法返回的是函数,不是函数返回值
bind方法返回的是函数,而不是函数返回值
箭头函数和this
// 下面代码在Chrome浏览器中运行,可以正常
globalThis.name = 'magedu'
const getName = () => {
console.log(1, this === globalThis, this.name);
}
let tom = {
name: 'tom',
getName
}
getName()
tom.getName()
//返回
1 true "magedu"
1 true "magedu"
返回的一样,this都是globalThis, tom.getName()
调用的方法是箭头函数,这里this就是globalThis。
箭头函数的this是创建时的作用域有关,这个函数对象创建时决定好了this是谁
上例中箭头函数在第4~6行被解释器扫描代码并创建的函数对象,创建时作用域正好是全局的环境,那么箭头函数中this就定死了就是globalThis了。除非你使用call、apply、bind修改this。
// 下面代码在Chrome浏览器中运行,可以正常
globalThis.name = 'magedu'
const getName = function () {
console.log(1, this === globalThis, this.name);
return () => {
console.log(2, this === globalThis, this.name);
}
}
let tom = {
name: 'tom',
getName
}
getName()()
tom.getName()() // 看到什么?
//返回
1 true magedu
2 true magedu
1 false tom
2 false tom
因为第6~8行是tom.getName()动态绑定的this为tom后return一个新建的箭头函数,这个函数创建时作用域中this正好是tom。
一句话,箭头函数的this是在函数对象创建的那一瞬间决定下来的。
原型
avaScript 常被描述为一种基于原型的语言 (prototype-based language) ——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。
准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非对象实例本身。
function Point(x,y) {
this.x = x;
this.y = y;
console.log(this);
}
let a = new Point(4, 5)
console.log(a.__proto__ === Point.prototype);
let b = new Point(10, 11)
console.log(b.__proto__ === Point.prototype);
console.log(a.x, a.y);
console.log(b.x, b.y);
console.log(a.z, b.z); //undefined
Point.prototype.z = 20
console.log(a.z,b.z);
console.log(a.__proto__.z); //可以访问到了
Point.prototype.z = 20 ,构造器的prototype属性上创建z,相当于a实例和b实例也可以都可以访问这个属性了。有点 是类的,就是大家的 意思。
这也是JavaScript中常用的使用原型给该类所有实例增加公共属性的方式。
使用原型链,沿着原型链搜索属性,完成了继承的作用