重学ES - 2(Function & Object & Class)

3 篇文章 0 订阅

一、Function

1.函数的参数

默认参数

对于函数而言,经常会用到参数,关于参数的默认值通常都是写在函数体中,如在 ES5 的时候大家都会这么写:

function foo(x, y) {
    y = y || 'world'
    console.log(x, y)
}
foo('hello', 'imooc')
foo('hello', 0)

当一个函数有很多参数涉及初始化的时候,这样写代码极其丑陋,所以在 ES6 中改变了对这种知识的写法:

function foo(x, y = 'world') {
    console.log(x, y)
}
foo('hello', 0)

函数参数是从左到右解析,如果没有默认值会被解析成 undefined

如果我们想让具体某个参数使用默认值,我们可以使用 undefined 进行赋值,如下段代码所示:

function f(x, y = 7, z = 42) {
    return x + y + z
}
console.log(f(1, undefined, 43)) // 51

在ES6中我们不仅可以给参数默认赋值具体的数值,同时参数赋值支持参数的逻辑运算进行赋值,如下段代码所示:

function f(x, y = 7, z = x + y) {
    return z * 0.5
}

console.log(f(1, 7)) // 4
function ajax(url, {
    body = '',
    method = 'GET',
    headers = {}
} = {}) {
    console.log(method)
}

ajax('http://www.imooc.com', {
    method: 'POST'
})

拓展:
在函数体内,有时候需要判断函数有几个参数,一共有2个办法。在 ES5 中可以在函数体内使用 arguments 来判断。

function foo(a, b = 1, c) {
    console.log(arguments.length)
}
foo('a', 'b') //2

然而在 ES6 中不能再使用 arguments 来判断了,但可以借助 Function.length 来判断。

function foo(a, b = 1, c) {
    console.log(foo.length)
}
foo('a', 'b') // 1

我们发现 Function.length 结果和 arguments 的结果不同!没错,Function.length 是统计第一个默认参数前面的变量数:

function foo(a = 2, b = 1, c) {
    console.log(foo.length)
}
foo('a', 'b') // 0

Rest 参数

在写函数的时候,部分情况我们不是很确定参数有多少个,比如求和运算,之前都是这么做的:

function sum() {
    let num = 0
    Array.prototype.forEach.call(arguments, function(item) {
        num += item * 1
    })
    return num
}

console.log(sum(1, 2, 3)) // 6
console.log(sum(1, 2, 3, 4)) // 10

其实在上面说过,这个代码在 ES5 中可以这么写,在 ES6 就不能这么写了,因为 arguments 的问题。现在需要这样写:

function sum(...nums) {
    let num = 0
    nums.forEach(function(item) {
        num += item * 1
    })
    return num
}

console.log(sum(1, 2, 3)) // 6
console.log(sum(1, 2, 3, 4)) // 10

当然,Rest Parameter 也可以和其他参数一起来用,比如:

function sum(base, ...nums) {
    let num = base
    nums.forEach(function(item) {
        num += item * 1
    })
    return num
}

console.log(sum(30, 1, 2, 3)) // 36
console.log(sum(30, 1, 2, 3, 4)) // 40

注意:
arguments 不是数组,所以不能直接使用数组的原生 API 如 forEach,而 Rest Parameter 是数组,可以直接使用数组的原生 API。

2.扩展运算符

Spread Operator 和 Rest Parameter 是形似但相反意义的操作符,简单的来说 Rest Parameter 是把不定的参数“收敛”到数组,而 Spread Operator 是把固定的数组内容“打散”到对应的参数。示例如下:

function sum(x = 1, y = 2, z = 3) {
    return x + y + z
}

console.log(sum(...[4])) // 9
console.log(sum(...[4, 5])) // 12
console.log(sum(...[4, 5, 6])) // 15

大家可以好好体会下前面两个示例,Rest Parameter 用来解决函数参数不确定的场景,Spread Operator 用来解决已知参数集合应用到固定参数的函数上,如果没有这个语法,可能需要这样做:

