JS对象模型
JavaScript 是一种基于原型(Prototype)的面向对象语言,而不是基于类的面向对象语言。
C++、Java有类Class和实例Instance的概念,类是一类事物的抽象,而实例则是类的实体。
JS是基于原型的语言,它只有原型对象的概念。原型对象就是一个模板,新的对象从这个模板构建从而获取最初的属性。任何对象在运行时可以动态的增加属性。而且,任何一个对象都可以作为另一个对象的原型,这样后者就可以共享前者的属性
定义类
字面式声明方式
var obj = {
x: 1,
1: 'abc',
"y": '123'
}
for (let x in obj)
console.log(x, typeof x);
/*运行结果:
1 string
x string
y string
*/
这种方法称作字面值创建对象
let a = 1, b = 2
let obj = {a, b}
let obj1 = {a: a, 'b': b} // 引号可以省去
console.log(obj)
console.log(obj1)
let c = 'str1'
let d = {
c: 100, // 这个c是字符串'c', 不是上面的变量c
[c]: 200 // 如果使用c变量, 就要用中括号括起来
}
console.log(d)
/*运行结果:
{ a: 1, b: 2 }
{ a: 1, b: 2 }
{ c: 100, str1: 200 }
*/
对象的键key只能是字符串类型, 如果使用变量, 用中括号[]
括起来
ES6之前的构造器
1、定义一个函数(构造器)对象,函数名首字母大写
2、使用this定义属性
3、使用new和构造器创建一个新对象
function Point(x, y) {
this.x = x;
this.y = y;
this.show = () => console.log(this.x, this.y);
console.log('Point ----')
}
p1 = new Point(3, 4)
console.log(p1)
console.log('--'.repeat(20))
function PointChild(x, y, z) {
Point.call(this, x, y) // 继承
this.z = z
console.log('PointChild ---')
}
p2 = new PointChild(4, 5, 6)
console.log(p2)
p2.show()
/*运行结果:
Point ----
Point { x: 3, y: 4, show: [Function] }
----------------------------------------
Point ----
PointChild ---
PointChild { x: 4, y: 5, show: [Function], z: 6 }
4 5
*/
构建一个新的对象必须用new来构建, 类中调用基类使用call方法, 可以传入子类的this
注意:如果不使用new关键字,就是一次普通的函数调用,this不代表实例
ES6中的class
从ES6开始,新提供了class关键字,使得创建对象更加简单、清晰。
1、类定义使用class关键字。创建的本质上还是函数,是一个特殊的函数
2、一个类只能拥有一个名为constructor的构造器方法。如果没有显式的定义一个构造方法,则会添加一个默认的constuctor方法。
3、继承使用extends关键字
4、一个构造器可以使用super关键字来调用一个父类的构造函数
5、类没有私有属性
class Point {
constructor(x, y){ // 构造器
this.x = x;
this.y = y;
}
show() { //方法
console.log(this, this.x, this.y);
}
}
p1 = new Point(2,3);
console.log(p1);
p1.show(); // this就是p1代表实例本身, 类似python的self
//继承
class PointChild extends Point {
constructor(x, y, z) {
super(x, y); //调用基类super
this.z = z;
}
}
p2 = new PointChild(4, 6, 8);
console.log(p2);
p2.show();
/*运行结果:
Point { x: 2, y: 3 }
Point { x: 2, y: 3 } 2 3
PointChild { x: 4, y: 6, z: 8 }
PointChild { x: 4, y: 6, z: 8 } 4 6
*/
使用new来构造对象, 继承类时, 在属性未定义前一定要用super方法调用基类
重写方法
子类PointChild的show方法需要重写
class Point {
constructor(x, y){ // 构造器
this.x = x;
this.y = y;
}
show() { //方法
console.log(this, this.x, this.y);
}
}
p1 = new Point(2,3)
console.log(p1)
p1.show() // this就是p1代表实例本身, 类似python的self
//继承
class PointChild extends Point {
constructor(x, y, z) {
super(x, y); //调用基类super
this.z = z;
}
show() { // 重写方法
console.log(this, this.x, this.y, this.z);
}
}
p2 = new PointChild(4, 6, 8)
console.log(p2)
p2.show()
/*运行结果:
Point { x: 2, y: 3 }
Point { x: 2, y: 3 } 2 3
PointChild { x: 4, y: 6, z: 8 }
PointChild { x: 4, y: 6, z: 8 } 4 6 8
*/
子类中直接重写父类的方法即可。
如果需要使用父类的方法,使用super.method()的 方式调用
还可以使用箭头函数定义方法:
class Point {
constructor(x, y){ // 构造器
this.x = x;
this.y = y;
this.show = () => console.log('Point ---'); // 箭头函数定义方法
}
}
p1 = new Point(2,3)
p1.show() // this就是p1代表实例本身, 类似python的self
//继承
class PointChild extends Point {
constructor(x, y, z) {
super(x, y); //调用基类super
this.z = z;
this.show = () => console.log('PointChild ---');
}
}
p2 = new PointChild(4, 6, 8)
p2.show()
/*运行结果:
Point ---
PointChild ---
*/
从运行结果来看, 箭头函数也支持子类覆盖基类方法
class Point {
constructor(x, y){ // 构造器
this.x = x;
this.y = y;
this.show = () => console.log('Point ---');
}
}
p1 = new Point(2,3)
p1.show() // this就是p1代表实例本身, 类似python的self
//继承
class PointChild extends Point {
constructor(x, y, z) {
super(x, y); //调用基类super
this.z = z;
}
show() { // 重写方法
console.log(this, this.x, this.y, this.z);
}
}
p2 = new PointChild(4, 6, 8)
p2.show()
/*运行结果:
Point ---
Point ---
*/
上例父类用箭头函数定义方法, 子类用普通函数定义方法, 从运行结果来看, 优先使用了父类的show方法
class Point {
constructor(x, y){ // 构造器
this.x = x;
this.y = y;
}
show() {
console.log(this, this.x, this.y);
}
}
p1 = new Point(2,3)
p1.show() // this就是p1代表实例本身, 类似python的self
//继承
class PointChild extends Point {
constructor(x, y, z) {
super(x, y); //调用基类super
this.z = z;
this.show = () => console.log('PointChild ---');
}
}
p2 = new PointChild(4, 6, 8)
p2.show()
/*运行结果:
Point ---
PointChild ---
*/
上例父类用普通函数定义方法, 子类用箭头函数定义方法, 从运行结果来看, 优先使用了子类的show方法
总结:
父类、子类使用同一种方式类定义属性或者方法,子类覆盖父类。
访问同名属性或方法时,优先使用属性
箭头函数定义在实例的属性里, 普通函数定义在类的方法中, 属性优先于方法
静态方法
在方法名前加上static,就是静态方法了
class Add {
constructor(x, y) {
this.x = x;
this.y = y;
}
static print() {
console.log('static method', this.x)
}
}
add = new Add(3, 5)
console.log(Add)
// add.print() // 实例不能访问直接访问静态方法,和C++、Java一致
Add.print()
add.constructor.print() // 实例可以通过constructor访问静态方法
/*运行结果:
[Function: Add]
static method undefined
static method undefined
*/
静态方法中的this是Add类,而不是Add的实例
注意:
静态的概念和Python的静态不同,相当于Python中的类变量
this的坑
虽然Js和 C++ 、Java一样有this,但是Js的表现是不同的。
原因在于, C++ 、Java是静态编译型语言,this是编译期绑定,而Js是动态语言,运行期绑定
var school = {
name: 'school',
getNameFunc:function () {
console.log(this.name);
console.log(this);
return function () {
console.log(this === global); // this是否是global对象
return this.name;
};
}
};
console.log(school.getNameFunc()());
/*运行结果:
school
{ name: 'school', getNameFunc: [Function: getNameFunc] }
true
undefined
*/
函数执行时,会开启新的执行上下文环境ExecutionContext。
创建this属性,但是this是什么就要看函数是怎么调用的了。
1、myFunction(1,2,3),普通函数调用方式,this指向全局对象。全局对象是nodejs的global或者浏览器中的window。
2、myObject.myFunction(1,2,3),对象方法的调用方式,this指向包含该方法的对象。
3、call、apply、bind方法调用时,要看第一个参数是谁。
分析上例:
school
和 { name: 'school', getNameFunc: [Function: getNameFunc] }
很好理解。
第三行打印的true,是 console.log(this === global)
执行的结果,说明当前是global,因为调用这个返回的函数
是直接调用的,这就是个普通函数调用,所以this是全局对象。
第四行undefined,就是因为this是global,没有name属性
显示传入
var school = {
name: 'school',
getNameFunc:function () {
console.log(this.name);
console.log(this);
return function (that) {
console.log(this === global); // this是否是global对象
return that.name;
};
}
};
console.log(school.getNameFunc()(school));
/*运行结果:
school
{ name: 'school', getNameFunc: [Function: getNameFunc] }
true
school
*/
通过主动传入对象,这样就避开了this的问题
apply、call方法
var school = {
name: 'school',
getNameFunc:function () {
console.log(this.name);
console.log(this);
return function () {
console.log(this === global); // this是否是global对象
return this.name;
};
}
};
console.log(school.getNameFunc().call(school)); // call方法显示传入this对应的对象
/*运行结果:
school
{ name: 'school', getNameFunc: [Function: getNameFunc] }
false
school
*/
apply、call方法都是函数对象的方法,第一参数都是传入对象引入的。
apply传其他参数需要使用数组
call传其他参数需要使用可变参数收集
function Point(x, y){
this.x = x;
this.y = y;
console.log(this === global);
console.log('Point ----');
}
p1 = Point(3, 4);
console.log(p1);
console.log('--'.repeat(20))
p2 = new Object();
console.log(p2);
p3 = Point.call(p2, 10, 11);
// p3 = Point.apply(p2, [10, 11]);
console.log(p3);
console.log(p2);
/*运行结果:
true
Point ----
undefined
----------------------------------------
{}
false
Point ----
undefined
{ x: 10, y: 11 }
*/
p1 = Point(3, 4);
和p3 = Point.call(p2, 10, 11);
没有用new方法构造对象, 只能算是普通函数调用, Point没有返回值, 所以p1和p3都是undefined
p3 = Point.call(p2, 10, 11);
通过call方法显示传入p2为函数的this对象, 函数内为该对象注入x,y属性.
bind方法
var school = {
name: 'school',
getNameFunc:function () {
console.log(this.name);
console.log(this);
return function () {
console.log(this === global); // this是否是global对象
return this.name;
};
}
};
var boundfunc = school.getNameFunc().bind(school); // bind方法绑定后返回新的函数
console.log(boundfunc);
console.log(boundfunc());
/*运行结果:
school
{ name: 'school', getNameFunc: [Function: getNameFunc] }
[Function: bound ]
false
school
*/
bind方法与apply、call方法不同, 是为函数先绑定this, 返回一个新函数, 调用时直接用.
apply、call方法都是调用时传入this.
ES6引入支持this的箭头函数
ES6 新技术,就不需要兼容this问题
var school = {
name: 'school',
getNameFunc:function () {
console.log(this.name);
console.log(this);
return () => {
console.log(this === global); // this是否是global对象
return this.name;
};
}
};
console.log(school.getNameFunc()());
/*运行结果:
school
{ name: 'school', getNameFunc: [Function: getNameFunc] }
false
school
*/