进击JavaScript核心 --- (3)面向对象
var student = new Object();
student.name = 'Jim Green';
student.gender = 'Male';
student.age = 8;
student.say = function() {
console.log(`My name is ${this.name}, I'm ${this.age} years old`);
} student.say(); // My name is Jim Green, I'm 8 years old
(2)、对象字面量
var student = {
name: 'Jim Green',
gender: 'Male',
age: 8,
say: function() {
console.log(`My name is ${this.name}`)
}
} student.say(); // My name is Jim Green
2、属性类型
var person = {
age: 8,
gender: 'male'
};
Object.defineProperty(person, "name", {
enumerable: false,
value: "Roger"
});
for(var key in person) {
console.log(`key --> ${key}, value --> ${person[key]}`);
} // key --> age, value --> 8
// key --> gender, value --> male
// "use strict";
var person = {};
Object.defineProperty(person, "name", {
configurable: false,
writable: true,
value: "Roger"
});
console.log(person.name); // Roger
delete person.name;
console.log(person.name); // Roger (configurable属性为false时,返回undefined) // Uncaught TypeError: Cannot delete property 'name' of #<Object> (严格模式下报错)
// "use strict"
var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Roger"
}) console.log(person.name); // Roger
person.name = "Frank";
console.log(person.name); // Roger // console.log(person.name); // Uncaught TypeError: Cannot assign to read only property 'name' of object '#<Object>'
var person = {};
Object.defineProperty(person, "name", {
configurable: true,
value: "Roger"
});
person.name = "Frank";
console.log(person.name); // Roger Object.defineProperty(person, "name", {
writable: true,
value: "Roger"
}); person.name = "Kobe";
console.log(person.name); // Kobe
var person = {};
Object.defineProperty(person, "name", {
configurable: false,
value: "Roger"
});
person.name = "Frank";
console.log(person.name); // Roger Object.defineProperty(person, "name", { // Uncaught TypeError: Cannot redefine property: name
writable: true,
value: "Roger"
}); person.name = "Kobe";
console.log(person.name);
2-2、访问器属性
访问器属性不包含数据值,它们包含一对getter和setter函数(非必需)。共包含4个特性:
var person = {
name: 'Kobe',
_number: 8
} Object.defineProperty(person, 'number', {
get: function() {
return this._number;
},
set: function(newValue) {
if(newValue > 8) {
this._number = 24;
}
}
}) person.number = 9;
console.log(person._number); //
var book = {};
Object.defineProperties(book, {
_year: {
writable: true,
value: 8
},
year: {
get: function() {
return this._year;
},
set: function(newValue) {
if(newValue > 8) {
alert('111')
this._year = 24;
}
}
}
}) console.log(book); // {_year: 8}
book.year = 9;
console.log(book); // {_year: 24}
var book = {};
Object.defineProperties(book, {
_year: {
writable: true,
value: 8
},
year: {
get: function() {
return this._year;
},
set: function(newValue) {
if(newValue > 8) {
alert('111')
this._year = 24;
}
}
}
}); var obj1 = Object.getOwnPropertyDescriptor(book, '_year');
var obj2 = Object.getOwnPropertyDescriptor(book, 'year'); console.log(obj1); // {value: 8, writable: true, enumerable: false, configurable: false}
console.log(obj2); // {get: ƒ, set: ƒ, enumerable: false, configurable: false}
// 例如:Java中定义一个Student类
public class Student { private String name; // 姓名
private int age; // 年龄 public Student(String name, int age) { //构造器
super();
this.name = name;
this.age = age;
} // 属性的getter和setter方法
public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} // 自定义方法
public void say() {
System.out.println("姓名:" + this.name + "年龄:" + this.age);
} } // 通过Student类来创建实例对象
Student xiaoMing = new Student("XiaoMing", 12);
Student xiaoHong = new Student("XiaoHong", 9); xiaoMing.say(); // 姓名:XiaoMing年龄:12
xiaoHong.say(); // 姓名:XiaoHong年龄:9
var xiaoMing = {
name: "XiaoMing",
age: 12
}; var XiaoHong = {
name: "XiaoHong",
age: 9
}
function createStudent(name, age) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.say = function() {
console.log(`My name is ${this.name}, I'm ${this.age} years old.`);
};
return obj;
} var xiaoMing = createStudent('XiaoMing', 12);
var xiaoHong = createStudent('XiaoHong', 8); xiaoMing.say(); // My name is XiaoMing, I'm 12 years old.
xiaoHong.say(); // My name is XiaoHong, I'm 8 years old.
function Student(name, age) {
this.name = name;
this.age = age;
this.say = function() {
console.log(`My name is ${this.name}, I'm ${this.age} years old.`);
};
} var xiaoMing = new Student('XiaoMing', 12);
var xiaoHong = new Student('XiaoHong', 8); xiaoMing.say(); // My name is XiaoMing, I'm 12 years old.
xiaoHong.say(); // My name is XiaoHong, I'm 8 years old.
console.log(xiaoMing.constructor) // ƒ Student(name, age) {
// this.name = name;
// this.age = age;
// this.say = function() {
// console.log(`My name is ${this.name}, I'm ${this.age} years old.`);
// };
// } console.log(xiaoMing.constructor == Student) // true
console.log(xiaoMing.constructor == xiaoHong.constructor) // true
console.log(xiaoMing instanceof Student) // true
console.log(xiaoMing instanceof Object) // true
function Student(name, age) {
this.name = name;
this.age = age;
this.say = function() {
console.log(`My name is ${this.name}, I'm ${this.age} years old.`);
};
} // 将Student当作构造函数(this指向实例对象)
var xiaoMing = new Student('XiaoMing', 12);
xiaoMing.say(); // My name is XiaoMing, I'm 12 years old. // 将Student当作普通函数(由于Student函数属于全局作用域,因此实际上是window.Student(),this指向window)
Student('Bob', 9);
say(); // My name is Bob, I'm 9 years old. // 在特定的作用域中调用函数(this指向obj)
var obj = new Object();
Student.call(obj, 'Ryan', 30);
console.log(obj); // {name: "Ryan", age: 30, say: ƒ}
obj.say(); // My name is Ryan, I'm 30 years old.
// 上面的say方法等价于
function Student(name, age) {
this.name = name;
this.age = age;
this.say = new Function(`My name is ${this.name}, I'm ${this.age} years old.`);
}
function Student(name, age) {
this.name = name;
this.age = age;
this.say = say;
}
function say() {
console.log(`My name is ${this.name}, I'm ${this.age} years old.`);
} var xiaoHong = new Student('XiaoHong', 8);
xiaoHong.say(); // My name is XiaoHong, I'm 8 years old.
这样做的话同样存在问题,首先是封装性不太好,对象的某些属性必须依赖于全局的属性;其次,我们期望全局作用域内的函数say只能用于构造函数Student,这样就跟js的理念相冲突了
3-3、原型模式
function Student() { }
Student.prototype.name = 'Bob';
Student.prototype.age = 12;
Student.prototype.say = function() {
console.log(`My name is ${this.name}, I'm ${this.age} years old.`);
} var xiaoMing = new Student();
var xiaoHong = new Student(); xiaoMing.say(); // My name is Bob, I'm 12 years old.
xiaoHong.say(); // My name is Bob, I'm 12 years old.
console.log(xiaoMing.__proto__); // {name: "Bob", age: 12, say: ƒ, constructor: ƒ}
console.log(xiaoMing.__proto__ === Student.prototype); // true
console.log(Student.prototype.isPrototypeOf(xiaoMing)); // true
console.log(Student.prototype.isPrototypeOf(xiaoHong)); // true
// 说明实例对象 xiaoMing和xiaoHong都存在于Student.prototype的原型链上
console.log(Object.getPrototypeOf(xiaoHong)); // {name: "Bob", age: 12, say: ƒ, constructor: ƒ}
console.log(Object.getPrototypeOf(xiaoHong) === Student.prototype); // true
xiaoMing.name = 'XiaoMing'; console.log(xiaoMing.name); // XiaoMing
console.log(xiaoHong.name); // Bob
为了进一步对比,可以删除掉实例对象 xiaoMing 的属性name,然后再访问该属性
delete xiaoMing.name; console.log(xiaoMing.name); // Bob
console.log(xiaoHong.name); // Bob
function Student() { }
Student.prototype = {
name: 'Bob',
age: 12,
course: ['Chinese', 'Math']
} var xiaoMing = new Student();
var xiaoHong = new Student(); xiaoMing.name = 'XiaoMing';
console.log(xiaoMing.name); // XiaoMing
console.log(xiaoHong.name); // Bob xiaoMing.course.push('English');
console.log(xiaoMing.course); // ["Chinese", "Math", "English"]
console.log(xiaoHong.course); // ["Chinese", "Math", "English"]
xiaoMing.name = 'XiaoMing'; console.log(xiaoMing.hasOwnProperty('name')); // true --- 来自实例
console.log(xiaoHong.hasOwnProperty('name')); // false --- 来自原型
// 实例对象xiaoMing有自己的name属性,xiaoHong则没有
xiaoMing.name = 'XiaoMing'; console.log('name' in xiaoMing); // true
console.log(xiaoMing.hasOwnProperty('name')); // true console.log('name' in xiaoHong); // true
console.log(xiaoHong.hasOwnProperty('name')); // false
xiaoMing.name = 'XiaoMing'; // 判断一个属性仅存在于对象的原型中
function checkPropertyInPrototype(Object, prop) {
return (prop in Object) && !Object.hasOwnProperty(prop)
} console.log(checkPropertyInPrototype(xiaoMing, 'name')); // false
console.log(checkPropertyInPrototype(xiaoHong, 'name')); // true
function Student() { }
Student.prototype.name = 'Bob';
Student.prototype.age = 12;
Student.prototype.say = function() {
console.log(`My name is ${this.name}, I'm ${this.age} years old.`);
} var xiaoHong = new Student();
xiaoHong.gender = 'female'; for(var prop in xiaoHong) {
console.log(prop +' --> '+ xiaoHong[prop]);
} /*
gender --> female
name --> Bob
age --> 12
say --> function() {
console.log(`My name is ${this.name}, I'm ${this.age} years old.`);
}
*/
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
say: function() {
console.log(`My name is ${this.name}, I'm ${this.age} years old.`);
}
} var xiaoMing = new Person('XiaoMing', 12);
var xiaoHong = new Person('xiaoHong', 9); xiaoMing.say(); // My name is XiaoMing, I'm 12 years old.
xiaoHong.say(); // My name is xiaoHong, I'm 9 years old.
4、继承
JS中的继承主要是依靠原型链来实现的
4-1、原型链
简单回顾下构造函数、原型和实例对象的关系
(1)、每个构造函数都包含一个prototype属性指向原型对象
(2)、每个原型对象都包含一个constructor指针指向构造函数
(3)、每个实例都包含一个[[prototype]]指针指向构造函数的原型对象
原型链的基本思想就是利用原型,让一个引用类型继承另一个引用类型的属性和方法
具体说就是让原型对象等于另一个类型的实例,由于该实例包含指向其原型对象的指针,因此,此时的原型对象也包含了另一个原型对象的 指针。层层向上直到一个对象的原型对象是null。根据定义,null没有原型,并作为原型链中的最后一个环节
function Person() { }
Person.prototype = {
leg: 4,
ear: 2
} function Student() { } // 让Student的原型对象等于Person的实例,因此Student的原型对象也包含了指向Person的原型对象的指针
Student.prototype = new Person(); // 给Student的原型对象添加方法
Student.prototype.say = function() {
console.log(`I have ${this.ear} ears and ${this.leg} legs.`);
} var student = new Student();
student.say(); // I have 2 ears and 4 legs.
console.log(student.toString()); // [object Object] console.log(Object.getPrototypeOf(student) === Student.prototype); // true (student实例的原型对象就是Student.prototype)
console.log(Person.prototype.isPrototypeOf(student)); // true (student实例存在于Person对象的原型链上)
console.log(student instanceof Person); // true (student就是Person对象的实例)
console.log(student.hello()); // Uncaught TypeError: student.hello is not a function
图中所示,红色的粗线条代表的就是原型链,绿色细线条代表是构造函数与其原型对象之间的关联。
执行 student.say(),student实例自己没有say方法,于是沿着原型链在它的原型对象是找到了,但是该原型对象中并没有ear和leg属性,由于该原型对象包含了指向Person原型对象的指针,因此,继续沿着原型链向上查找,在Person的原型对象上找到了ear和leg属性
执行 student.toString(),student实例没有提供该方法,于是沿着原型链逐级向上查找,由于所有引用类型都继承自Object,最终在Object的原型对象上找到了
执行 student.hello(),student实例没有提供该方法,沿着原型链逐级向上查找,都没有找到该方法,因此会报错
注意:
(1)、通过原型链实现继承时,不能使用对象字面量创建原型方法,否则会重写原型链
function Person() { }
Person.prototype = {
say: function() {
console.log('hello world');
}
} function Student() { } // 让Student的原型对象等于Person的实例,因此Student的原型对象也包含了指向Person的原型对象的指针
Student.prototype = new Person(); // 使用对象字面量给Student创建原型方法
Student.prototype = {
class: 2,
grade: 1
} var student = new Student();
student.say(); // Uncaught TypeError: student.say is not a function
此例中,先是把Person的实例赋值给Student的原型对象,构建出了一条图中红色粗线部分显示的原型链。然后,使用对象字面量的方法使得Student的原型对象指向了一个实例对象,切断了原来的原型链,重新构建出图中蓝色粗线部分的原型链,自然就找不到say方法了
(2)、给原型添加方法的代码一定要放在替换原型的语句之后。如子类型重写父类型中的某个方法,或在子类型中添加一个父类型中不存在的方法
function Person() { }
Person.prototype = {
say: function() {
console.log('hello world');
}
} function Student() { } // 让Student继承Person
Student.prototype = new Person(); // 子类型重写父类型中的方法
Student.prototype.say = function() {
console.log(`I'm Iron man`)
} // 子类型中添加一个父类型中不存在的方法
Student.prototype.walk = function() {
console.log('walk with legs')
} var student = new Student(); student.say(); // I'm Iron man
student.walk(); // walk with legs
首先确定了Student继承Person这一继承关系,Student原型对象可以读取到Person原型对象中的say方法,然后为Student原型对象添加的重写和新方法,会覆盖掉原来的say方法
function Person() { }
Person.prototype = {
say: function() {
console.log('hello world');
}
} function Student() { } // 子类型重写父类型中的方法
Student.prototype.say = function() {
console.log(`I'm Iron man`)
} // 子类型中添加一个父类型中不存在的方法
Student.prototype.walk = function() {
console.log('walk with legs')
} // 让Student继承Person
Student.prototype = new Person(); var student = new Student(); console.log(Person.prototype.isPrototypeOf(student)); // true
student.say(); // hello world
student.walk(); // Uncaught TypeError: student.walk is not a function
与前面唯一的区别就是继承关系是在Student原型对象添加方法之后确定的,尽管student实例和Person的原型对象依然在同一条原型链上,但是会用Person原型对象中的属性和方法覆盖掉Student原型对象中的属性和方法,导致输出和前面的不一样
原型链弊端:
(1)、针对属性值是引用类型的情况,当某一个实例对象改变该共享属性时,其它实例也会随之改变
function Person() {
this.course = ['chinese', 'math'];
}
function Student() { }
Student.prototype = new Person(); var student1 = new Student(); console.log(student1.course); // ["chinese", "math"] student1.course.push('english');
var student2 = new Student();
console.log(student2.course); // ["chinese", "math", "english"]
(2)、创建子类型的实例时,不能向父类型的构造函数中传递参数。实际上就是一旦给父类型的构造函数传递参数,就会影响所有的实例对象
function Person(name, age) {
this.name = name;
this.age = age;
}
function Student() { }
// 此处调用父类型的构造函数是需要传参的
Student.prototype = new Person('Bob', 12); var s1 = new Student();
var s2 = new Student(); console.log(s1.name); // Bob
console.log(s2.name); // Bob
4-2、借用构造函数
用于解决原型中包含引用类型值所带来的问题
基本思想是在子类型构造函数中调用父类型的构造 函数,通过call()或apply()方法在(将来)新创建的对象上执行构造函数
function Person(name, age) {
this.name = name;
this.age = age;
this.say = function() {
console.log(`My name is ${this.name}, I'm ${this.age} years old`)
}
} function Student(name, age, grade) {
Person.call(this, name, age);
this.grade = grade;
}
function Teacher(name, age, height) {
Person.apply(this, [name, age]);
this.height = height;
} var student = new Student("Jim", 9, 4);
var teacher = new Teacher('Mr Lee', 33, 1.75); student.say(); // My name is Jim, I'm 9 years old
teacher.say(); // My name is Mr Lee, I'm 33 years old
通过这种方式,子类型不仅可以继承父类型,还可以向父类型构造函数传递参数
如何解决原型中包含引用类型值带来的问题?
function Person() {
this.course = ['chinese', 'math'];
}
function Student() {
Person.call(this);
} var s1 = new Student(); console.log(s1.course); // ["chinese", "math"]
s1.course.push('english');
console.log(s1.course); // ["chinese", "math", "english"] var s2 = new Student();
console.log(s2.course); // ["chinese", "math"]
利用call方法将this绑定到了实例对象,所以即便修改了引用类型的值,也只是在实例对象的作用域范围内,不影响其他实例
弊端:方法都在构造函数中定义,如果在父类型的原型对象中定义方法,对子类型还是不可见的
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function() {
console.log(`My name is ${this.name}`);
} function Student(name, age) {
Person.call(this, name, age);
} var s = new Student("Jim", 2);
s.say(); // Uncaught TypeError: s.say is not a function
4-3、组合继承
就是将原型链和借用构造函数两种方法组合在一起实现继承,这也是JS中最常用的继承模式
思路:使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承
function Person(name) {
this.name = name;
this.course = ['chinese', 'math'];
}
Person.prototype.say = function() {
console.log(`My name is ${this.name}`);
} function Student(name, age) {
Person.call(this, name); // 继承实例属性
this.age = age;
} // 继承原型属性和方法
Student.prototype = new Person(); // 添加子类方法
Student.prototype.sayAge = function() {
console.log(`I'm ${this.age} years old`);
} // 重写父类方法
Student.prototype.say = function() {
console.log(`My name is ${this.name}, I'm ${this.age} years old`);
} var s1 = new Student('Bob', 8); // 修改引用类型值的属性
s1.course.push('english'); s1.sayAge(); // I'm 8 years old
s1.say(); // My name is Bob, I'm 8 years old
console.log(s1.course); // ["chinese", "math", "english"] var s2 = new Student('Jim', 11);
console.log(s2.course); // ["chinese", "math"]
进击JavaScript核心 --- (3)面向对象的更多相关文章
- 进击JavaScript核心 --- (1)基本数据类型
ES5之前提供了 5种基本数据类型 和 1种引用数据类型 基本数据类型:Undefined, Null, String, Number, Boolean 引用数据类型:Object ES6开始引入了一 ...
- 进击JavaScript核心 --- (2)函数和预解析机制
一.函数 每个函数都是 Function类型的实例,也具有属性和方法.由于函数也是一个对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定 1.函数的定义方式 (1).函数声明 fun ...
- javascript 核心语言笔记- 3 - 类型、值和变量
JavaScript 中的数据类型分为两类:原始类型(primitive type)和对象类型(object type).原始类型包括数字.字符串和布尔值 JavaScript 中有两个特殊的原始值: ...
- JavaScript 核心参考教程 内置对象
这个标准基于 JavaScript (Netscape) 和 JScript (Microsoft).Netscape (Navigator 2.0) 的 Brendan Eich 发明了这门语言,从 ...
- JavaScript 核心学习——继承
本篇博文讲述如何在 JavaScript 中实现继承,以及原型与原型链的知识,在附录中将会讲述 JavaScript 面向对象的常见错误. ##原型与原型链在 JavaScript 中,使用类将会付出 ...
- 前端开发:面向对象与javascript中的面向对象实现(二)构造函数与原型
前端开发:面向对象与javascript中的面向对象实现(二)构造函数与原型 前言(题外话): 有人说拖延症是一个绝症,哎呀治不好了.先不说这是一个每个人都多多少少会有的,也不管它究竟对生活有多么大的 ...
- 简单分析JavaScript中的面向对象
初学JavaScript的时候有人会认为JavaScript不是一门面向对象的语言,因为JS是没有类的概念的,但是这并不代表JavaScript没有对象的存在,而且JavaScript也提供了其它的方 ...
- 前端开发:面向对象与javascript中的面向对象实现(一)
前端开发:面向对象与javascript中的面向对象实现(一) 前言: 人生在世,这找不到对象是万万不行的.咱们生活中,找不到对象要挨骂,代码里也一样.朋友问我说:“嘿,在干嘛呢......”,我:“ ...
- 最新的JavaScript核心语言标准——ES6,彻底改变你编写JS代码的方式!【转载+整理】
原文地址 本文内容 ECMAScript 发生了什么变化? 新标准 版本号6 兑现承诺 迭代器和for-of循环 生成器 Generators 模板字符串 不定参数和默认参数 解构 Destructu ...
随机推荐
- Jmeter之TCP取样器(模拟数据上报压测)
TCP压测 场景:模拟硬件设备上报数据(登录,心跳,GPS定位数据/光感数据/电量数据),对这个功能进行压测 啰嗦一句:TCP压测很简单,只要调通了一个TCP,后续的逻辑判断就用逻辑控制器和正则处理就 ...
- 怎样理解 Vue 项目的目录结构?
Vue 项目的目录结构如下, 我们将会在后面逐个去了解它们的作用: 01. build - 存储项目构建相关的代码, 比如 webpack. 02. config - Vue 的配置目录,包括端口 ...
- C# WebApi日期格式化
WebApi中日期格式化:在WebApiConfig文件中加入如下代码即可,之前遇到的问题,日期中总带有T,现在记录一下解决的方法. 代码: private static void ReturnDat ...
- centos配置发送邮件
邮件已经可以接收到了 CentOS下发送邮件有很多种方法,这里采用比较简单的mail客户端,配置第三方邮件服务商,例如:163.com 1.安装所用软件 yum install mailx sendm ...
- 【Zabbix】分布式监控系统Zabbix【二】
一.Zabbix基本操作 1.主机群组.主机.模板.触发器 a.创建主机群组和主机的过程比较简单,不再介绍 b.配置模板: 创建一个模板,将其分组到Template组,添加配置应用: 给应用创建监控项 ...
- Java学习笔记【十二、网络编程】
原计划的学习结束时间是3月4日,目前看来已经延迟了,距离低标还差一些,多方面原因,也不找借口,利用周末赶赶进度,争取本周末把低标完成吧! 参考: http://www.runoob.com/java/ ...
- 【异常】553 Mail from must equal authorized user
1 详细异常打印 2019-08-12 14:54:42,178 ERROR org.apache.camel.processor.DefaultErrorHandler: Failed delive ...
- servlet遇到的问题
1 创建web项目没有xml自动生成 2 servlet 忽然报奇怪500错误 出现的BUG原因 JAVA bean没有设置 自动导入了其他User包
- Hadoop_04_Hadoop 的HDFS客户端shell命令
1.Hdfs shell客户端命令操作: 1.1.查看命令列表:hadoop fs 帮助如下: Usage: hadoop fs [generic options] [-appendToFile &l ...
- C++之旋转矩阵和打印一个有规律的矩阵
旋转数组 描述: 某个图像通过一个整数组成的m*n矩阵表示,其中每个整数表示一个像素值.写出一种方法,根据flag变量的值将图像向右或者向左旋转90°.如果flag值为0,则向左旋转,如果flag为1 ...