function sum(x = 1, y = 2, z = 3) {
    return x + y + z
}

console.log(sum.apply(null, [4])) // 9
console.log(sum.apply(null, [4, 5])) // 12
console.log(sum.apply(null, [4, 5, 6])) // 15

3.length属性

函数指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。

function foo(x = 1, y = 2, z = 3) {
    console.log(x, y)
}
console.log(foo.length)
// 0
function ajax(url, {
    body,
    headers,
    method = 'GET',
}) {
    console.log(method, arguments.length, ajax.length) // POST 2 2 (这里是把第二个参数整体当做变量来判断的)
}
function ajax(url, {
    body,
    headers,
    method = 'GET',
} = {}) {
    console.log(method, arguments.length, ajax.length) // POST 2 1 (第二个参数默认值为{})
}

4.name属性

函数的name属性,返回该函数的函数名。

function foo() {}
foo.name // "foo"
console.log((new Function).name) // anonymous
function foo(x, y) {
    console.log(this, x, y)
}
foo.bind({name: 'mia'})() // {name: 'mia'} undefined undefined
foo.bind({name: 'mia'})(1, 2) // {name: 'mia'} 1, 2
console.log(foo.bind({name: 'mia'}).name) // bound foo
console.log((function () {}).bind({name: 'mia'}).name) // bound 

5.箭头函数

箭头函数可以说是 ES6 很大的福利了,不管你是函数式爱好者还是面向对象开发者,函数是必须要用到的东西。之前声明函数需要使用 function,如下:

function hello() {
    console.log('say hello')
}
// 或

let hello = function() {
    console.log('say hello')
}

现在可以这样做了:

let hello = () => {
    console.log('say hello')
}
let hello = (name) => {
    console.log('say hello', name)
}
// 或者

let hello = name => {
    console.log('say hello', name)
}

TIP
如果只有一个参数,可以省略括号,如果大于一个参数一定要记得带括号

函数的声明和参数写的很清楚了,那么对于返回值有什么要注意的地方呢?

如果返回值是表达式

如果返回值是表达式可以省略 return 和 {}

 let pow = x => x * x
let pow = val => val + 1;
console.log(pow(2)) // 3

如果返回值是字面量对象

如果返回值是字面量对象,一定要用小括号包起来

  let person = (name) => ({
      age: 20,
      addr: 'Beijing City'
  })  // 如果不写(),会报错:Uncaught SyntaxError: Unexpected token ':'

其他

其他情况就要中规中矩的写好啦!

6.拓展

看上去箭头函数真的很漂亮,可是它有什么神秘之处吗?this,对,就是它。普通函数和箭头函数对 this 的处理方式是截然不同的。

let foo = {
    name: 'es',
    say: function() {
        console.log(this.name)
    }
}

console.log(foo.say()) // es

这是用普通函数的写法,say 在被调用之后,this 指向的是调用 say 方法的对象,显示是 foo 对象,所以 this === foo this.name 也就是 foo.name。

let foo = {
    name: 'es',
    say: () => {
        console.log(this.name, this)
    }
}
console.log(foo.say()) // undefined  window

因为箭头函数中对 this 的处理是定义时,this 的指向也就是 foo 外层的所指向的 window,而 window 没有 name 属性,所以结果是 undefined。

window.onload = () => {
    let btn = document.querySelector('#btn');
    btn.addEventListener('click', function () {
        // setTimeout是window对象下的方法,平时都是简写形式
        window.setTimeout(function () {
            console.log(this) // window
        })
    })
}

上述代码中,箭头函数中this指向定义时所在的对象,而不是调用时所在的对象

