跳到主要内容

14-JavaScript三大特性

JavaScript封装性

  1. 首先需要回顾局部变量局部函数的知识
  2. 对象的私有变量与函数
    • 默认情况下对象中的属性和方法都是公有的, 只要拿到对象就能操作对象的属性和方法
    • 外界不能直接访问的变量和函数就是私有变量和是有函数,构造函数的本质也是一个函数,所以也会开启一个新的作用域,所以在构造函数中定义的变量和函数就是私有变量和函数
  3. JavaScript的封装性运用
    • 封装性就是隐藏实现细节,仅对外公开接口
    • 暴露属性的缺点:当一个类把自己的成员变量暴露给外部的时候,那么该类就失去对属性的管理权,别人可以任意的修改你的属性
    • 封装优点:将数据隐藏起来,只能用此类的方法才可以读取或者设置数据,不可被外部任意修改. 封装是面向对象设计本质(将变化隔离)。这样降低了数据被误用的可能 (提高安全性和灵活性)
function Person() {
this.name = "lnj";
// this.age = 34; // 定义一个属性,运用函数进行控制
let age = 34;
this.setAge = function (myAge) {
if(myAge >= 0){
age = myAge;
}
}
this.getAge = function () {
return age;
}
this.say = function () {
console.log("hello world");
}
}
let obj = new Person();
obj.setAge(-3); // 设置将失效,返回age默认值
console.log(obj.getAge());

封装私有属性

  • 在给一个对象不存在的属性设置值的时候, 不会去原型对象中查找, 如果当前对象没有就会给当前对象新增一个不存在的属性
  • 由于私有属性的本质就是一个局部变量, 并不是真正的属性, 所以如果通过对象.xxx的方式是找不到私有属性的, 所以会给当前对象新增一个不存在的属性
function Person() {
this.name = "lnj";
let age = 34; // age 局部变量,外界无法访问
this.setAge = function (myAge) { // this.xxx 私有属性
if (myAge >= 0) {
age = myAge;
}
}
this.getAge = function () {
return age;
}
this.say = function () {
console.log("hello world");
}
}
let obj = new Person();
// 1.操作的是私有属性(局部变量)
obj.setAge(-3);
console.log(obj.getAge());

// 2.操作的是公有属性
obj.age = -3;
console.log(obj.age);

属性方法分类

两种:

  • 实例属性/实例方法(通过实例对象访问属性/调用方法,称之为实例属性/实例方法
  • 静态属性/静态方法(通过构造函数访问属性/调用方法,称之为静态属性/静态方法
function Person() {
this.name = "lnj";
this.say = function () {
console.log("hello world");
}
}

let obj = new Person(); // 创建实例对象
console.log(obj.name); // 访问实例属性
obj.say(); // 访问实例方法

obj.age = 34; // 可以动态添加属性
console.log(obj.age);
obj.eat = function () { // 动态添加方法
console.log("eat");
}
obj.eat();

构造函数也是一个"对象", 所以我们也可以给构造函数动态添加属性和方法

function Person() {}

Person.num = 666;
Person.run = function () {
console.log("run");
}
console.log(Person.num);
Person.run();

JavaScript继承性

  1. 儿子继承父亲的物品就是继承最好的体现
  2. js中继承目的: 把子类型中共同的属性和方法提取到父类型中
  3. 较少代码的冗余度, 提升代码的复用性

在企业开发中如果构造函数和构造函数之间的关系是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("day day up");
}
}

Student.prototype = new Person(); // 将Stu构造函数指向person实例对象
Student.prototype.constructor = Student; // 将Stu构造函数的原型对象指向stu,避免破坏原型链
let stu = new Student();
stu.name = "zs";
stu.age = 18;
stu.score = 99;
stu.say();
stu.study();

运用继承性的三个方法

回顾知识:将函数赋值给变量,称为方法。谁调用当前函数或者方法, this就是谁,若直接调用函数this则是window

这三个方法都是用于修改函数或者方法中的this

