怎么建设一个网站,网上装修平台,公司后缀邮箱如何注册,一年级书签制作图片一、JavaScript中的类
1.什么是类 类描述了一种代码的组织结构形式#xff0c;不同的语言中对其实现形式各有差异。JavaScript中的类Class实际是一种描述对象之间引用关系的语法糖。 在Class语法糖出现之前#xff0c;我们想重用一个功能模块#xff0c;通常是用一个函数来…一、JavaScript中的类
1.什么是类 类描述了一种代码的组织结构形式不同的语言中对其实现形式各有差异。JavaScript中的类Class实际是一种描述对象之间引用关系的语法糖。 在Class语法糖出现之前我们想重用一个功能模块通常是用一个函数来进行封装
//声明一个函数用于功能的复用
function Animal(name) {this.name name;
}
//可以在函数的原型上进行属性的添加便于复用
Animal.prototype.walk function () {return 5555
};
//通过new操作符创建一个新的函数
//new的过程发生了什么
// 1.创建一个新的对象
// 2.将构造函数的作用域赋值给这个新对象从而this指向了这个新对象
// 3.执行构造函数中的代码为这个新对象添加属性
// 4.返回新对象
const dog new Animal(小狗);//
//通过new创建了一个新对象
console.log(dog);//Animal { name: 小狗 }
//改对象拥有其构造函数上的属性
console.log(dog.walk());//5555
//通过原型可以查看原型上绑定的属性当你获取一个属性而对象本身没有时会一层层的查找原型上是否存在该属性最后找到Object对象本身没有则返回undefined有则返回该属性
console.log(Animal.prototype);//Animal { walk: [Function] }
//引用对象的隐式原型指向构造函数的显示原型
console.log(dog.__proto__Animal.prototype);//true 为了更好的做成一个单独的模块还有使用IIFE立即执行函数形式来增强模块的
//立即执行函数
//立即执行函数模式是一种语法可以让你的函数在定义后立即被执行
//立即执行函数的组成定义一个函数将整个函数包裹在一对括号中将函数声明转换成表达式在函数尾部加上一对括号让函数立即被执行
//作用页面加载完成后只执行一次的设置函数将设置函数中的变量包裹在局部作用域中不会泄漏成全局变量
var Animal (function () {function Animal(name) {this.name name;console.log(this.name);}Animal.prototype.walk function (data) {console.log(data);};console.log(666);return Animal;
})();
Animal(我是name)
Animal.prototype.walk(执行方法)
//依次输出为
//666
//我是name
//执行方法 由于这是一种很常见的需求所以ECMAScript规范中加入了Class语法简化了之前的使用形式
//通过class关键字声明一个Animal类
//constructor构造函数用于将new的实例对象指回构造函数本身
class Animal {constructor(name) {this.name name;}walk() {console.log(执行了);}
}
//通过new关键字基于类生成实例对象
let dog new Animal(小狗)
console.log(Animal);//[Function: Animal]
console.log(dog);//Animal { name: 小狗 }
dog.walk()//执行了
2.静态属性 到目前为止我们只讨论了类的实例成员那些仅当类被实例化的时候才会被初始化的属性。 我们也可以创建类的静态成员这些属性存在于类本身上面而不是类的实例上。 在这个例子里我们使用 static定义 origin因为它是所有网格都会用到的属性。 每个实例想要访问这个属性的时候都要在 origin前面加上类名。 如同在实例属性上使用 this.前缀来访问属性一样这里我们使用 Grid.来访问静态属性。
class Grid {//用于定义静态属性static origin { x: 0, y: 0 };calculateDistanceFromOrigin(point) {let xDist (point.x - Grid.origin.x);let yDist (point.y - Grid.origin.y);console.log(this.scale);//声明实例对象时所传递的值return Math.sqrt(xDist * xDist yDist * yDist) / this.scale;}constructor(scale) {this.scale scale;}
}let grid1 new Grid(1.0); // 1x scale
let grid2 new Grid(5.0); // 5x scaleconsole.log(grid1.calculateDistanceFromOrigin({ x: 10, y: 10 }));
console.log(grid2.calculateDistanceFromOrigin({ x: 10, y: 10 }));
3.存取器 通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。 下面来看如何把一个简单的类改写成使用 get和 set。 首先我们从一个没有使用存取器的例子开始。
class Employee {fullName;
}let employee new Employee();
employee.fullName Bob Smith;
if (employee.fullName) {console.log(employee.fullName);
}我们可以随意的设置 fullName这是非常方便的但是这也可能会带来麻烦。 下面这个版本里我们先检查用户密码是否正确然后再允许其修改员工信息。 我们把对 fullName的直接访问改成了可以检查密码的 set方法。 我们也加了一个 get方法让上面的例子仍然可以工作。
let passcode secret passcode;class Employee {_fullName5;get fullName() {return this._fullName;}set fullName(newName) {if (passcode passcode secret passcode) {this._fullName newName;}else {console.log(验证失败);}}
}let employee new Employee();
employee.fullName Bob Smith;
if (employee.fullName) {console.log(employee.fullName);
}
4.继承与多态 使用extends关键字可以很方便的实现类之间的继承。
class Animal {move(distanceInMeters) {console.log(Animal moved ${distanceInMeters}m.);}
}class Dog extends Animal {bark() {console.log(Woof! Woof!);}
}const dog new Dog();
dog.bark();//Woof! Woof!
dog.move(10);//Animal moved 10m.
dog.bark();//Woof! Woof! 这个例子展示了最基本的继承类从基类中继承了属性和方法。 这里 Dog是一个 派生类它派生自 Animal 基类通过 extends关键字。 派生类通常被称作 子类基类通常被称作 超类。 因为 Dog继承了 Animal的功能因此我们可以创建一个 Dog的实例它能够 bark()和 move()。 下面我们来看个更加复杂的例子
//新建Animal类
class Animal {//用于将实例对象的this指向构造函数本身constructor(theName) { this.name theName; }move(distanceInMeters) {console.log(${this.name} moved ${distanceInMeters}m.);}
}
//新建Snake类继承于Animal类
class Snake extends Animal {//super关键字代表父类对象super(name)表示执行父类constructor(name) { super(name); }move(distanceInMeters 5) {console.log(Slithering...);//触发父类方法super.move(distanceInMeters);}
}
//新建Horse类继承于Animal类
class Horse extends Animal {//super关键字代表父类对象super(name)表示执行父类constructor(name) { super(name); }move(distanceInMeters 45) {console.log(Galloping...);//触发父类方法super.move(distanceInMeters);}
}
let sam new Snake(Sammy the Python);
let tom new Horse(Tommy the Palomino);
sam.move();
tom.move(34);
// 输出为
// Slithering...
// Sammy the Python moved 5m.
// Galloping...
// Tommy the Palomino moved 34m.这个例子展示了一些上面没有提到的特性。 这一次我们使用 extends关键字创建了 Animal的两个子类 Horse和 Snake。 与前一个例子的不同点是派生类包含了一个构造函数它 必须调用 super()它会执行基类的构造函数。 而且可以用使用super关键字调用Animal中的方法。 这个例子演示了如何在子类里可以重写父类的方法。 Snake类和 Horse类都创建了 move方法它们重写了从 Animal继承来的 move方法使得 move方法根据不同的类而具有不同的功能。 注意即使 tom被声明为 Animal类型但因为它的值是 Horse调用 tom.move(34)时它会调用 Horse里重写的方法。在继承链的不同层次中move方法被多次定义当调用方法时会自动选择合适的定义这就是多态的一种实现。
5.类的本质 文章的开头说过Class只是一种描述对象之间引用关系的语法糖。现在我们就来看看语法糖背后的本质是什么。
class Animal {constructor(name) {this.name name;}print() {console.log(this.name);}
}const animal new Animal(dog);
animal.print(); // dog 这里我们创建了一个Animal的实例可以看到实例可以使用print中的方法。实现这一切的前提需要搞清楚new操作符到底做了什么下面是一个new操作符的模拟实现
//模拟实现new操作符
//创建一个Person函数声明属性与方法
function Person(name) {this.name name;
}
Person.prototype.sayName function () {console.log(this.name);
}//创建一个createPerson函数用于模拟new Person实现逻辑
function createPerson() {// 1 创建一个新的对象var o {};// 2 获取构造函数以便实现作用域的绑定//arguments的作用当在js中调用一个函数的时候我们经常会给这个函数传递一些参数js把传入到这个函数的全部参数存储在arguments中。// Arguments是一个类数组对象对象中的每一项分别对应传入的参数console.log(arguments);//[Arguments] { 0: [Function: Person], 1: ydb }//因为arguments不是数组对象,是一个类数组对象所以不能调用数组的方法比如prototype属性不能使用console.log(arguments.prototype);//undefined//所以为了实现后续new实现对象之前的相关绑定需要使用prototype进行绑定所以需要将arguments转换成数组对象// console.log([].shift.call(arguments));//[Function: Person]//通过[].shift.call(arguments)将arguments从类数组对象转成数组对象//原理: //[].slice.call( arguments )相当于Array.prototype.slice.call( arguments )//在这里slice()是数组上的一个方法它不改变原数组而是从原数组中返回指定的元素也就是一个新的数组当没有传入任何参数的时候也就是会返回整个数组复制原数组不改变原数组然后是 call 函数它和 apply 函数一样他们两个都是改变函数的 this 指向区别就是参数不一样他们的第一个参数都是一个对象或者是 “this”而 apply 的第二个参数是一个数组call 后面可以继续跟多个参数也就是 apply ( this[ ] )call ( this , n1n2n3....)// 所以重点就是// 因为slice内部实现是使用的this为代表调用对象那么当[ ].slice.call() 传入 arguments 对象的时候通过 call 函数改变原来 slice 方法的 this 指向, 使其指向 arguments并对 arguments 进行复制操作然后返回一个新数组。所以就达到了把 arguments 类数组转为数组的目的// 当然[ ].shift.call( arguments ) 也是如此shift () 方法为删除数组的第一项并返回删除项所以这句我们可以理解为 “ 删除arguments的第一项并返回也就是拿到 arguments 的第一项 ”。var _constructor [].shift.call(arguments);//获取到传入的Person函数console.log(_constructor);//[Function: Person]// 3 由于通过new操作创建的对象实例内部的不可访问的属性[[Prototype]](有些浏览器里面为__proto__)//指向的是构造函数的原型对象的所以这里实现手动绑定。// 实例对象的隐式原型指向构造函数的现实原型o.__proto__ _constructor.prototype;// 4.作用域的绑定使用apply改变this的指向//作为参数传递console.log(arguments,arguments);//[Arguments] { 0: ydb } argumentsconsole.log(o,o)_constructor.apply(o, arguments);console.log(o,o);//Person { name: ydb } oreturn o;
}
var person1 createPerson(Person, ydb);
person1.sayName();
console.log(person1);//Person { name: ydb } 注意这里说的构造函数并不是指的是constructor而是指的是Person函数因为每一个函数的原型对象上有一个constructor属性指向函数本身
console.log(Person.prototype.constructor Person); // true到这里可以很清楚的知道之所以person1可以使用sayName方法完全是因为这句代码
o.__proto__ _constructor.prototype;它们实现了同一个对象的引用这个对象在这里指的是Person的原型对象也就是Person.prototype。并不是Person把原型对象复制给了o对象上它们之前只是一种对象引用的关系。 再来看看继承是如何实现的
class Animal {//将new的实例对象指向构造函数本身constructor(name) {this.name name;}print() {console.log(this.name);}
}class Dog extends Animal {//将new的实例对象指向构造函数本身constructor(name) {super(name);}print() {console.log(dog);}walk() {console.log(${this.name} is a dog);}
}const dog new Dog(二哈);
dog.print(); // dog
dog.walk(); // 二哈 is a dog 转换一下
var __extends (this this.__extends) || (function () {var extendStatics function (d, b) {extendStatics Object.setPrototypeOf ||({ __proto__: [] } instanceof Array function (d, b) { d.__proto__ b; }) ||function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] b[p]; };return extendStatics(d, b);};return function (d, b) {extendStatics(d, b);function __() { this.constructor d; }d.prototype b null ? Object.create(b) : (__.prototype b.prototype, new __());};
})();
var Animal /** class */ (function () {function Animal(name) {this.name name;}Animal.prototype.print function () {console.log(this.name);};return Animal;
}());
var Dog /** class */ (function (_super) {__extends(Dog, _super);function Dog(name) {return _super.call(this, name) || this;}Dog.prototype.print function () {console.log(dog);};Dog.prototype.walk function () {console.log(this.name is a dog);};return Dog;
}(Animal));
var dog new Dog(二哈);
dog.print(); // dog
dog.walk(); // 二哈 is a dog可以看出通过__extends方法实现了某些对象对其他对象的引用达到功能模块复用的目的并没有什么魔法。 所以到现在你应该明白了JavaScript中其实并没有类这个概念的只不过是语法糖隐藏了内部实现方便开发人员的实现对对象之间引用这种功能。JavaScript中也没有所谓的“原型继承”本质都是对象之间的引用。