window.onload = () => {
    let btn = document.querySelector('#btn');
    btn.addEventListener('click', function () {
        // setTimeout是window对象下的方法,平时都是简写形式
        window.setTimeout(function () {
            console.log(this) // <button id="btn">点击</button>
        }.bind(this), 1000) // 这里bind修改的this是,'click'后面function ()的this,而他的this指向的定义所在的对象btn,所以里面打印的时候是btn 
    })
}

修改函数指向可以用:bind、apply、call

function People(name, age) {
    console.log(this) // People {}
    this.name = name
}
let p1 = new People('mia', 19)
console.log(p1) // People {name: 'mia'}
let People = (name, age) => {
    console.log(this)
    this.name = name
}
let p1 = new People('mia', 19)
console.log(p1) // 报错:Uncaught TypeError: People is not a constructor

由上述内容看出:箭头函数不可以当作构造函数

function foo(...val) {
    // arguments是一个伪数组
    console.log(arguments) // Arguments(3) [1, 2, 3, callee: (...), Symbol(Symbol.iterator): ƒ] 
}
foo(1, 2, 3)
let foo = (...val) => {
    console.log(arguments) // Uncaught ReferenceError: arguments is not defined
}
console.log(foo(1, 2, 3))

由上述内容看出:箭头函数不可以使用arguments对象,但是可以使用…rest的方式来代替arguments

总结:
1、箭头函数中this指向定义时所在的对象,而不是调用时所在的对象,箭头函数中实际上并没有this,他会逐层去外面的执行上下文找this,直到找到window为止
2、箭头函数不可以当作构造函数
3、箭头函数不可以使用arguments对象

二、Object

1. 属性简洁表示法

在 ES6 之前 Object 的属性必须是 key-value 形式,如下:

  let name = 'xiecheng'
  let age = 34
  let obj = {
      name: name,
      age: age,
      study: function() {
          console.log(this.name + '正在学习')
      }
  }

在 ES6中当key和value是一样的时候,是可以用简写的形式来表达:

  let name = 'xiecheng'
  let age = 34
  let obj = {
      name,
      age,
      study() {
          console.log(this.name + '正在学习')
      }
  }

但是,注意我们这里不可以使用箭头函数来定义

let s = 'school';
let name = 'mia'
window.name = 'tom'
let obj = {
    foo: 'bar',
    [s]: 'imooc',
    name: 'boo',
    study: () => {
        console.log(this.name, '正在学习') // tom 正在学习,这里的箭头函数中this的指向是定义时函数的指向,所以这里指向的是window
    }
}
obj.study()

2.属性名表达式

在 ES6 可以直接用变量或者表达式来定义Object的 key。

let s = 'school';
let name = 'mia'
let obj = {
    foo: 'bar',
    [s]: 'imooc',
    name: 'boo',
    study: function () {
        console.log(this.name, '正在学习') // boo 正在学习
    }
}
obj.study()

对比一下:

let s = 'school';
let name = 'mia'
window.name = 'tom'
let obj = {
    foo: 'bar',
    [s]: 'imooc',
    name: 'boo',
    study() {
        console.log(this.name, '正在学习') // boo
    },
    study1: () => {
        console.log(this.name) // tom
    },
    study2: function () {
        console.log(this.name) // boo
    }
}
obj.study()
obj.study1()
obj.study2()

3.Object.is():严格判断两个对象是否相等

console.log(Object.is(2, '2'), 2 === '2') // false  false
console.log(Object.is(2, 2), 2 === 2) // true  true
console.log(Object.is(NaN, NaN), NaN === NaN) // true  false
console.log(Object.is(+0, -0), +0 === -0) // false  true
let obj1 = { // 这里定义了对象,相当于是new Object(),栈内存中引用地址与堆内存中的空间都是不同的
    name: 'xiecheng',
    age: 34
}
let obj2 = { // new Object()
    name: 'xiecheng',
    age: 34
}
console.log(obj1 == obj2) // false  并不是看依赖一样就是相等的,实际上是两个地址
console.log(Object.is(obj1, obj2)) // false
let obj2 = obj1
console.log(Object.is(obj1, obj2)) // true

