概念
函数就是将某一段代码进行封装,并为这段代码起个名子,通过这个名子就可以调用这段代码,使用函数可以实现代码复用、便于修改;
函数声明
JavaScript 有三种声明函数的方法
[1]函数声明式
函数声明式是使用 function关键字 来定义函数; function关键字后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数。函数体放在大括号里面
-
语法
-
function 函数名(形参){ // 函数体 }
-
-
举例说明
-
function paly(str){ return `我最喜欢玩${str}` }
-
[2]函数表达式
采用变量赋值的方法,写一个匿名函数赋值给变量,这又称作函数表达式(因为等号右侧只能是表达式)
-
语法
-
const 变量名 = function(){ // 函数体 }
-
-
举例说明
-
const play = function(str){ return `我最喜欢玩${str}` }
-
-
注意事项
-
采用函数表达式声明函数时,function关键字后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效;
-
const play = function toplay(str){ console.log('toplay1', toplay) return `我最喜欢玩${str}` } play() // f toplay(str){...} console.log('toplay2', toplay) // Error toplay is not defined
-
[3]使用构造函数声明函数
使用构造函数声明函数,参数中最后一个参数为函数体,除了最后一个参数之外的参数为函数的参数(不推荐使用);
-
语法
-
new Function(形参···,函数体)
-
-
示例
-
const paly = new Function('str','return `我最喜欢玩${str}`') console.log(paly('游戏')) // 我最喜欢玩游戏
-
函数调用
调用函数时,要使用圆括号运算符。圆括号之中,可以加入函数的参数;
函数名(实参)
函数提升
函数声明式具有函数提升->可以在声明之前调用
-
console.log('play', paly('游戏')) // play 我最喜欢玩游戏 function paly(str){ return `我最喜欢玩${str}` }
-
上述代码等价于 将函数声明提升到
当前
作用域的最前面;-
function paly(str){ return `我最喜欢玩${str}` } console.log('play', paly('游戏'))
-
函数表达式没有函数提升,必须先声明后使用;
但是使用var定义变量过程中具有变量提升
-
console.log('play', play) // undefined console.log('play', play('游戏')) // Error play is not a function var play = function(str){ return `我最喜欢玩${str}` }
-
上述代码等价于
-
var play console.log('play', play) // undefined console.log('play', play('游戏')) // Error play is not a function play = function(str){ return `我最喜欢玩${str}` }
-
使用let与const定义变量接收函数表达式则没有变量提升;
-
console.log('play', play) // Error Cannot access 'play' before initialization console.log('play', play('游戏')) let play = function(str){ return `我最喜欢玩${str}` }
-
console.log('play', play) // Error Cannot access 'play' before initialization console.log('play', play('游戏')) const play = function(str){ return `我最喜欢玩${str}` }
函数提升与变量提升的优先级
优先级:函数提升>变量提升
;若是函数名与变量名相同,提升过程中不会被覆盖
,但是重新赋值会被覆盖
;
-
举例说明·
-
console.log(bar); // function bar() { console.log(123); } console.log(bar()); // 123 undefined var bar = 456; function bar() { console.log(123); } console.log(bar); // 456 bar = 789; console.log(bar); //789 console.log(bar()) // bar is not a function /* 解析说明-[1]函数提升优先级大于变量提升,并且在提升过程中不会被变量提升覆盖 var bar = function() { console.log(123); } var bar console.log(bar); console.log(bar()); 解析说明-[2]变量赋值会将函数覆盖 bar = 456; console.log(bar); bar = 789; console.log(bar); console.log(bar()) */
-
函数重复声明
如果同一个函数被声明了多次,后一个函数就会覆盖
前一个函数的声明;与此同时需要注意由于函数声明式具有函数提升,因此第一次声明在任何时候都是无用的!!!
function test(){
console.log(111)
}
test() // 222
function test(){
console.log(222)
}
test() // 222
-
无论何时调用都是222
-
上面代码等价于
-
function test(){ console.log(111) } function test(){ console.log(222) } test() // 222 test() // 222
-
函数传值
函数涉及到的数据有两个方向:
- 1、函数外向函数内传递->通过
形参与实参
可以实现将函数外的数据传递给函数内部。 - 2、函数内向函数外传递->通过return关键字即可以实现将函数内的数据传递到函数的调用处
[1]参数
函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数;
function square(x) {
return x * x;
}
square(2) // 4
square(3) // 9
- 上面x就是square函数的形参,2、3就是square函数的实参;
形参与实参之间就是赋值的关系;
- 若是简单数据类型,无论怎么修改不会影响原始值;
- 若是复杂数据类型,相当于赋值的地址引用,
修改属性/元素将会影响原始值
;// 简单类型 function square(x) { x = 222 } const num = 111 square(num) console.log('num', num) // 111 // 引用类型 function square(x) { x.age = 22 } const obj = { name:'chaochao', age:18 } square(obj) console.log('obj', obj) // {name: 'chaochao', age: 22}
在函数调用过程中,实数是可以省略的,省略的参数的值就变为undefined
function square(x) {
return x * x;
}
console.log(square()) // NaN
-
但是,没有办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式传入undefined;
function f(a, b) { return a; } f( , 1) // SyntaxError: Unexpected token ,(…) f(undefined, 1) // undefined
如果有同名的参数,则取最后出现的那个值;
function f(a, a) { console.log(a); } f(1, 2) // 2
function f(a, a) { console.log(a); } f(1) // undefined
arguments对象
由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这就是arguments对象的由来;
arguments是一个对应于传递给函数的参数的伪数组;
[1]除箭头函数外每个函数内部都有一个arguments对象
[2]作用是存储函数调用时传入的每一个参数(实参);
<script>
function getArg (a, b, c) {
console.log(arguments) //{0:1,1:2,length:2}
}
getArg(1, 2)
</script>
<script>
function getArg (a) {
console.log(arguments) //{0:1,1:2,length:2}
}
getArg(1, 2)
</script>
[3]arguments.callee的值是函数本身;
[4]arguments对象一般是用于在函数实参不确定的情况下使用的!
[5]案例
var length = 10
function fn () {
console.log(this.length)
}
var obj = {
method: function (fn) {
fn() // window调用---相当于window.length=10
arguments[0]() //arguments[0]等同于arguments.fn ---相当于对象调用 arguments.length=2
}
}
obj.method(fn, 1)
全局变量与局部变量
在最外层定义的是全局变量,可以在任何位置使用;
在函数中声明的变量称为局部变量,只能在本变量声明的函数内部使用;
其中的具体关系可以看作用域与作用域链
mst1
var a = 18
f1()
function f1(){
var b = 9
console.log(a)
console.log(b)
var a = '123'
}
-
运行结果为 undefined 9;
-
上述代码存在变量提升、函数提升,在运行过程中的代码如下
-
var a function f1(){ var b var a b = 9 console.log(a) console.log(b) a = '123' } f1()
-
打印a—>由于当前作用域中定义了a,获取当前作用域中a的值;
-
mst2
function f1(){
var a=b=c=9
console.log(a)
console.log(b)
console.log(c)
}
f1()
console.log(c)
console.log(b)
console.log(a)
-
结果为9 9 9 9 9 Error a is not defined
-
由于
javascript存在隐士赋值,并且隐士赋值的变量为全局变量
,详情可见作用域与作用域链; -
上述代码在运行过程中的结果为
-
var b var c function f1(){ var a a = 9 b = 9 c = 9 console.log(a) console.log(b) console.log(c) } f1() console.log(c) console.log(b) console.log(a)
-
[2]return
[1]使用retrurn返回函数内部成员(变为全局变量)的缺点–造成空间浪费
(1)在返回值为复杂数据类型的时候—每调用一次分配一次空间,若多次调用造成空间浪费的问题;
(2)举例说明
//创建一个函数
function Per() {
let per = {
name: '牛牛',
age: 23
}
return per;
}
//调用函数接收函数的返回值(对象)
let per1 = Per();
let per2 = Per();
//比较对象的值
console.log(per1 == per2); //false
- 在上面这个例子中:
- 当我们调用Per时返回一个对象分别使用per1、per2接收,但是引用地址却不相同,说明两个的数据不是存储在同一块空间;
- 局部变量在函数调用的时候被创建,在函数调用结束被销毁!
- 流程
- 1.创建Per函数–复杂数据类型,在堆区分配空间存储数据,在栈区分配空间存储数据的首地址;
- 2.系统调用函数得到函数的返回值为一个对象p1;
- 为局部变量分配空间
- 由于per为引用数据,为其在堆区分配空间,存放数据,在函数中存放堆区空间的首地址;
- return per
- 返回的是堆区空间的首地址;
- 函数执行完毕
- 局部变量内存地址被销毁,函数中存放的内存地址被销毁(但是堆区中的内容没有被销毁)
- 原因:复杂数据类型在销毁的时候销毁的是引用地址,若是在别的地方还有地址指向这个内容区域,这个内容就不会被销毁;但是若是没有内容指向这个区域,这个内容就会被销毁;
- 此时我们将per的地区地址返回作为全局变量,此时有人使用这块内容,所以不会被销毁
- 局部变量内存地址被销毁,函数中存放的内存地址被销毁(但是堆区中的内容没有被销毁)
- 为局部变量分配空间
- 3.系统调用函数得到函数的返回值为一个对象p2
- 重新为局部变量分配空间
- 由于per为引用数据,为其在堆区分配空间,存放数据,在函数中存放堆区空间的首地址;
- return per
- 返回的是堆区空间的首地址;
- 重新为局部变量分配空间
- 两个返回对象的地址不相同,所以不相等
[2]使用闭包解决return 关键字引起的空间浪费问题
- 作用:
- 可以在函数外部调用函数中的成员;
- 保证是同一个–解决了内存空间浪费的问题;
- 举例说明
<script>
//创建一个函数
function Per () {
let per = {
name: '牛牛',
age: 23
}
function cursor () {
return per
}
return cursor
}
let cursor = Per() //得到一个闭包函数
//调用函数接收函数的返回值(对象)
let per1 = cursor()
let per2 = cursor()
//比较对象的值
console.log(per1 == per2) //true
</script>
- 原因:
- 函数只执行一次,也就只生成一个per;
- 闭包函数执行多次
- 闭包函数的返回值就是我们想要数据的引用;
- 注:此处的cursor可以访问父函数(Per)的内部变量per的值,正常情况下局部变量在函数执行完毕就会被销毁,但是此处per不会销毁(因为还有人可以使用它)
- 闭包的使用步骤
- [1]在 外部函数中声明一个闭包函数;
- [2]在闭包函数中返回我们想要返回的值;
- [3]返回闭包函数;
- 使用
- [1]调用外部函数得到闭包函数;
- [2]每次想要获取返回值时调用闭包函数
- 闭包的缺点
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题;
this关键字
箭头函数
箭头函数是函数声明式与函数表达式的变形!
-
箭头函数是函数表达式的一个变形,语法比箭头函数更加整洁;
-
声明箭头函数:(形参)=>{函数体}
-
举例说明
-
// 函数声明 function getSum1(a,b){ return a+b } // 箭头函数 const getSum2 = (a,b)=>{ return a+b } console.log(getSum1(1,2), getSum2(1,2)) // 3 3
-
-
若是仅有一个形参,可以省略();
-
const getSum2 = a=>{ return a }
-
-
若是函数体内仅有一行代码,可以省略 {}; 若是函数体内仅有一行代码且有return,需要将return一起省略
-
const getSum2 = a=>a
-
-
如果函数返回的是对象,若是想省略函数体外面的{},需要在函数体外面加()
-
const getSum2 = a=>{a:a} getSum2(1) // undefined
-
原因:解析的时候,会将{}解析为函数体外的{},不会解析为对象
-
-
箭头函数与普通函数的区别
[1]没有函数提升
-
箭头函数是函数表达式的一个变形,函数表达式是没有函数提升的,箭头函数也没有寒暑假提升;
-
sum(1,2) // Cannot access 'sum' before initialization const sum = (a,b)=>{ console.log(a,b) }
-
-
函数提升中:只有函数声明定义的函数具有函数提升;
[2]没有自己的this
-
箭头函数没有自己的this指向,其本质是通过作用域链获取上一级的this指向;
-
举例说明
-
// 定时器里的函数为箭头函数,this要看上一级函数->Person的this指向->指向p function Person(){ this.age = 0; setInterval(() => { this.age++; console.log(p.age)// 依次递增 }, 1000); } var p = new Person();
-
// 定时器里面的函数为函数表达式,this指向window function Person(){ this.age = 0; setInterval(function(){ this.age++; console.log(p.age) // 一直为0 }, 1000); } var p = new Person();
-
-
因此箭头函数不能修改this指向
-
[1]使用call、apply、bind方法调用一个函数时,传递的第一个参数总会被忽略;
-
const obj = { a:111, b:222 } const fun = ()=>{ this.a++ return this.a } console.log(fun()) // NaN // apply的第一个参数被忽略,fun函数内的this还是指向window console.log(fun.apply(obj)) // NaN
-
const obj = { a:111, b:222 } function fun(){ this.a++ return this.a } console.log(fun()) // NaN // apply修改fun内部函数的this指向为obj console.log(fun.apply(obj)) // 112
-
-
[2]箭头函数不能通过new关键字调用,会报错fun is not a constructor
-
[3]箭头函数没有原型对象
- 每一个构造函数被创建的时候都会生成一个与之对象的对象,被称作原型对象;
- 箭头函数不能通过new关键字调用;
- 箭头函数不能作为构造函数;
- 箭头函数没有原型对象;
- 箭头函数不能作为构造函数;
[4]没有arguments
-
const fun = ()=>{ console.log('arguments',arguments) //arguments is not defined } fun()
[5]没有super
[6]没有new.target
构造函数
构造函数也称为构造器,通常在创建对象(Array,Function,Object)时会调用;
function
Es5中的构造函数就是一个普通函数,在表现形式上看与普通函数没有什么区别,只是在调用形式上有所区分;
-
使用new 关键字调用的函数被成为构造函数;
-
为作区分,一般函数名首字母大写;
-
举例说明
-
function CreateStu(name,age){ this.name = name this.age = age this.play =function(){ console.log('我最喜欢学校!') } } let s1 = new CreateStu('chaochao', 18) let s2 = new CreateStu('niuniu', 23) console.log(s1,s2, s1.play == s2.play) // false
-
发现每一次调用都会重新创建一个方法(引用类型),若是所有实例对象都具有的属性和方法–>可以添加在原型中;
-
function CreateStu(name,age){ this.name = name this.age = age } CreateStu.prototype.play = function(){ console.log('我最喜欢学校!') } let s1 = new CreateStu('chaochao', 18) let s2 = new CreateStu('niuniu', 23) console.log(s1,s2, s1.play == s2.play) // true
-
-
new关键字
new关键字的作用
- [1]创建一个空对象;
- [2]修改函数的this指向,将this指向改创建的空对象;
- [3]将该对象的__ proto __指向该函数;
- [4]执行函数体内部代码,返回该对象;
class
ES6 为了使得构造函数的写法更接近传统语言的写法,引入了 Class(类)这个概念;ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
语法
class 类名{
constructor(){
// 相当于es5的构造函数
}
}
constructor方法
- constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法;
- 一个类必须有constructor()方法,如果没有显式定义,一个
空的
constructor()方法会被默认添加;
举例说明
需求:现在创建一个构造函数用于创建 学生 每个学生有 姓名、年龄属性以及 say 函数,还有一个标示,表明是‘学生’
-
使用Es5的构造函数进行创建
function Student(name,age){ this.name = name this.age = age this.attr = '学生' this.say = function(){ console.log(`大家好,我叫${name},今年${age}岁`) } } let s1 = new Student('chaochao', 18) let s2 = new Student('niuniu', 23) s1.say() // 大家好,我叫chaochao,今年18岁 s2.say() // 大家好,我叫niuniu,今年23岁 console.log(s1.say == s2.say) // false
使用Es5创建实例化对象,
默认情况下会将 属性与方法添加在实例化对象身上
。
每一次创建一个实例化对象都会创建一个函数,不同实例化对象的方法引用地址不同!
若是想要添加在原型对象上,需要使用如下代码function Student(name,age){ this.name = name this.age = age } Student.prototype.say = function(){ // 实例化对象调用 -> this指向的是实例化对象 console.log(`大家好,我叫${this.name},今年${this.age}岁`) } Student.prototype.attr = '学生' let s1 = new Student('chaochao', 18) let s2 = new Student('niuniu', 23) s1.say() // 大家好,我叫chaochao,今年18岁 s2.say() // 大家好,我叫niuniu,今年23岁 console.log(s1,s2,s1.say == s2.say) // true
需要注意的是,属性只有每个实例化对象都相同才能添加在原型对象上,否则会被覆盖。
-
使用Es6构造函数进行创建
class Student{ constructor(name,age){ this.name = name this.age = age this.attr = '学生' } say1(){ console.log(`大家好,我叫${this.name},今年${this.age}岁`) } say2 = () { console.log(`大家好,我叫${this.name},今年${this.age}岁`) } } let s1 = new Student('chaochao', 18) let s2 = new Student('niuniu', 23) s1.say() s2.say() console.log(s1,s2, s1.say == s2.say) // true
在类中
直接定义在{}中的方法默认会添加在该构造函数的原型对象上
;通过赋值的形式定义方法是将方法添加在实例化对象上面
了 -
在类中,若是有些变量是常量,不需要在构造器(constructor)中去声明,可直接在类中声明即可。 -> 如上需求每个学生都要有一个标识为学生
class Student{ attr = '学生' constructor(name,age){ this.name = name this.age = age } say(){ console.log(`大家好,我叫${this.name},今年${this.age}岁`) } } let s1 = new Student('chaochao', 18) let s2 = new Student('niuniu', 23) s1.say() s2.say() console.log(s1,s2, s1.say == s2.say) // true
attr属性
默认会添加在实例化对象身上
static关键字
目前我们在类中定义的属性、声明的方法,都会被实例继承,若是不想属性、方法被实例继承,要在前面加static关键字—> 表示该方法/属性为静态方法/属性(是构造函数的属性/方法
),不被实例所继承;
-
静态属性/方法仅能被当前类/自类访问;
-
举例说明
-
class Foo { static classMethod() { return 'hello'; } } Foo.classMethod() // 'hello' var foo = new Foo(); foo.classMethod() // TypeError: foo.classMethod is not a function
-
class MyClass { static myStaticProp = 42; constructor() { console.log(MyClass.myStaticProp); // 42 } } const m = new MyClass() console.log('myStaticProp',m.myStaticProp) // undefined
-
私有属性
私有属性的属性名之前使用#
表示;
class IncreasingCounter {
#count = 0;
get value() {
console.log('Getting the current value!');
return this.#count;
}
increment() {
this.#count++;
}
}
上面代码中,#count
就是私有属性,只能在类的内部使用(this.#count
)。如果在类的外部使用,就会报错。
const counter = new IncreasingCounter();
counter.#count // 报错
counter.#count = 42 // 报错
上面示例中,在类的外部,读取或写入私有属性#count
,都会报错。
另外,不管在类的内部或外部,读取一个不存在的私有属性,也都会报错。这跟公开属性的行为完全不同,如果读取一个不存在的公开属性,不会报错,只会返回undefined
。
class IncreasingCounter {
#count = 0;
get value() {
console.log('Getting the current value!');
return this.#myCount; // 报错
}
increment() {
this.#count++;
}
}
const counter = new IncreasingCounter();
counter.#myCount // 报错
上面示例中,#myCount
是一个不存在的私有属性,不管在函数内部或外部,读取该属性都会导致报错。
注意,私有属性的属性名必须包括#
,如果不带#
,会被当作另一个属性。
class Point {
#x;
constructor(x = 0) {
this.#x = +x;
}
get x() {
return this.#x;
}
set x(value) {
this.#x = +value;
}
}
上面代码中,#x
就是私有属性,在Point
类之外是读取不到这个属性的。由于井号#
是属性名的一部分,使用时必须带有#
一起使用,所以#x
和x
是两个不同的属性。
这种写法不仅可以写私有属性,还可以用来写私有方法。
class Foo {
#a;
#b;
constructor(a, b) {
this.#a = a;
this.#b = b;
}
#sum() {
return this.#a + this.#b;
}
printSum() {
console.log(this.#sum());
}
}
上面示例中,#sum()
就是一个私有方法。
另外,私有属性也可以设置 getter 和 setter 方法。
class Counter {
#xValue = 0;
constructor() {
console.log(this.#x);
}
get #x() { return this.#xValue; }
set #x(value) {
this.#xValue = value;
}
}
上面代码中,#x
是一个私有属性,它的读写都通过get #x()
和set #x()
操作另一个私有属性#xValue
来完成。
私有属性不限于从this
引用,只要是在类的内部,实例也可以引用私有属性。
class Foo {
#privateValue = 42;
static getPrivateValue(foo) {
return foo.#privateValue;
}
}
Foo.getPrivateValue(new Foo()); // 42
上面代码允许从实例foo
上面引用私有属性。
私有属性和私有方法前面,也可以加上static
关键字,表示这是一个静态的私有属性或私有方法。
class FakeMath {
static PI = 22 / 7;
static #totallyRandomNumber = 4;
static #computeRandomNumber() {
return FakeMath.#totallyRandomNumber;
}
static random() {
console.log('I heard you like random numbers…')
return FakeMath.#computeRandomNumber();
}
}
FakeMath.PI // 3.142857142857143
FakeMath.random()
// I heard you like random numbers…
// 4
FakeMath.#totallyRandomNumber // 报错
FakeMath.#computeRandomNumber() // 报错
上面代码中,#totallyRandomNumber
是私有属性,#computeRandomNumber()
是私有方法,只能在FakeMath
这个类的内部调用,外部调用就会报错。
get与set方法
class People{
constructor(name, age){
this.name = name
this.age = age
}
}
const p1 = new People('chaochao', 18)
p1.age = 20
console.log('p1', p1) // {name: 'chaochao', age: 20}
如上案例中,我们可以随意去修改实例化对象的属性或方法。这样对于数据来说是非常不安全的,如对于age来说不能为负数…
像一些有限制的数据 我们希望不能被随意更改,此时就用到了set
以及get
方法。
- 语法如下
当通过点语法去赋值时并不会直接赋值而是调用同名的set方法,当读取时也是直接调用同名的get方法。get 属性名(){ return xxx } set 属性名(){ // 赋值xxx }
但是需要注意一点,若是需要使用get与set方法时属性名不要与方法名相同,举例说明
class People{
constructor(name, age){
this.name = name
this.age = age
}
get age(){
console.log('age', this.age)
return this.age
}
set age(value){
console.log('value', value)
this.age = value
}
}
const p1 = new People('chaochao', 18) // 进入死循环 error: Maximum call stack size exceeded
在调用constructor函数进行赋值时,this.age = age 就会调用age的set方法,而set函数里面的this.age = value又会调用age的set方法,就会陷入死循环了。
class People{
constructor(name, age){
this.name = name
this._age = age
}
get age(){
return this._age
}
set age(value){
this._age = value > 0 ? value : this._age
}
}
const p1 = new People('chaochao', 18)
这样就没有问题了!
tips: 属性名和方法名不重复即可!
总结
- 在Es5函数中
- 默认会将属性与方法添加在实例化对象身上
- 在Es6类中
- 属性:无论是在
{}内部赋值
还是在constructor
中赋值都是添加给实例化对象
的属性 - 方法:若是通过
函数声明式
声明的函数是添加在原型对象
上,若是通过函数表达式
方式声明的函数是添加在实例化对象
上的 - 静态属性/方法:在属性/方法前面加
static
关键字表示当前属性/方法为静态属性/方法,静态属性/方法是构造函数
本身的属性,仅能被当前类所访问; - 私有属性/方法:在属性方法前面加
#
表示当前属性/方法为私有属性。私有属性/方法仅能在当前类的内部
使用,在类的外部使用会被错。
查看属性class Person { // 属性:无论是在{}内部赋值还是在constructor中赋值都是添加给实例化对象的属性 // 属性[1] 每个人都有共同的特点都生活在地球中,不需要实例化对象传递过来,因此可以直接声明在{}中 area = '地球圈' // 属性[2] 每个人都有不同的名字、性别、年龄,需要实例化对象的时候传递过来,因此声明在constructor中 constructor(name, age, sex ){ this.name = name this.age = age this.sex= sex } // 方法[1]:直接定义在{}中的方法是添加在 构造函数的原型对象上的 lookthis(){ console.log('this',this) } // 方法[2]:通过赋值的形式定义方法是将方法是将方法添加在实例化对象上面了(与变量赋值相同) hello = function(){ console.log(`大家好,我是${this.name},我${this.age}岁了`) } // 若是我们想要将属性和方法添加给构造函数而非实例化对象,需要使用 static 关键字 static attr = '创建一个person对象' static mth = function (){ console.log('我是一个创建person对象的构造函数') } // 若是仅想要属性与方法在类的内部使用就以 # 开头 #value ='月球没有?' }
const p1 = new Person('chaochao', 18, '女') console.log('p1',p1)
- 属性:无论是在
继承
Class 可以通过extends
关键字实现继承,让子类继承父类的属性和方法;
super
ES6 规定,子类必须在constructor()方法中调用super()
,否则就会报错(上述代码就报错了);原因是ES6 的继承机制是先将父类的属性和方法,加到一个空的对象上面,然后再将该对象作为子类的实例
,也就是说只有调用super方法,才能生成一个继承父类的this对象,否则无法生成;
-
若是子类没有constructor方法,子类会隐士生成一个constructor方法并隐士调用super方法;
-
class People{ constructor(name,age){ this.name = name this.age = age } play(){ console.log('直立行走') } } class Stu extends People{ } const s1 = new Stu('chaochao', 18) console.log('s1',s1)
-
-
若是显示调用 super,则必须将需要的值传递给super
class People{ constructor(name,age){ this.name = name this.age = age } play(){ console.log('直立行走') } } class Stu extends People{ constructor(name,age,sex){ super(name,age) this.sex= sex } } const s1 = new Stu('chaochao', 18, '女') console.log('s1',s1)
若是显示定义了构造函数,但是却没有将值传递到super中将无法通过this获取对应的值class People{ constructor(name,age){ this.name = name this.age = age } play(){ console.log('直立行走') } } class Stu extends People{ constructor(name,age,sex){ super() this.sex= sex } } const s1 = new Stu('chaochao', 18, '女') console.log('s1',s1)
-
这也意味着新建子类实例时,父类的构造函数必定会先运行一次;
class People{ constructor(name,age){ this.name = name this.age = age console.log(11111111111) } play(){ console.log('直立行走') } } class Stu extends People{ constructor(name,age,id){ super(name,age,id); this.id = id console.log(2222222222) } } const s1 = new Stu('chaochao', 18, 222)
发现在创建s1对象时,先打印了11111111111,又打印了22222222222;
原因
- 在创建s1对象时,我们使用new关键字调用了Stu构造函数->会自动调用Stu类的constructor构造函数;
- 在Stu类的constructor构造函数中调用super方法->会调用父类的constructor构造函数;
-
只有调用
super()
之后,才可以使用this
关键字,否则会报错;- 因为没有调用super之前,没有完成还没有子类实例;
class People{ constructor(name,age){ this.name = name this.age = age } play(){ console.log('直立行走') } } class Stu extends People{ constructor(name,age,sex){ this.sex= sex super() } } const s1 = new Stu('chaochao', 18, '女') console.log('s1',s1)
- 因为没有调用super之前,没有完成还没有子类实例;
-
若是想在子类中调用父组件的方法可以通过
super.父组件方法名
来调用,举例说明class Animal{ constructor(name){ this.name = name } say(){ console.log(`${this.name}在叫`) } } class Dog extends Animal{ say(){ super.say() // 打印旺财在叫 } } const dog1 = new Dog('旺财') dog1.say()
被继承的属性和方法
父类的属性和方法,除了私有属性和私有方法外都会被子类继承;
-
class People{ #p = 'people' constructor(name,age){ this.name = name this.age = age console.log(11111111111) } play(){ console.log('直立行走') } } class Stu extends People{ constructor(name,age,id){ super(); this.id = id console.log(this.#p) //Private field '#p' must be declared in an enclosing class } } const s1 = new Stu() // s1.#p // Private field '#p' must be declared in an enclosing class
-
可以发现父类的私有属性 无论是在子类的{}里面还是外面都无法获取;若是我们想获取父类的某个属性该怎么办呢->可以使用get和set方法;
-
class People{ #p = 'people' constructor(name,age){ this.name = name this.age = age console.log(11111111111) } play(){ console.log('直立行走') } getp(){ return this.#p } } class Stu extends People{ constructor(name,age,id){ super(); this.id = id console.log('#p', this.getp()) // #p people } } const s1 = new Stu()
-
-
获取子类的父类
可以通过Object.getPrototypeOf()
方法获取子类的父类;
-
class People{ } class Stu extends People{ constructor(name,age,id){ super(); } } console.log(Object.getPrototypeOf(Stu)) console.log(Object.getPrototypeOf(People))
可以判断一个类是否继承了另一个类;
-
class People{ } class Stu extends People{ constructor(name,age,id){ super(); } } console.log(Object.getPrototypeOf(Stu) == People) // true