bind/call/apply三个方法
这三个方法都是用于修改函数或者方法中的this的
bind方法作用
- 修改函数或者方法中的
this为指定的对象,并且会返回一个修改之后的新函数 -
bind方法也可以传递参数, 参数必须写在this对象的后面
let obj = {
name:'Jason Wang'
}
function test1() {
console.log(this);
}
// 将test1中的this指定为obj对象,并返回一个新的函数fn1
let fn1 = test1.bind(obj); // {name: "Jason Wang"}
fn1()
function test2(a,b) {
console.log(a, b, this);
}
// 将test2中的this指定为obj对象,并接受两个参数传递给test2,返回一个新的函数fn2
let fn2 = test2.bind(obj,10,20); // 10 20 {name: "Jason Wang"}
fn2();
call方法作用
- 修改函数或者方法中的
this为指定的对象, 并且会立即调用修改之后的函数 -
call方法也可以传递参数, 参数必须写在this对象的后面
let obj = {
name:'Jason Wang'
}
function test1() {
console.log(this);
}
test1.call(obj); // {name: "Jason Wang"}
function test2(a,b) {
console.log(a, b, this);
}
test2.call(obj,10,20); // 10 20 {name: "Jason Wang"}
apply方法作用
- 修改函数或者方法中的this为指定的对象, 并且会立即调用修改之后的函数
-
apply也可以传递参数, 只不过参数必须通过数组的方式传递
let obj = {
name:'Jason Wang'
}
function test1() {
console.log(this);
}
test1.apply(obj); // {name: "Jason Wang"}
function test2(a,b) {
console.log(a, b, this);
}
test2.apply(obj,[10,20]); // 10 20 {name: "Jason Wang"}
强类型语言与弱类型语言
强类型语言
- 一般编译型语言都是强类型语言,
- 强类型语言,要求变量的使用要严格符合定义
- 例如定义
int num;那么num中将来就只能够存储整型数据
弱类型语言
- 一般解释型语言都是弱类型语言,
- 弱类型语言, 不会要求变量的使用要严格符合定义
- 例如定义
let num;,num中既可以存储整型, 也可以存储布尔类型等
面向对象三大特性
封装性
封装性就是隐藏实现细节,仅对外公开接口
为什么要封装
- 不封装的缺点:当一个类把自己的成员变量暴露给外部的时候,那么该类就失去对属性的管理权,别人可以任意的修改你的属性
- 封装是将数据隐藏起来,只能用此类的方法才可以读取或者设置数据,不可被外部任意修改
- 封装是面向对象设计本质(将变化隔离),这样降低了数据被误用的可能 (提高安全性和灵活性)
对象的私有变量和函数
- 默认情况下对象中的属性和方法都是公有的, 只要拿到对象就能操作对象的属性和方法
- 外界不能直接访问的变量和函数就是私有变量和是有函数
- 构造函数的本质也是一个函数,所以会开启一个新的作用域,所以在构造函数中定义的变量和函数就是私有属性和方法
- 由于私有属性的本质就是一个局部变量,并不是真正意义上的属性,所以如果通过
对象.xxx的方式是找不到私有属性的,会给当前对象新增一个xxx属性
function Person(myName,myAge) {
// 公有属性
this.name = myName;
this.age = myAge;
// 私有属性
let position = 'Beijing';
// 公有接口
this.setPosition = function (pst) {
position = pst;
};
// 公有接口
this.getPosition = function () {
return position;
};
// 公有方法
this.say = function () {
console.log("hello world");
}
}
let obj = new Person('Jason',24);
// 通过接口获取 position
console.log(obj.getPosition()); // Beijing
// 通过接口设置 position,操作的是私有属性
obj.setPosition('Shanghai');
console.log(obj.getPosition()); // Shanghai
// 会给obj对象新增一个position属性,操作的是公有属性
obj.position = 'Guangzhou';
console.log(obj.position); // Guangzhou'
属性和方法分类
- 实例属性与实例方法
- 通过构造函数创建的对象,称为实例对象
- 通过实例对象访问的属性,称为实例属性
- 通过实例对象调用的方法,称为实例方法
- 静态属性与静态方法
- 构造函数也是一个对象,所以也可以给构造函数动态添加属性和方法
- 通过构造函数访问的属性,称为静态属性
- 通过构造函数调用的方法,称为静态方法
function Person() {
this.name = "Jason";
this.say = function () {
console.log("hello world"
}
}
// 创建实例对象
let obj = new Person();
// 访问实例属性
console.log(obj.name);
// 调用实例方法
obj.say();
// 添加静态属性
Person.num = 666;
// 添加静态方法
Person.run = function () {
console.log("run");
}
// 访问静态属性
console.log(Person.num);
// 调用静态方法
Person.run();
继承性
- 把子类型中共同的属性和方法提取到父类型中
- 较少代码的冗余度, 提升代码的复用性
- 构造函数和构造函数之间的关系是
is a关系, 那么就可以使用继承来优化代码
借用原型链实现继承
将子类的原型对象修改为父类对象,这样就能使用原型链上的属性和方法
// 父类
function Person() {
this.name = null;
this.age = 0;
this.say = function () {
console.log(this.name, this.age);
}
}
// 子类
function Student() {
this.score = 0;
this.study = function () {
console.log('Learning JavaScript');
}
}
// 将子类的原型对象修改为父类对象,子类就拥有了父类对象的实例属性和方法
// 但由于是直接将子类原型对象修改为了父类对象,所以继承的属性值很难自定义
Student.prototype = new Person();
Student.prototype.constructor = Student;
let stu = new Student();
stu.name = 'Jason Wang';
stu.age = 18;
stu.score = 99;
stu.say(); // Jason Wang,18
stu.study(); // Learning JavaScript
// 此种方法存在的弊端:
// 在开发中,通常需要在创建对象的时候要指定或方法的取值
// 若使用此种继承,没法在创建子类对象时给其指定属性或方法的取值
借用构造函数实现继承
在子类中调用父类构造函数,并且将父类构造函数的this修改为子类对象
function Person(myName,myAge) {
this.name = myName;
this.age = myAge;
this.say = function () {
console.log(this.name, this.age);
}
}
function Student(myName,myAge,myScore) {
// let stu = new Object();
// let this = stu;
// 将Person构造函数中的this改为stu
Person.call(this,myName,myAge); // Person.call(stu)
this.score = myScore;
this.study = function () {
console.log('Learning JavaScript');
}
}
let stu = new Student('Jason Wang',24,99);
stu.say();
stu.study();
// 若动态的父类在原型中添加方法,则子类不能使用
借用构造函数和借用原型链组合继承
- 通过借用构造函数实现属性继承
- 通过借用原型链实现方法继承
function Person(myName,myAge) {
this.name = myName;
this.age = myAge;
}
// 动态的给父类添加方法
Person.prototype.say = function () {
console.log(this.name, this.age);
}
// 要想使用Person原型对象中的属性和方法,
// 那么就必须将Student的原型对象改为Person的原型对象才可以
Student.prototype = Person.prototype; // 继承Person原型对象的属性和方法
Student.prototype.constructor = Student;
function Student(myName,myAge,myScore) {
Person.call(this,myName,myAge); // 继承Person对的属性
this.score = myScore;
this.study = function () {
console.log('Learning JavaScript');
}
}
let stu = new Student('Jason Wang',24,99);
stu.say();
继承终极方案
- 在子类的构造函数中通过
call借助父类的构造函数 - 将子类的原型对象修改为父类的实例对象
function Person(myName,myAge) {
this.name = myName;
this.age = myAge;
}
Person.prototype.say = function () {
console.log(this.name, this.age);
}
function Student(myName,myAge,myScore) {
Person.call(this,myName,myAge);
this.score = myScore;
this.study = function () {
console.log('Learning JavaScript');
}
}
// 由于修改了Person原型对象的constructor属性, 所以破坏了Person的三角恋关系
// 由于Person和Student的原型对象是同一个, 所以给Student的元素添加方法, Person也新增方法
// Student.prototype = Person.prototype;
// 将子类的原型对象修改为父类的实例对象
Student.prototype = new Person();
Student.prototype.constructor = Student;
// 此时给Student(子类)添加一个方法
Student.prototype.run = function () {
console.log('running');
}
let per = new Person('Jason',23);
// 父类也可以调用子类方法
// per.run();
let stu = new Student('Jason',23,100)
stu.study(); // 实例对象自己的方法
stu.say(); // 继承父类的方法
stu.run() // 实例对象原型对象的方法
多态性
多态是指事物的多种状态
多态在编程语言中的体现
- 父类型变量保存子类型对象,父类型变量当前保存的对象不同,产生的结果也不同,在其他编程语种中,多态需要通过继承等方式实现
- 由于JavaScript语言是弱类型的语言,默认就是多态的,不需要继承就能实现,所以我们不用关注多态
function Dog() {
this.eat = function () {
console.log(" 狗吃东西");
}
}
function Cat() {
this.eat = function () {
console.log(" 猫吃东西");
}
}
function feed(animal){
animal.eat();
}
let dog = new Dog();
feed(dog); // 狗吃东西
let cat = new Cat();
feed(cat); // 猫吃东西