4.Object.assign():对象拼接

Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,它将返回目标对象。

const target = {
    a: 1,
    b: 2
}
const source = {
    b: 4,
    c: 5
}
const returnedTarget = Object.assign(target, source)
console.log(target)
// expected output: Object { a: 1, b: 4, c: 5 }
console.log(returnedTarget)
// expected output: Object { a: 1, b: 4, c: 5 }

TIP
从语法上可以看出源对象的个数是不限制的(零个或多个),如果是零个直接返回目的对象,如果是多个相同属性的会被后边的源对象的属相覆盖。

let s = Object.assign({
    a: 1
})
// {a: 1}

注意:
如果目的对象不是对象,则会自动转换为对象

let t = Object.assign(2)
// Number {2}
let s = Object.assign(2, {
    a: 2
})
// Number {2, a: 2}

如果对象属性具有多层嵌套,这时使用Object.assign()合并对象会怎么样呢?

let target = {
    a: {
        b: {
            c: 1
        },
        e: 4,
        f: 5,
        g: 6
    }
}
let source = {
    a: {
        b: {
            c: 1
        },
        e: 2,
        f: 3
    }
}
Object.assign(target, source)
console.log(target)

我们惊奇的发现, g 属性消失了…

注意
Object.assign()对于引用数据类型属于浅拷贝。

TIP
对象的浅拷贝:浅拷贝是对象共用的一个内存地址,对象的变化相互印象。
对象的深拷贝:简单理解深拷贝是将对象放到新的内存中,两个对象的改变不会相互影响。

思考:
1.如果目标对象传入的是 undefined 和 null 将会怎样呢?

let target = undefined
let source = {
    a: {
        b: {
            c: 1
        },
        e: 2,
        f: 3
    }
}
Object.assign(target, source)
console.log(target)// 会报错:Uncaught TypeError: Cannot convert undefined or null to object

2.如果源对象的参数是 undefined 和 null 又会怎样呢?

let source = undefined
let target = {
    a: {
        b: {
            c: 1
        },
        e: 2,
        f: 3
    }
}
Object.assign(target, source)
console.log(target) // 直接打印出target参数值

3.如果目标对象是个嵌套的对象,子对象的属性会被覆盖吗?
会被覆盖掉,因为是浅拷贝,具体参考上面的例子

拓展:深拷贝与浅拷贝

基本数据类型 都是深拷贝:

let a = 3;
let b = a;
a = 7;
console.log(a, b) // 7 3 属于深拷贝,a的值改变之后b的值不会受影响

涉及对象的时候,要注意深浅拷贝的问题
浅拷贝:

let obj1 = {
    name: 'mia',
    age: 19
}
let obj2 = obj1;
obj1.name = 'boo'
console.log(obj1.name, obj2.name) // boo boo  浅拷贝

深拷贝:
1、JSON.parse(JSON.stringify(a))
弊端:一般在实际项目当中最好不要用这个方式

  1. 如果obj里面存在时间对象,JSON.parse(JSON.stringify(obj))之后,时间对象变成了字符串。
  2. 如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象。
  3. 如果obj里有函数,undefined,则序列化的结果会把函数, undefined丢失。
  4. 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null。
  5. JSON.stringify()只能序列化对象的可枚举的自有属性。

2、手写深拷贝(大厂面试会遇到)

function deepClone(obj = {}) {
        // 先判断是否是obj/arr 这里考察的是typeof类型判断考点,null、obj、arr都是'object'
      // 
        if (typeof obj !== 'object' || typeof obj == null) {
            return obj
        }

        // 初始化返回结果,这里返回出去的是我们最终需要的数值
        let result;
        if (result instanceof Array) {
            result = []
        } else {
            result = {}
        }

        for (let key in obj) {
            // 保证key不是原型的属性,如果不是原型属性才进行拷贝
            if (obj.hasOwnProperty(key)) {
                // 拷贝这里一定要使用递归!!!!!
                result[key] = deepClone(obj[key])
            }
        }

        // 返回结果
        return result
    }

