定义类
文章目录
字面式声明方式
- 语法
// key(property)不管是什么类型,最后都会强制类型转换为字符串;
// value可以为任意合法的数据类型、变量、表达式
var obj = {
property:value, // property 可以是一个 标识符
"property_1":value_1, // 或是一个 字符串
["property_2" + 2]:value_2.obj, // 或是一个 可计算的key名
3:value_3, // 或是一个 数字
property_4, // 或是一个 变量
} ;
- 举例
let a = 1;
let b = "xyz";
let c = [1,2,3];
let d = x => x + 1;
var obj = {
"a":a, // 引号不省略;明确使用该字符串为属性名
b:b, // 引号省略;强制类型转换为字符串作为属性名
1:200, // '1': // 数值强制类型转换为字符串作为属性名
[c.push(5)]:c, // 计算;数组push(5)且返回数组新的长度,强制类型转换为字符串作为属性名
[c.pop()]:c, // 计算,数组pop(5)且返回5,强制类型转换为字符串作为属性名
d, // 变量d作为属性名,值为变量d指向的对象
d:d(3), // 属性重名,后者覆盖前者
test: (x = 1,2,3,4), // 4,赋值表达式的值x = 逗号表达式的最后一个表达式的值4,
}
console.log(obj)
for (let key in obj){
console.log(key,typeof key) // key皆为string
}
ES6之前–构造器
- 语法
- 定义一个函数(构造器)对象,函数名首字母大写;
- 函数中,使用关键字this定义属性;
- 使用关键字new和构造器函数创建一个新对象;
- new构建一个对象,使用这个this(指向这个对象)调用构造器;
z注意:如果不使用new关键字,就是一次普通的函数调用,函数中的this不指向实例,而是指向global对象。
- 构造器举例
function Point(x,y){
this.x = x; // 此处的this,类似于python的类对象中self(指向类实例)
this.y = y;
this.show = () => {console.log(this,this.x,this.y)};
console.log("Point~~~~~~~~~")
return this.x + this.y // 返回值没用
}
console.log(Point); // [Function: Point],本质是一个函数
var p1 = new Point(4,5); // 必须使用关键字new创建新对象
console.log(p1); // Point { x: 4, y: 5, show: [Function] }
console.log('---'.repeat(15));
- 继承
- ES6之前,调用父类的call()方法,实现继承
- For a given function, creates a bound function that has the same body as the original function;
- 对于给定的函数,创建一个与此函数具有相同身体的绑定函数;
- 即父类调用call函数,使用父类的构造器为子类实例创建属性
- call(this: Function, thisArg: any, …argArray: any[]): any;
- 参数
- this,指向子类对象;注意:会将this的指向对象从global修正为子类对象;
- thisAry,传入单个父类构造器函数所需的实参;
- …argArray,可以传入多个父类构造器函数所需的实参
- Father.call(this,…argArray)
- 调用父类构造器,为this(此处为子类实例对象)创建属性;
- …argArray接收多个实参,传入Father构造器
- 参数
function Point(x,y){
this.x = x; // 此处的this,类似于python的类对象中self(指向类实例)
this.y = y;
this.show = () => {console.log(this,this.x,this.y)};
console.log("Point~~~~~~~~~")
}
function Point3D(x,y,z){
Point.call(this,x,y); // “继承”
this.z = z;
console.log("Point3D*********")
}
console.log(Point3D); // [Function: Point3D]
console.log(p2 = new Point3D(6,[3,4]));
console.log('---'.repeat(30))
console.log(p2);
p2.show();
ES6中的class
- 语法
-
类定于使用关键字class。创建的类,本质上还是函数,只不过是一个特殊的函数;
- 类属性定义:使用关键字this;class中this指向当前类实例;
- 类方法定义:不能使用关键字function 和 this,直接使用 函数名(){} 的的格式定义;否则抛异常SyntaxError;
-
一个类只能拥有一个名为constructor的构造器方法;
- 如果没有显示的定义一个构造器方法,则会添加一个默认的constructor方法;
- 使用关键字new创建实例的时候,会调用constructor,参考python类定义中的构造函数__init__;
-
继承使用关键字extends;语法为 class 子类 extends 父类;
- 关键字super
- super指向父类类对象;
- super(),调用父类构造函数;
- super.method(),调用父类show方法;
- 关键字super
子类的显示定义了constructor方法,必须调用父类的构造函数,否则抛异常;
注意事项
1. js中只有单继承
1. 子类必须调用父类的constructor方法
1. 必须使用关键字new创建类实例,否则抛异常
- 举例
class Point {
constructor(x,y){ // 构造器constructor,注意函数名前没有关键字function和this
this.x = x; // 关键字this
this.y = y;
}
show(){console.log(this,this.x,this.y)}
}
// let p1 = Point(3,4) // SyntaxError: Identifier 'p1' has already been declared
let p1 = new Point(3,4) // 关键字new
p1.show()
class Point3D extends Point{ // 关键字extends
constructor(x,y,z){ // 子类有构造器函数,必须调用父类的构造器方法
super(x,y); // 关键super(),调用父类构造器方法
this.z = z
}
}
p3d = new Point3D(3,4,5)
p3d.show()
- 重写方法
class Point {
constructor(x,y){
this.x = x;
this.y = y;
}
show(){console.log(this,this.x,this.y)}
}
class Point3D extends Point{
constructor(x,y,z){
super(x,y);
}
show(){ // 方法重写,overide
super.show(); // super代指类对象
console.log(this.z)
}
}
p3d = new Point3D(3,4,5)
p3d.show() // Point3D { x: 3, y: 4, a: 1, z: 5 } 3 4,Point3D类继承父类的show方法
- 箭头函数
- 类属性定义,可以使用箭头函数
- 类方法定义,不能使用箭头函数
- 继承
- 父类定义了类属性show,父类和子类调用show,均访问类属性show
- 父类未定义类属性show,子类优先使用自己的show方法
class Point {
constructor(x,y){
this.x = x;
this.y = y;
this.show = () => {console.log("Point")} // 注释这行代码,p3d.show()返回Point3D
}
show() {
console.log(this.x,this.y)
}
}
class Point3D extends Point{
constructor(x,y,z){
super(x,y);
// this.show() => {console.log(5)} // 放开这行代码,返回5
}
show(){
// super.show()
console.log("Point3D")
}
}
p3d = new Point3D(3,4,5)
p3d.show() // 皆返回Point
- 父类、子类使用同一种方式定义属性或者方法,子类覆盖父类;
注意:访问同名属性或方法时,访问优先级 子类属性 > 父类属性 > 子类方法 > 父类方法
静态属性
- 在类属性名前加上static,就是静态属性
- 静态属性目前还没有得到很好的支持
静态属性,就是类的;只能类对象访问,类实例不能访问
class Point {
static s = 100 // 定义静态方法
s = 100 // 等价在构造器中,this.s = 100
// let s = 100 SyntaxError
// const s = 100 SyntaxError
// var s = 100 SyntaxError
constructor(x,y){
let a = 100; // 仅仅在构造器中声明了一个本地局部变量
const b = 200; // 仅仅在构造器中声明了一个本地常量
var c = 300; // 仅仅在构造器中声明了一个本地全局常量
// d = 400; ReferenceError: d is not defined
this.x = x; // 实例创建属性x
this.y = y;
this.show = () => {console.log("Point")}
}
show() {
console.log(this.x,this.y)
}
}
p1 = new Point(2,3)
console.log(p1.s) // undefined
console.log(Point.s) // 100
静态方法
- 在类方法名前加上static,就是静态方法
静态方法,就是类的;只能类对象访问,类实例不能访问
class Point {
constructor(x,y){
this.x = x;
this.y = y;
}
// 静态方法中,不能引用this,使用原则同python
// 1. 实例不存在;
// 2. 就算实例存在,一个抽象的类对象,如何访问千千万万个实例:
// static show() {
// console.log(this.x,this.y) // undefined undefined
// }
static show() {
console.log("show")
return // 等价return undefined
}
}
p1 = new Point(2,3)
// console.log(p1.show) // p1不能访问静态方法show,即没有此属性,返回undefined
// p1.show() // 等价undefined(),抛TypeError: p1.show is not a function
console.log(Point.show(),'~~~~~~')
console.log(p1.constructor.show(),'*****')
类对象不能访问类实例对象的属性和方法
class Point {
constructor(x,y){
this.x = x;
this.y = y;
}
show() {
console.log("show")
return // 等价return undefined
}
}
p1 = new Point(2,3)
console.log(Point.x) // undefined
console.log(Point.show) // undefined
Point.show() // 等价undefined(),抛TypeError: Point.show is not a function
- 静态的概念与Python的静态完全不同,相当于Python中的类变量
注意:静态的就是类的,只能类对象访问;实例的就是实例的,只能实例对象访问。
this的坑
坑的来源
-
虽然js和C++、Java一样有this,但是JS的表现是不同的。
- C++、Java是静态编译型语言,this编译期绑定;
- JS是动态语言,this运行期绑定;
-
函数执行时,会开启新的 执行上下文环境ExecutionContext
- 函数中创建this属性,函数调用的方式不同,this指向的对象不同
普通函数方式调用,this指向全局对象;
- nodejs中,全局对象为global
- 浏览器中,全局对象为window
function myFn(){
this.show = () => {console.log(this)}
return show;
}
let f = myFn(1,2);
f(); // 指向Object [global]
对象方法的调用方式(关键字new创建新对象)/ 字面式声明方式
// 对象方法调用
function myFn(){
this.show = () => {console.log(this)}
return this.show
}
let f = new myFn(1,2) ; // new创建新对象
f(); // 指向myFn对象
// 字面式声明
let obj = {
name : 'Obj',
show : function (){console.log(this)}
}
console.log(obj)
obj.show() // 指向obj对象
- 嵌套测试
var school = { // 字面式声明,就式定义一个类objcet
name : 'JS', // 等价this.name = 'JS'
getNameFunc : function (){
console.log(this.name); // 此处的this指向school对象
console.log(this);
return function (){ // 临时生成一个函数对象
console.log(this == global); // 普通函数调用,this指向全局对象global
console.log(this);
return this.name;
}
}
}
console.log(school.getNameFunc())
console.log('~~~~~~~~~~~~~~~')
console.log(school.getNameFunc()())
// 运行结果
// JS
// { name: 'JS', getNameFunc: [Function: getNameFunc] }
// [Function]
// ~~~~~~~~~~~~~~~
// JS
// { name: 'JS', getNameFunc: [Function: getNameFunc] }
// true
// Object [global] {
// global: [Circular],
// clearInterval: [Function: clearInterval],
// clearTimeout: [Function: clearTimeout],
// setInterval: [Function: setInterval],
// setTimeout: [Function: setTimeout] { [Symbol(util.promisify.custom)]: [Function] },
// queueMicrotask: [Function: queueMicrotask],
// clearImmediate: [Function: clearImmediate],
// setImmediate: [Function: setImmediate] {
// [Symbol(util.promisify.custom)]: [Function]
// }
// }
// undefined
this坑的解决
1. 显示传入对象
2. ES3(ES-262第三版)引入call、apply方法
-
apply、call方法都是函数对象的方法;注意:是函数对象方法
- call(this: Function, thisArg, …argArray): any;
- 第一个参数传入对象引用,修正this的指向对象
- 其他参数,使用可变参数收集
- apply(this: Function, thisArg, argArray): any;
- 第一个参数传入对象引用,修正this的指向对象
- 其他参数,需要使用数组
- call(this: Function, thisArg, …argArray): any;
-
举例
var school = {
name : 'JS',
getNameFunc : function (){
return function (x,y){ // 临时生成一个函数对象
return x+y
}
}
}
console.log(school.getNameFunc().call(school,3,4)) // 可变参数收集
console.log(school.getNameFunc().apply(school,[3,4])) // 数组
- 应用
function Point(x,y){
this.x = x;
this.y = y;
console.log(this == global);
console.log("Point ~~~~~");
return 100
}
var p1 = Point(4,5);
console.log(p1);
console.log('~~~'.repeat(15));
var p2 = new Object();
var p3 = Point.call(p2,10,10); // this指向了p2,调用Point函数,动态为p2对象注入属性x,y;而p3拿到函数的返回值
console.log(p3); // 100
console.log(p2); // { x: 10, y: 10 }
3. ES5引入bind方法 – 最常用
- bind方法,先为函数绑定this,生成新的函数对象,然后调用时直接用
- // 类似于Python中的偏函数partial
var school = {
name : 'JS',
getNameFunc : function (){
console.log(this.name);
console.log(this);
return function (x,y){
console.log(this == global);
console.log(this);
return x+y
}
}
}
console.log(school.getNameFunc().bind(school)) // 生成一个新函数对象
console.log(school.getNameFunc().bind(school)(5,5)) // 需要调用
ES6引入支持this的箭头函数
- ES6新技术,不需要兼容this问题
- ES6之前的定义方法
var school = {
name : 'JS',
getNameFunc : function (){
console.log(this.name);
console.log(this);
return (x,y) => { // 箭头函数
console.log(this == global);
console.log(this);
return x+y
}
}
}
console.log(school.getNameFunc()(3,4)
- ES6 新的定义方法
class school {
constructor (){
this.name = 'JS'
}
getNameFunc (){
console.log(this.name);
console.log(this);
return (x,y) => { // 箭头函数
console.log(this == global);
console.log(this);
return x+y
}
}
}
console.log(new school().getNameFunc()(3,4))
- ES6 新的定义方法
class school {
constructor (){
this.name = 'JS'
}
getNameFunc (){
console.log(this.name);
console.log(this);
return (x,y) => { // 箭头函数
console.log(this == global);
console.log(this);
return x+y
}
}
}
console.log(new school().getNameFunc()(3,4))