JavaScript函数和类
JavaScript函数
函数定义
一个函数定义(也称为函数声明,或函数语句)由一系列的函数
关键字组成, 依次为:
- 函数的名称。
- 函数参数列表,包围在括号( )中并由逗号( , )区隔。
- 函数功能,包围在花括号{ }中,用于定义函数功能的一些JavaScript语句。
function add(x, y) {
return x + y;
}
函数参数传递时,如果是传值则是传形参。如果是传对象,则是传引用(函数内部对对象的改变对外部是可见的)。
函数表达式
虽然上面的函数声明在语法上是一个语句,但函数也可以由函数表达式创建。这样的函数可以是匿名的;它不必有一个名称。例如,函数square
也可这样来定义:
// 第一种定义方式
const add = function(x, y) {
return x + y;
}
// 第二种定义方式
const add = function _add(x, y) {
return x + y;
}
函数表达式可以直接调用:
let val = function (x, y) {
return x + y
}(1, 3)
console.log(val); // 输出4
函数表达式就是把一个函数赋值给变量或者常量
什么时候使用函数表达式?什么时候使用命名方式的函数表达式?
- 当存在递归的时候,应该以命名函数表达式的方式定义函数
- 不存在递归时,习惯使用函数表达式
// 演示1.命名方式定义函数,赋值给常量时,递归函数正常执行
function fib(x) {
if (x < 2) {
return 1
} else {
return fib(x - 1) + fib(x - 2)
}
}
const fib2 = fib
console.log(fib2(5))
delete fib
console.log(fib2(5))
// 演示2.命名方式定义函数,赋值给变量,递归函数正常执行
function fib(x) {
if (x < 2) {
return 1
} else {
return fib(x - 1) + fib(x - 2)
}
}
let fib2 = fib
console.log(fib2(5))
delete fib
console.log(fib2(5))
// 演示3.函数表达式方式定义递归函数时,有可能会存在问题
let fib = function(x) {
if (x < 2) {
return 1
} else {
return fib(x - 1) + fib(x - 2)
}
}
const fib2 = fib
console.log(fib2(5))
//delete fib
fib = function() {}
console.log(fib2(5)) // 输出NaN (NaN表示not a number)
高阶函数
JavaScript的高阶函数的定义和Python是一样的,只是JavaScript函数的参数可以直接写上函数的实现部分,而Python最多可以写上一个lambda函数。
// 高阶函数:函数作为参数
map = function(arr, fn) {
const result = []
for (let a of arr) {
result.push(fn(a))
}
return result
}
let square = function(x) {
return x * x
}
const val = map([1, 2, 3, 4], square)
console.log(val)
// 高阶函数:函数实现直接作为参数
map = function(arr, fn) {
const result = []
for (let a of arr) {
result.push(fn(a))
}
return result
}
const val = map([1, 2, 3, 4], function(x) {
return x * x
})
console.log(val)
箭头函数
箭头函数表达式(也称胖箭头函数, fat arrow function)主要作用是是函数表达式更为简洁。
- 箭头函数总是匿名的
- 当箭头函数至少有一个参数时,可以省去小括号
- 当箭头函数只有一条语句时,可以省去大括号和return
map = function(arr, fn) {
const result = []
for (let a of arr) {
result.push(fn(a))
}
return result
}
// fn可以改写为箭头函数如下
const val1 = map([1, 2, 3, 4], x => {
console.log(x)
return x * x
})
// 更精简的箭头函数
const val2 = map([1, 2, 3, 4], x => x * x)
函数参数
默认参数
const add = function (x, y = 5) {
return x + y;
}
console.log(add(1)) // 6
// 到目前为止JavaScript仍然不支持位置参数跟在默认参数的后面
const add = function (x = 5, y) {
return x + y;
}
console.log(add(y = 1)) // NaN
可变参数
参数前加...
表示其是可变参数,可变参数在函数体内,表现为一个数组。
// 求所有参数的和
const sum = function(...args) {
let ret = 0;
for (let v of args) {
ret += v
}
return ret
}
const val = sum(1, 2, 3)
console.log(val)
参数结构
const add = function(x, y) {
return x + y
}
console.log(add(...[1, 2])) // 3
而且不能对object做参数解构,因为JavaScript还不支持关键字参数。
JavaScript类
基本使用
- 使用class关键字定义类
- constructor方法是构造方法
- 使用new关键字创建对象,参数为constructor方法的参数
- 实例调用静态方法的时候需要通过constructor属性
代码:下面的代码会定一个点类Point
class Point {
// 构造方法
constructor(x, y) {
this.x = x;
this.y = y;
}
// 普通方法
print() {
console.log(`<${this.x}, ${this.y}>`)
}
// 静态方法
static info() {
console.log('this is static method')
}
}
// 可以使用类直接调用静态方法
Point.info()
// 创建Point类的对象point
let point = new Point(1, 2)
// 对象调用普通方法
point.print()
// 实例不能直接调用静态方法
// point.info() // 会报错:不存在point.info函数
// 实例的constructor属性等价于Python的__class__
point.constructor.info()
类表达式
和函数一样,类除了有上面的命名方式的定义之外,还可以有类表达式。
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
const inst = new MyClass();
console.log(inst.getClassName()); // Me
console.log(Me.name); // ReferenceError: Me is not defined
类表达式本质上就是把一个类赋值给一个变量。
类的继承
- 继承采用extends关键字,借鉴于Java
class Point3D extends Point {
constructor(x, y, z) { // 重写构造函数
console.log(`x: ${x}, y: ${y}`)
// this.z = x + y this 不能再super之前使用
super(x, y) // 调用父类的构造函数
this.z = z
this.format = this.format.bind(this)
}
format() {
return super.format() + 'xxx'
}
}
let p = new Point3D(3, 5, 8)
p.print()
多继承-MixIn模式
JavaScript中子类的使用有两个原因:
- 接口继承:子类的实例肯定也是超类的实例(可以用instanceof运算符测试得到这条结论)。子类实例的行为类似于超类实例。但和超类实例相比,可以有一些额外的功能(即方法)。
- 实现继承:超类将功能传递给子类。实现继承的类的作用是有限的,因为只支持单继承,不可能从多个超类继承。
用通俗的话举例子来理解接口继承和实现继承,下面有三个类,分别是:
- Person:人类
- Storage:数据存储类,拥有一个存储数据的方法save
- Validation:数据验证类,拥有一个验证数据的方法validate
代码
class Person {
}
class Storage {
save(data) {
console.log('store data')
}
}
class Validation {
validate(schema) {
console.log('validate data')
}
}
现在要定义一个职工类Employee ,那么这个职工类肯定是要继承自Person类的(难道你敢说职工不是人?),同时这个职工还需要有两个能力:一个是存储数据的能力,一个是验证数据的能力。那么就又需要继承自Storage类和Validation类。那么Employee 类从Person类继承就是接口继承,因为超类和子类的行为类似。Employee 类从Storage继承或者从Validation继承都是实现继承,因为超类只是将功能传递给子类。
如果我们想实现这样的一个Employee类,那么一个很自然的写法就是多继承,下面的这种写法虽然很自然,但是多数语言都是不支持的,因为多重继承的时候会出现继承冲突。关于多重继承的冲突举一个简单的例子:定义一个动物(类)既是狗(父类1)也是猫(父类2),两个父类都有“叫”这个方法。那么当我们调用“叫”这个方法时,它就不知道是狗叫还是猫叫了,这就是多重继承的冲突。
// 在定义的时候就会抛出SyntaxError
class Employee extends Person, Storage, Validation {
}
em = new Employee()
em.save()
em.validate()
为了实现多继承,ES6中有自己独特的MinIn技术:将实现继承的类视作一个函数,输入是超类,输出是扩展该超类的子类
class Person {
}
const Storage = Sup => class extends Sup {
save(data) {
console.log('store data')
}
}
const Validation = Sup => class extends Sup {
validate(schema) {
console.log('validate data')
}
}
class Employee extends Storage(Validation(Person)) {
}
em = new Employee()
em.save() // 输出store data
em.validate() // 输出validate data
通过这样的MixIn技术给Person类混入了Storage类的save方法和Validation类的validate方法,成功的变相的实现了多继承。
下面再举一个例子
- Point类
- 可序列化类Serializable
- Point3D类:需要继承自Point类,然后还需要混入可序列化的功能
代码
class Point {
constructor(x ,y) {
this.x = x
this.y = y
}
print() {
console.log(`(${this.x}, ${this.y})`)
}
}
const Serializable = Sup => class extends Sup {
constructor(...args) {
super(...args)
if (typeof this.constructor.stringify !== 'function') {
throw new ReferenceError('must be define stringify')
}
if (typeof this.constructor.parse !== 'function') {
throw new ReferenceError('must be define parse')
}
}
toString() {
return this.constructor.stringify(this)
}
}
class Point3D extends Serializable(Point){
constructor(x, y, z) {
super(x, y)
this.z = z
}
static stringify(obj) {
let {x, y, z} = obj
return JSON.stringify({x, y, z}) // 返回值是一个string
}
static parse(data) {
let {x, y, z} = JSON.parse(data)
return new Point3D(x, y, z)
}
}
p3d_obj = new Point3D(2,5,19)
str = p3d_obj.toString()
console.log(str) // {"x":2,"y":5,"z":19}
console.log(typeof str) // string
new_P3d = Point3D.parse(str) // //通过序列化反序列化复制对象
console.log(new_P3d) // Point3D { x: 2, y: 5, z: 19 }
console.log(new_P3d instanceof Point3D) // true
console.log(new_P3d instanceof Point) // true
console.log(new_P3d == p3d_obj) // false
Serializable 在语义上变成一种装饰,用来装饰Person类,即 Employee 是一种可序列化的 Person。这种MixIn的思想就是Python装饰器在JavaScript里面的应用了,只是JavaScript没有像Python一样用语法糖的形式来实现。
参考:
- 分布式监控系统Zabbix-添加windows监控主机
- 盘点世界十大著名黑客攻击事件
- AS3中的单件(Singleton)模式
- puremvc框架之hello world!
- windows平台下编辑的内容传到linux平台出现中文乱码的解决办法
- puremvc框架之Command
- python sorted函数
- Centos 6.9下部署Oracle 11G数据库环境的操作记录
- puremvc框架之proxy
- Oracle数据库冷备份与热备份操作梳理
- Oracle数据库重做日志及归档日志的工作原理说明
- 用vs.net2010做flex/flash/as3开发
- python中input()与raw_input()的区别到底是啥?
- VB下中文URL编码问题的解决
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- WebSth 指纹识别插件简要分析
- Android实现3D层叠式卡片图片展示
- Android Studio屏幕方向以及UI界面状态的保存代码详解
- webshell,禁止你执行
- android实现下拉菜单三级联动
- Flutter 假异步的实现示例
- 详解Android4.4 RIL短信接收流程分析
- android 使用kotlin 实现点击更换全局语言(中日英切换)
- EasySec基于XP的渗透平台
- Android集成腾讯X5实现文档浏览功能
- 8个小时8个小时的上机课!!
- Android实现根据评分添加星级条
- Android悬浮窗的实现(易错点)
- phpcms上传导致getshell详解及案例
- android popupwindow用法详解