5.in :判断对象中是否包含某属性

let x = {a: 3, b: 4}
let y = {...x}
console.log('a' in x) // true

in不止可以用在对象上,也可以用在数组上,但在数组中判断的是下标

let arr = ['mia', 'boo', 'tom']
console.log(1 in arr) // true
console.log('mia' in arr) // false

在数组中可以用来简写长度的判断

if (arr.length - 1 === 3) {} // 等价于下面的写法
if (2 in arr) {}

6.对象的遍历方式

如何能够遍历出对象中每个key和value的值呢?

let obj = {
    name: 'xiecheng',
    age: 34,
    school: 'imooc'
}

Array中演示过,for…in不能够用于遍历Array,for…in的作用是用于遍历对象的。

for (let key in obj) {
    console.log(key, obj[key])
}

Object.keys()用于返回对象所有key组成的数组。

Object.keys(obj).forEach(key => {
    console.log(key, obj[key])
})

Object.getOwnPropertyNames()用于返回对象所有key组成的数组。

Object.getOwnPropertyNames(obj).forEach(key => {
    console.log(key, obj[key])
})

Reflect.ownKeys()用于返回对象所有key组成的数组。

Reflect.ownKeys(obj).forEach(key => {
    console.log(key, obj[key])
})

三、Class

Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象。但是,它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class(类)。

这是摘自阮一峰老师的博客,这句话放在 ES5 可以说不为过,然而到了 ES6 这么说就已经不严谨了。因为 ES6 中已经有了专属的 class 语法了。

有的同学喜欢函数式的编程方式,有的同学喜欢面向对象的编程思维,我们今天不论长短,重点讲述怎么使用 ES6 的 class 语法完成面向对象的开发。

对于面向对象编程而言,更关注类的声明、属性、方法、静态方法、继承、多态、私有属性。class只是基于es5实现方式的语法糖。

1.声明类

首先我们要先来说明在 JavaScript 世界里如何声明一个 “类”。在 ES6 之前大家都是这么做的

let Animal = function(type) {
    this.type = type
    this.walk = function() {
        console.log( `I am walking` )
    }
}

let dog = new Animal('dog')
let monkey = new Animal('monkey')

在上述代码中,我们定义了一个叫 Animal 的类,类中声明了一个属性 type、一个方法 walk;然后通过 new Animal 这个类生成实例,完成了类的定义和实例化。当然你也可以这样写:

let Animal = function(type) {
    this.type = type
}

Animal.prototype.walk = function() {
    console.log( `I am walking` )
}

let dog = new Animal('dog')
let monkey = new Animal('monkey')

在 ES6 中把类的声明专业化了,不在用 function 的方式了,请看:

class Animal {
    constructor(type) {
        this.type = type
    }
    walk() {
        console.log( `I am walking` )
    }
}
let dog = new Animal('dog')
let monkey = new Animal('monkey')

很明显,从定义上就很专业了,有构造函数、方法,但是 ES6 增加了新的数据类型 class 吗?

console.log(typeof Animal) //function

可以发现 class 的类型还是 function,和 ES5 貌似并没有什么区别,那么 class 中定义的方法在哪呢?我们知道只要是函数,就一定会有 prototype 对象。那么类的方法和 prototype 对象有什么关系呢?

console.log(Animal.prototype)
// {constructor: ƒ, walk: ƒ}
//   constructor: class Animal
//   walk: ƒ walk()
//   __proto__:
//   constructor: ƒ Object()
//   hasOwnProperty: ƒ hasOwnProperty()
//   isPrototypeOf: ƒ isPrototypeOf()
//   propertyIsEnumerable: ƒ propertyIsEnumerable()
//   toLocaleString: ƒ toLocaleString()
//   toString: ƒ toString()
//   valueOf: ƒ valueOf()
//   __defineGetter__: ƒ __defineGetter__()
//   __defineSetter__: ƒ __defineSetter__()
//   __lookupGetter__: ƒ __lookupGetter__()
//   __lookupSetter__: ƒ __lookupSetter__()
//   get __proto__: ƒ __proto__()
//   set __proto__: ƒ __proto__()