bind方法作用
  • 修改函数或者方法中的this为指定的对象, 会返回一个修改后的新函数给我们,需要重新赋值
  • bind方法除了可以修改this以外, 还可以传递参数, 只不过参数必须写在this对象的后面
let obj = {
name: "zs"
}
function test(a, b) {
console.log(a, b);
console.log(this);
}
test(10, 20); // 如果将数据参数存入函数的默认值,this为window
let fn = test.bind(obj, 10, 20); // 调用bind存入对象和函数默认值,this为obj
fn()

call方法作用
  • 与bind作用与注意点相近,不同在于修改this对象后,会立即调用修改后的函数
let obj = {
name: "zs"
}
function test(a, b) {
console.log(a, b);
console.log(this);
}

test.call(obj, 10, 20); // 与bind作用相同,不同的是会立即调用修改后的函数
apply方法作用
  • 与call作用与注意点相近,不同在于修改this对象后,传参必须是以数组的形式进行
let obj = {
name: "zs"
}
function test(a, b) {
console.log(a, b);
console.log(this);
}

test.apply(obj, [10, 20]); // 数组形式的参数
修改方法中的this
let obj = {
name: "zs"
}
function Person() {
this.name = "lnj";
this.say = function () {
console.log(this);
}
}
let p = new Person(); // 需要创建变量
let fn = p.say.bind(obj); // 与上述三种使用方法相同 // p.say.call(obj);
fn(); // p.say.apply(obj);

继承性方式《推演二》

通过修改父级构造函数中的this,借助父级构造函数中的代码来实现目的,两者构造函数之间没有任何关系

function Person(myName, myAge) {
// let per = new Object();
// let this = per;
// this = stu;
this.name = myName; // stu.name = myName;
this.age = myAge; // stu.age = myAge;
this.say = function () { // stu.say = function () {}
console.log(this.name, this.age);
}
// return this;
}
function Student(myName, myAge, myScore) {
// let stu = new Object();
// let this = stu;
Person.call(this, myName, myAge); // 将Preson中的this 赋值给 stu ;
this.score = myScore;
this.study = function () {
console.log("day day up");
}
// return this;
}
let stu = new Student("ww", 19, 99);
// stu.name = "zs";
// stu.age = 18;
// stu.score = 99;
console.log(stu.score);
stu.say();
stu.study();

继承性方式《推演三》

由于**继承性推演《二》**中,需要单独添加stu.say的方法创建Per的原型对象,假如重复调用Per的构造函数会遇到代码的局限性,可以通过添加原型对象动态添加原型对象来解决问题

function Person(myName, myAge) {
this.name = myName;
this.age = myAge;
}
// 将Per添加动态原型对象
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("day day up");
}
}
// 注意点: 使用Person原型对象中的属性和方法, 那么就必须将Student的原型对象改为Person的原型对象才可以
Student.prototype = Person.prototype;
Student.prototype.constructor = Student;

let stu = new Student("ww", 19, 99);
console.log(stu.score);
stu.say();
stu.study();

弊端:

  1. 破坏了父类的原型链关系
  2. 父和子的原型对象是同一个, 所以给子元素添加方法, 父也会新增方法

继承性方式《推演四-最终》

  1. 在子类的构造函数中通过call借助父类的构造函数
  2. 将子类的原型对象修改为父类的实例对象

完整格式

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("day day up");
}
}
Student.prototype = new Person();
Student.prototype.constructor = Student;
Student.prototype.run = function(){
console.log("run");
}

let per = new Person();
per.run();

JavaScript多态性

强类型语言弱类型语言
一般编译型语言都是强类型语言一般解释型语言都是弱类型语言
要求变量的使用要严格符合定义不会要求变量的使用要严格符合定义
例如定义int num;那么num中将来就只能够存储整型数据例如定义 let num; num中既可以存储整型, 也可以存储布尔类型等
  1. 多台性的定义指事物的多种状态
  2. 多态在编程语言中的体现:父类型变量保存子类型对象, 父类型变量当前保存的对象不同, 产生的结果也不同

默认情况下,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);