构造函数
- 构造函数就是一个普通的函数,但具有自己的特征和用法
var Vehicle = function () {
this.price = 1000;
};
- 上面代码中,Vehicle 就是构造函数。为了与普通函数区别,构造函数名字的第一个字母通常大写
- 构造函数的特点有两个
- 函数体内部使用了 this 关键字,代表了所要生成的对象实例
- 生成对象的时候,必须使用 new 命令
- 构造函数内部,this 指的是一个新生成的空对象,所有针对 this 的操作,都会发生在这个空对象上。构造函数之所以叫“构造函数”,就是说这个函数的目的,就是操作一个空对象(即 this 对象),将其“构造”为需要的样子。
- 如果构造函数内部有 return 语句,而且 return 后面跟着一个对象,new 命令会返回 return 语句指定的对象;否则,就会不管 return 语句,返回 this 对象
var Vehicle = function () {
this.price = 1000;
return 1000;
};
(new Vehicle()) === 1000
// false
上面代码中,构造函数 Vehicle 的 return 语句返回一个数值。这时,new 命令就会忽略这个 return 语句,返回“构造”后的 this 对象
如果 return 语句返回的是一个跟 this 无关的新对象,new 命令会返回这个新对象,而不是 this 对象。这一点需要特别注意
var Vehicle = function (){
this.price = 1000;
return { price: 2000 };
};
(new Vehicle()).price
// 2000
上面代码中,构造函数 Vehicle 的 return 语句,返回的是一个新对象。new 命令会返回这个对象,而不是 this 对象
如果对普通函数(内部没有 this 关键字的函数)使用 new 命令,则会返回一个空对象
function getMessage() {
return 'this is a message';
}
var msg = new getMessage();
msg // {}
typeof msg // "object"
- 上面代码中,getMessage 是一个普通函数,返回一个字符串。对它使用 new 命令,会得到一个空对象。这是因为 new 命令总是返回一个对象,要么是实例对象,要么是 return 语句指定的对象。return 语句返回的是字符串,所以 new 命令就忽略了该语句
new 命令
- new 命令的作用,就是执行构造函数,返回一个实例对象
var Vehicle = function () {
this.price = 1000;
};
var v = new Vehicle();
- 使用 new 命令时,它后面的函数依次执行下面的步骤
- 创建一个空对象,作为将要返回的对象实例
- 将这个空对象的原型,指向构造函数的 prototype 属性
- 将这个空对象赋值给函数内部的 this 关键字
- 开始执行构造函数内部的代码
this 关键字
- 个人理解,只有函数调用的时候,才可以确定 this 指向,谁调用指向谁;未调用,在哪调用没关系,只和谁在调用有关
- 全局环境下的 this ,指向全局对象
- 全局作用域里函数其实就是 window (全局对象)的方法,所以全局作用域调用函数 this 指向 windows,
- 内部函数中的 this,指向全局对象
- 箭头函数在哪里定义就指向谁,箭头函数外部指向谁就指向谁,或者箭头函数没有 this
- 构造函数的 this 指向实例对象
- 对象里的方法 this 指向对象
- 对象的副属性里的方法中 this 指向副属性
- 对象里属性的值中 this,指向全局对象
prototype 属性:原型对象
每个函数都有一个 prototype 属性,这个属性是指向一个对象的引用,这个对象称为原型对象,原型对象包含函数实例共享的方法和属性,也就是说将函数用作构造函数调用(使用 new 操作符调用)的时候,新创建的对象会从原型对象上继承属性和方法
每个函数都有一个 prototype 属性,指向一个对象
- 对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型
原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上
原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象
原型链
所有对象都有自己的原型对象(prototype),任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”:对象到原型,再到原型的原型
原型链的尽头就是 null
constructor 属性
prototype 对象下有个 constructor 属性,默认指向 prototype 对象的构造函数
- 实例对象里原型对象的 constructor 指向构建对象
- 普通对象的 constructor 指向 Object
公有属性/方法,私有属性/方法,静态属性/方法,共享属性/方法
公有是指,在外部环境中可以获取的,可以直接通过实例对象用”.”运算符操作获得。(在构造函数中用 this 进行声明)
私有是指,在外部环境中不能直接访问,该属性/方法只能在对象的构造函数内访问。(声明在构造函数里的变量和方法,没有用到 this)
静态是指,属性/方法是归属于构建函数/类的,而非实例对象。可以直接通过类名来调用得到
func.静态方法名 = function () {....};
func.静态方法名()
共享是指,实例对象之间,共享属性和方法,借助向原型对象添加属性和方法,可以实现继承
class 类
- class 可以看作只是一个语法糖,完全可以看作构造函数的另一种写法
- 使用的时候,也是直接对类使用 new 命令,跟构造函数的用法完全一致
constructor()方法
- constructor()方法是类的默认方法,通过 new 命令生成对象实例时,自动调用该方法
- constructor()方法默认返回实例对象,也就是 this 的指向
super 关键字
super 这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同
- super 作为函数调用时,代表父类的构造函数,子类的构造函数必须执行一次super 函数,且只能用在构造函数中,用在其他地方就会报错
super 作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类
实例
<script>
// 构造函数
let Animal = function (name) {
this.height = 120;
this.name = name;
};
// 通过原型对象给实例添加共享方法
Animal.prototype.walk = function () {
return `${this.name} 在缓慢行动`;
};
Animal.prototype.foot = function () {
return this.name + "有四条腿";
};
let tigger = new Animal("tigger");
console.log(tigger);
console.log(tigger.foot());
console.log(tigger.walk());
let cat = new Animal("cat");
console.log(cat);
console.log(cat.walk());
</script>
<script>
// class用法
class animal {
// 私有属性 只能在类内部使用
#foot = 4;
// constructor 引用构造函数
constructor(name) {
// this指向animal的实例化对象
// 公有属性
this.name = name;
}
// 共享方法/属性,和构造函数的prototype对象的一样
walk() {
return this.name + "开始行走";
}
run() {
return this.name + "跑向猎物";
}
// 静态只需要在前面机上static关键字
// 如果静态方法包含this关键字,这个this指的是类,而不是实例
static eat() {
console.log("快跑野兽要进食了");
}
}
// 静态属性
animal.live = "live";
// new一个对象
let tigger = new animal("tigger");
// 调用静态方法
animal.eat();
console.log(tigger.walk());
console.log(tigger.run());
console.log(animal.live);
// 继承
class Dog extends animal {
constructor(name, color) {
// 子类必须使用一次super函数
super(name);
this.color = color;
}
growl() {
console.log("growl");
}
// 访问器属性 get set方法读写
get dogColor() {
console.log(this.color);
}
set reColor(value) {
this.color = value;
}
}
// 父类的静态方法会也被子类继承
Dog.eat();
let lucky = new Dog("lucky", "black");
console.log(lucky.walk());
lucky.reColor = "white";
lucky.dogColor;
</script>
解构赋值
从数组和对象中提取值,对变量进行赋值,这被称为解构
实例
let [a, b, c] = [1, 2, 3];
// 如果解构不成功,变量的值就等于undefined
var [j, q, k] = [11, 12];
console.log(j, q, k);
// 可以使用默认的值来避免结构不成功造成的undefined
var [j, q, k = 13] = [11, 12];
console.log(j, q, k);
// 使用...rest参数把多余的值,放入一个数组
let [z, x, ...v] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
console.log(z, x, v);
// 两个变量交换值很方便
let [one, two] = [10, 100];
console.log(one, two);
one = one + two;
two = one - two;
one = one - two;
console.log(one, two);
// 上面方法太麻烦了
// 再用解构的方法变回去
[one, two] = [two, one];
console.log(one, two);
// 对象解构
let { name, age, sex } = { name: "terry", age: 28, sex: "male" };
console.log(name, age, sex);
// 更新使用括号当作表达式
({ name, age, sex } = { name: "marry", age: 26, sex: "female" });
console.log(name, age, sex);
// 变量名冲突
({
name: nickname,
age,
sex,
} = { name: "jordan", age: 36, sex: "female" });
console.log(nickname, age, sex);
// 函数的参数解构
function pValues({ name, age, sex }) {
console.log(name, age, sex);
}
let userValues = { name: "ice", age: 26, sex: "male" };
pValues(userValues);