可以看出在 Animal.prototype 对象上有两个方法,一个是构造函数(constructor)、一个是自定义的方法(walk)。这是不是和 ES5 的第二种写法一模一样?我们再来看下属性,在 ES5 中有个 API 用来判断对象的自有属性(hasOwnProperty)。

console.log(dog.hasOwnProperty('type')) //true

这个表现也和 ES5 中直接使用 function 定义类的方式相同,所以得出一个结论:class 的方式是 function 方式的语法糖。

2.Setters & Getters

对于类中的属性,可以直接在 constructor 中通过 this 直接定义,还可以直接在类的顶层来定义:

class Animal {
    constructor(type, age) {
        this.type = type
        this._age = age
    }
    // 顶层来定义:
    get age() {
        return this._age
    }
    set age(val) {
        this._age = val
    }
}
let c1 = new Animal('monkey', 19)
c1.age = 18
console.log(c1.age) // 18

这个代码演示了,通过 get/set 来给类定一个属性,不过貌似没有说服力。因为 age 和 _age 都是类的属性,而且是相同的含义这样做感觉没有实际用途。但是如果一个属性是个只读的呢?或者需要添加一些业务逻辑呢?

class Animal {
    constructor(type) {
        this.type = type
    }
    // 这里只使用了get方法,意味着这个属性是只读的,当我们在下面更改的时候会报错
    get addr() {
        return '北京动物园'
    }
}

class People {
    constructor(name, age) {
        this.name = name;
        this.age = age;
        this._sex = 'male';
    }
    get sex() {
        return this._sex
    }
    set sex(val) {
        if (val === 0) {
            this._sex = 'male'
        } else if (val === 1) {
            this._sex = 'female'
        } else {
            this._sex = ''
        }
    }
}
let c1 = new People('mia', 19)
c1.sex = 1
console.log(c1.sex) // female

毋庸赘述,大家都能看出来含义。再来看下如下的应用场景:

class CustomHTMLElement {
    constructor(element) {
        this.element = element
    }
    get html() {
        return this.element.innerHTML
    }
    set html(value) {
        this.element.innerHTML = value
    }
}

利用 set/get 实现了对 element.innerHTML 的简单封装。

可是,有时候我们真的需要设置一个私有属性(闭包),然后通过一定的规则来限制对它的修改,利用 set/get就可以轻松实现。这是我们在construction中定义无法实现的。

let _age = 1
class Animal {
    constructor(type) {
        this.type = type
    }
    get age() {
        return _age
    }
    set age(val) {
        if (val > 0 && val < 10) {
            _age = val
        }
    }
}
let c2 = new Animal()
c2.age = 9;
console.log(_age) // 9

3.静态方法

静态方法是面向对象最常用的功能,可直接调用类下面的方法,不用new 实例调用。

静态方法,属于类的方法,即类可以直接调用的方法。为类所有实例化对象所共用(但不能用实例对象调用),所以静态成员只在内存中占一块区域;

实例方法,属于实例化类后对象的方法,即实例对象调用的方法。每创建一个类的实例,都会在内存中为非静态成员分配一块存储;

静态方法在一启动时就实例化了,因而静态内存是连续的,且静态内存是有限制的;而非静态方法是在程序运行中生成内存的,申请的是离散的空间。

在 ES5 中利用 function 实现的类是这样实现一个静态方法的:

let Animal = function(type) {
    this.type = type
    this.walk = function() {
        console.log( `I am walking` )
    }
}

Animal.eat = function(food) {
    console.log( `I am eating` )
}

