(79)Wangdao.com第十五天_JavaScript 对象的继承_prototype原型对象_封装_函数式编程
javascript 内置了许多 function 函数(){...}
js 执行首先就会执行自己内置的函数定义 (function Function、function Object)
对象的继承
大部分面向对象的编程语言,都是通过“类”(class)实现对象的继承。
传统上,JavaScript 语言的继承不通过 class,而是通过“原型对象”(prototype)实现,称之为 JavaScript 的原型链继承
JavaScript 继承机制的设计思想就是,原型对象 prototype 的所有属性和方法,都能被实例对象共享
ES6 引入了 class 语法
- 构造函数的缺点
- 同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费
function Cat(name, color) {
this.name = name;
this.color = color;
this.bar = function () {
console.log('喵喵');
};
} var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色'); cat1.meow === cat2.meow // falsecat1
和cat2
是同一个构造函数的两个实例,它们都具有 bar 方法。由于 bar 方法是生成在每个实例对象上面,所以两个实例就生成了两次。
- 也就是说,每新建一个实例,就会新建一个 bar方法。这既没有必要,又浪费系统资源,因为所有 bar 方法都是同样的行为,完全应该共享
- 这个问题的解决方法,就是 JavaScript 的原型对象(prototype)
- JavaScript 的原型对象
- JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享
- 属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系
- JavaScript 规定,每个函数都有一个
prototype
属性,指向一个对象。- 对于普通函数来说,该属性基本无用。
- 但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型
function Animal(name) {
this.name = name;
}
Animal.prototype.color = 'white'; var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛'); cat1.color // 'white'
cat2.color // 'white'
- 原型对象的作用,就是定义所有实例对象共享的属性和方法。
- 在函数创建时,都会默认创建 显示原型对象
读取属性/方法,沿着原型链找
设置属性/ 方法,只会查看和影响自身
所有函数都具有 prototype 显式原型属性,指向一个对象____原型对象
- function Dog(){};
所有对象都是某个构造函数的实例,都拥有 __proto__隐式原型属性
- var wangCai = new Dog("旺财", "2");
注意:
- 由于 函数 也是一个对象,所以 函数 也拥有一个 __proto__隐式原型属性,由原生底层语言实现
constructor 构造函数____等于函数本身
__proto__ 隐式原型属性____指向 该对象的构造函数的原型对象 prototype (即 隐式原型属性 指向 上一级对象的原型对象)
即 同一构造函数的 所有实例对象 都有一个 隐式原型 指向同一个原型对象
- 也就是说,可以在 构造函数 定义时,存放一些实例对象 公共方法 和 公共属性
- 底层 native code 用 c/c++ 实现
- 所有 函数 都有一个 prototype 显示原型属性
所有函数都是 function Function(){...} 的实例
- 所有 实例对象 都有一个 __proto__ 隐式原型属性 (包括原型对象都有一个 __proto__ )
- 所有 原型对象 都有 constructor 属性指向 构造函数
- 原型链
- 当调用某对象的属性或者方法时,沿着 __proto__ 这条链向上查找,
- 首先在自身作用域中寻找,
- 然后到原型对象中寻找,
- 再到原型对象的原型对象中寻找,直到找到 Object 。
- 如果始终没找到就返回 undefined。
- 如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性____这叫做 “覆盖”(overriding)
- JavaScript 规定,所有对象都有自己的 隐式原型属性 __proto__ 指向 new 构造函数的 原型对象
- 一方面,任何一个对象,都可以充当其他对象的原型;
- 另一方面,由于原型对象也是对象,所以它也有自己的原型。
- 因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……
- Object.prototype 的原型是 null ,因此,原型链的尽头就是 null
- 所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
/*
面试题
*/
var A = function() { } A.prototype.n = 1 var b = new A() A.prototype = { // 改变的 只是一个地址值,而不会改变 真正对象 的存在(b 始终指向那个 原始的 prototype 对象)
n: 2,
m: 3
} var c = new A()
console.log(b.n, b.m, c.n, c.m)
- 举例来说,如果让构造函数的 prototype 属性指向一个数组,就意味着实例对象可以调用数组方法
- prototype 对象有一个 constructor 属性,默认指向 prototype 对象所在的构造函数
- constructor 属性的作用
- 可以得知某个实例对象,到底是哪一个构造函数产生的
- 有了 constructor 属性,就可以从一个实例对象新建另一个实例
function Constr() {}
var x = new Constr(); var y = new x.constructor();
y instanceof Constr; // true这使得在实例方法中,调用自身的构造函数成为可能
Constr.prototype.createCopy = function () {
return new this.constructor();
};
- constructor 属性表示原型对象与构造函数之间的关联关系,如果修改了原型对象,一般会同时修改 constructor 属性,防止引用的时候出错
- 要么将 constructor 属性重新指向原来的构造函数,要么只在原型对象上添加方法,这样可以保证 instanceof 运算符不会失真
- 如果不能确定 constructor 属性是什么函数,还有一个办法:通过 name 属性,从实例得到构造函数的名称。
function Foo() {}
var f = new Foo();
f.constructor.name // "Foo"
- 当调用某对象的属性或者方法时,沿着 __proto__ 这条链向上查找,
所有 函数 都是 function Function(){} 的实例对象,
包括 Object 的构造函数 的 __proto__ 都指向 Function 的原型对象
- 甚至 Function 自己的构造函数 的 __proto__ 都指向 Function 的原型对象
console.log(Object instanceof Function); // true
console.log(Object instanceof Object); // true
console.log(Function instanceof Function); // true
console.log(Function instanceof Object); // true
- instanceOf 运算符
- A instanceof B 如果 B 函数的显示原型对象 在 A 对象的 __proto__ 原型链上,则返回 true,否则返回 false
- 返回一个布尔值,表示对象是否为某个构造函数的实例
var v = new Vehicle();
v instanceof Vehicle; // true实例对象 instanceOf 构造函数
- 它会检查右边构建函数的原型对象(prototype),是否在左边对象的原型链上
v instanceof Vehicle
// 等同于
Vehicle.prototype.isPrototypeOf(v)
- 它会检查右边构建函数的原型对象(prototype),是否在左边对象的原型链上
- 有一种特殊情况,就是左边对象的原型链上,只有
null
对象。这时,instanceof
判断会失真var obj = Object.create(null);
typeof obj; // "object"
Object.create(null) instanceof Object; // false因此,只要一个对象的原型不是null,instanceof运算符的判断就不会失真。
- 使用 instanceOf 判断一个变量的类型
var x = [1, 2, 3];
var y = {};
x instanceof Array // true
y instanceof Object // true注意,instanceof 运算符只能用于对象,不适用原始类型的值。
- 对于undefined和null,instanceOf运算符总是返回false
- 利用 instanceof 运算符,还可以巧妙地解决,调用构造函数时,忘了加 new 命令的问题
function Fubar (foo, bar) {
if (this instanceof Fubar) {
this._foo = foo;
this._bar = bar;
} else {
return new Fubar(foo, bar);
}
}
- 构造函数的继承
- 原型链
让一个构造函数继承另一个构造函数,是非常常见的需求。这可以分成两步实现
- 在子类的构造函数中,调用父类的构造函数
- 让子类的原型指向父类的原型,这样子类就可以继承父类原型
function Sub(value) {
Super.call(this);
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub; this.name = value;
Sub.prototype.method = '...';
}另外一种写法是
Sub.prototype
等于一个父类实例,但是子类会具有父类实例的方法。有时,这可能不是我们需要的,所以不推荐使用这种写法Sub.prototype = new Super();
- 有时只需要单个方法的继承,这时可以采用下面的写法
ClassB.prototype.print = function() {
ClassA.prototype.print.call(this);
// some code
}这就等于继承了父类A的 print 方法
- 封装——函数式编程
- 在 ES6 加入 class 关键字之前的编程方式
/**** 旨在实现封装的前提下,最少占用内存 ****/
// 封装 父类
function Parent(name, age){
this.name = name;
this.age = age;
}; Parent.prototype.setName = function(name){
this.name = name;
}; Parent.prototype.setAge = function(age){
this.age = age;
}; // 封装 子类
function Child(name, age){
Parent.call(this, name, age); // 继承父类的属性
this.isCrying = false;
}; Child.prototype = new Parent(); // 继承父类的方法
Child.prototype.constructor = Child; // 修正构造器指向
- 多重继承
- JavaScript 不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过变通方法,实现这个功能
function M1() {
this.hello = 'hello';
}function M2() {
this.world = 'world';
}function S() {
M1.call(this);
// 继承 M1
S.prototype = Object.create(M1.prototype);M2.call(this);
// 继承链上加入 M2
Object.assign(S.prototype, M2.prototype);// 指定构造函数
S.prototype.constructor = S;
}var s = new S();
s.hello // 'hello'
s.world // 'world'子类 S 同时继承了父类 M1 和 M2 。这种模式又称为 Mixin(混入)
- JavaScript 不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过变通方法,实现这个功能
- 多重继承
- 模块
- JavaScript 不是一种模块化编程语言,ES6 才开始支持 “类” 和 “模块”
- ES5 中传统方法实现模块
- 模块是实现特定功能的一组属性和方法的封装
- 简单的做法是把模块写成一个对象,所有的模块成员都放到这个对象里面
- 但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。
var module1 = new Object({
_count : 0,
m1 : function (){
//...
},
m2 : function (){
//...
}
});
- 可以利用构造函数,封装私有变量
function StringBuilder() {
var buffer = []; this.add = function (str) {
buffer.push(str);
}; this.toString = function () {
return buffer.join('');
};
}- 构造函数有双重作用,既用来塑造实例对象,又用来保存实例对象的数据
- 背了构造函数与实例对象在数据上相分离的原则(即实例对象的数据,不应该保存在实例对象以外。同时,非常耗费内存)
- 解决:
function StringBuilder() {
this._buffer = [];
StringBuilder.prototype = {
constructor: StringBuilder,
add: function (str) {
this._buffer.push(str);
},
toString: function () {
return this._buffer.join('');
}
};
}以上代码将私有变量放入实例对象中,好处是看上去更自然,但是它的私有变量可以从外部读写,不是很安全
- 模块是实现特定功能的一组属性和方法的封装
- 模块
- 使用 “立即执行函数”(Immediately-Invoked Function Expression,IIFE),将相关的属性和方法封装在一个函数作用域里面,可以达到不暴露私有成员的目的
var module1 = (function () {
var age = 0;
var getAge = function () {
return this.age;
};
var setAge = function (age) {
this.age = age;
};
return {
getAge : getAge,
setAge : setAge
};
})();使用上面的写法,外部代码无法直接获取内部的
_count
变量。
- 使用 “立即执行函数”(Immediately-Invoked Function Expression,IIFE),将相关的属性和方法封装在一个函数作用域里面,可以达到不暴露私有成员的目的
- 模块的 放大模式 (argumentation)
- 如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用“放大模式”。
var module1 = (function (mod){
mod.m3 = function () {
//...
};
return mod;
})(module1);上面的代码为 module1 模块添加了一个新方法 m3(),然后返回新的 module1 模块。
- 在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。
- 如果采用上面的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用 "宽放大模式"(Loose augmentation)
var module1 = (function (mod) {
//...
return mod;
})(window.module1 || {});与"放大模式"相比,“宽放大模式”就是“立即执行函数”的参数可以是空对象
- 如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用“放大模式”。
- 模块的 放大模式 (argumentation)
- 输入全局变量
- 独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互
- 为了在模块内部调用全局变量,必须显式地将其他变量输入模块
var module1 = (function ($, YAHOO) {
//...
})(jQuery, YAHOO);上面的
module1
模块需要使用 jQuery 库和 YUI 库,就把这两个库(其实是两个模块)当作参数输入module1
。这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显
- 立即执行函数还可以起到命名空间的作用
(function($, window, document) { function go(num) {
} function handleEvents() {
} function initialize() {
} function dieCarouselDie() {
} //attach to the global scope
window.finalCarousel = {
init : initialize,
destroy : dieCouraselDie
} })( jQuery, window, document );上面代码中,finalCarousel 对象输出到全局,对外暴露 init() 和 destroy() 接口,
内部方法 go()、handleEvents()、initialize()、dieCarouselDie() 都是外部无法调用的。
- 输入全局变量
(79)Wangdao.com第十五天_JavaScript 对象的继承_prototype原型对象_封装_函数式编程的更多相关文章
- (78)Wangdao.com第十五天_JavaScript 面向对象
面向对象编程(Object Oriented Programming,缩写为 OOP) 是目前主流的编程范式. 是单个实物的抽象, 是一个容器,封装了属性(property)和方法(method),属 ...
- (77)Wangdao.com第十五天_JavaScript 用于数据交换的文本格式 JSON 对象
JSON 对象 JSON (JavaScript Object Notation 的缩写) 也是一种数据,是 JavaScript 的原生对象,用来处理 JSON 格式数据.它有两个静态方法:JSON ...
- (76)Wangdao.com第十四天_JavaScript 正则表达式对象 RegExp
RegExp Regular Expression,正则表达式是一种表达 文本模式(字符串结构) 的式子. 常常用来按照“给定模式”匹配文本.比如,正则表达式给出一个 Email 地址的模式, ...
- (92)Wangdao.com_第二十五天_线程机制_H5 Web Workers 分线程任务_事件 Event
浏览器内核 支撑浏览器运行的最核心的程序 IE 浏览器内核 Trident内核,也是俗称的IE内核Chrome 浏览器内核 统称为 Chromium 内核或 ...
- java web学习总结(二十五) -------------------JSP中的九个内置对象
一.JSP运行原理 每个JSP 页面在第一次被访问时,WEB容器都会把请求交给JSP引擎(即一个Java程序)去处理.JSP引擎先将JSP翻译成一个_jspServlet(实质上也是一个servlet ...
- python第四十五课——继承性之多继承
测试模块 演示多继承的结构和使用: 子类:Child 直接父类(多个):Father.Mother 注意: 由于有多个直接父类,多个父类都要自己给其属性赋值, 避免混淆,我们使用类名.__init__ ...
- java 面向对象(三十五):泛型在继承上的体现
泛型在继承上的体现: /* 1. 泛型在继承方面的体现 虽然类A是类B的父类,但是G<A> 和G<B>二者不具备子父类关系,二者是并列关系. 补充:类A是类B的父类,A< ...
- 无废话ExtJs 入门教程十五[员工信息表Demo:AddUser]
无废话ExtJs 入门教程十五[员工信息表Demo:AddUser] extjs技术交流,欢迎加群(201926085) 前面我们共介绍过10种表单组件,这些组件是我们在开发过程中最经常用到的,所以一 ...
- 马凯军201771010116《面向对象与程序设计Java》第十五周学习知识总结
实验十五 GUI编程练习与应用程序部署 一.知识学习部分 清单文件 每个JAR文件中包含一个用于描述归档特征的清单文件(manifest).清单文件被命名为MANIFEST.MF,它位于JAR文件的 ...
随机推荐
- kubernetes云平台管理实战: 最小的资源pod(二)
一.pod初体验 1.编辑k8s_pod.yml文件 [root@k8s-master ~]# cat k8s_pod.yml apiVersion: v1 kind: Pod metadata: n ...
- * CSS 视觉格式化(基本框、包含块、盒模型、水平格式化、垂直格式化、行布局、em框、内容区、行间距、行内框、行框)
前言 CSS视觉格式化这个词可能比较陌生,但说起盒模型可能就恍然大悟了.实际上,盒模型只是CSS视觉格式化的一部分.视觉格式化分为块级和行内两种处理方式.理解视觉格式化,可以确定得到的效果是应该显示的 ...
- 算法第四版Question
1.ECLIPES标准输入流 ①Run As-->Run Configurations-->Commom-->Input File在Input File里面输入要读取的文本文件 这对 ...
- [物理学与PDEs]第1章习题10 自由电磁场在 Lorentz 规范变换下可使标势为零
在自由电磁场的情况, 证明: 在保持 Lorentz 条件下的规范变换下, 可使标势恒为零. 证明: 取 $\psi$ 满足 $\cfrac{\p \psi}{\p t}=\phi$ 且 $\cfra ...
- Python3:递归实现输出目录下所有的文件
今天来整理一下os库中方法的使用,如何输出一个目录下的所有文件? 1.首先介绍几个基本的的方法: 1)os.getcwd() #返回当前工作目录 2)os.listdir() #返回一个列表, ...
- 解决-webkit-box-orient: vertical;(文本溢出)属性在webpack打包后无法编译的问题
在scss里面: /*! autoprefixer: off */-webkit-box-orient: vertical;/* autoprefixer: on */
- 安装scala
Linux 环境 安装 下载的scala linux 压缩包放在目录 "/usr/scala “下 解压 tar -zxvf scala-2.11.8.tgz 再配置环境, 编辑文件 ”vi ...
- 转:spring boot log4j2配置(使用log4j2.yml文件)---YAML 语言教程
转:spring boot log4j2配置(使用log4j2.yml文件) - CSDN博客http://blog.csdn.net/ClementAD/article/details/514988 ...
- vue父子组件之间互相获取data值&调用方法(非props)
vue 子组件调用父组件方法&数据 vue有$parent这么一个属性,在子组件中就能拿到父组件的数据 this.$parent.xxx 就能调用父组件的方法 this.$parent.xxx ...
- 基于VC的MFC界面开发
教你熟悉VC6.0创建一个可视化软件的过程 UpdateData(TRUE);//将输入数据赋给文本框变量. UpdateData(FALSE);//将内容显示在文本框中 AfxMessageBox( ...