在 ES6 中使用 static 的标记是不是静态方法,代码如下:

class Animal {
    constructor(type) {
        this.type = type
    }
    walk() {
        console.log( `I am walking` )
    }
    static eat() {
        console.log( `I am eating` )
    }
    static num = 4
}
Animal.eat(); // I am eating  可以直接调用的方法
Animal.walk(); // Animal.walk is not a function
new Animal().walk(); // I am walking

// 添加静态属性 & 静态方法:
Animal.count = 0;
Animal.getName = () => {
    console.log(Animal.count)
}
Animal.getName()

有没有很清爽,代码可读性一下子就上来了。

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

class People {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    static showName() {
        console.log(this.name)
    }
    showName1() {
        console.log(this.name)
    }
    static num = 4
}
let s1 = new People('mia')
s1.showName() // s1.showName is not a function
s1.showName1() // mia

注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。静态方法可以与非静态方法重名。

class Foo {
  static bar() {
    this.baz();
  }
  static baz() {
    console.log('hello');
  }
  baz() {
    console.log('world');
  }
}

Foo.bar() // hello

静态方法和普通方法的区别:
1、如果静态方法包含this关键字,这个this指的是类,而不是实例。
注意与类中普通方法:类的方法内部如果含有this,它默认指向类的实例。
2、静态方法不能被实例调用,只能通过类来调用。

静态方法 & 私有变量 & 静态参数 应用场景:
1、比如 Array.isArray, Array.from ,都是跟实例无关的,一般就是一些 helper 方法,这种情况就可以写为静态方法

2、私有变量可以在静态方法中调用,在外部无法调用,但静态参数在外部是可以调用的

class MyClass {
    static create(name, age = 10) {
        var obj = new MyClass();
        obj.#name = name;
        obj.#age = age;
        return obj
    }

    #name;
    #age;

    static _name = 0

    constructor() {
        this.#name = "Default Name";
        this.#age = 0;
    }

    toString() {
        return `${this.#name}: ${this.#age}`;
    }
}
console.log(MyClass.#name, MyClass._name) // Private field '#name' must be declared in an enclosing class   0

4.继承

面向对象只所以可以应对复杂的项目实现,很大程度上要归功于继承。如果对继承概念不熟悉的同学,可以自行查询。在 ES5 中怎么实现继承呢?

// 定义父类
let Animal = function(type) {
    this.type = type
}
// 定义方法
Animal.prototype.walk = function() {
    console.log( `I am walking` )
}
// 定义静态方法
Animal.eat = function(food) {
    console.log( `I am eating` )
}
// 定义子类
let Dog = function() {
    // 初始化父类
    Animal.call(this, 'dog')
    this.run = function() {
        console.log('I can run')
    }
}
// 继承
Dog.prototype = Animal.prototype

从代码上看,是不是很繁琐?而且阅读性也较差。再看看 ES6 是怎么解决这些问题的:

class Animal {
    constructor(type) {
        this.type = type
    }
    walk() {
        console.log( `I am walking` )
    }
    static eat() {
        console.log( `I am eating` )
    }
}

class Dog extends Animal {
  constructor () {
    super('dog')
  }
  run () {
    console.log('I can run')
  }
}
class People {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    showName() {
        console.log(this.name)
    }
}
class Coder extends People{
    constructor(name, age, conpany) {
        super(name, age)
        this.conpany = conpany
    }
    showConpany() {
        console.log(this.conpany)
    }
}
let c1 = new Coder('mia', 19, 'palfish')
console.log(c1) // Coder {name: "mia", age: 19, conpany: "palfish"}
c1.showName(); // mia
c1.showConpany(); // palfish

虽然 ES6 在类的定义上仅是 ES5 定义类的语法糖,但是从开发者的角度而言,开发更有效率了,代码可阅读性大大提升。

文章仅作为学习使用,如有侵权请告知删除,谢谢

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值