网站推广建议,已有网站做app需要多少钱,企业互联网网站定位,聚名网合法吗对象继承 原型对象概述instanceof运算符构造函数的继承多重继承模块 A 对象通过继承 B 对象#xff0c;就能
直接拥有 B 对象的所有属性和方法#xff08;利于代码复用#xff09; 大部分面向对象的编程语言都是通过类#xff08;class#xff09;实现对象的继承 但
传统… 对象继承 原型对象概述instanceof运算符构造函数的继承多重继承模块 A 对象通过继承 B 对象就能
直接拥有 B 对象的所有属性和方法利于代码复用 大部分面向对象的编程语言都是通过类class实现对象的继承 但
传统上JavaScript 语言的继承不通过 class而是通过**原型对象prototype**实现 ES6引入了基于class的继承 下面是JS原型链继承相关笔记
原型对象概述
1 构造函数缺点 JS通过构造函数生成新的对象 因此构造函数可以视为对象的模版实例对象的属性和方法可以定义在构造函数内部
function Cat (name, color) {this.name name;this.color color;
}var cat1 new Cat(LH, White);
cat1.name // LH
cat1.color // White 上面代码中Cat函数是一个构造函数函数内部定义了name属性和color属性所有实例对象上例是cat1都会生成这两个属性即这两个属性会定义在实例对象上面
通过构造函数为实例对象定义属性虽然很方便但是有一个缺点是 同一个构造函数的多个实例之间无法共享属性从而造成对系统资源的浪费
function Cat(name, color) {this.name name;this.color color;this.meow function () {console.log(miao);};
}var cat1 new Cat(LH, White);
var cat2 new Cat(EH, Black);cat1.meow cat2.meow
// false上面代码中cat1和cat2是同一个构造函数的两个实例都具有meow方法 由于meow方法是生成在每个实例对象上面所以两个实例就生成了两次 也即每新建一个实例就会新建一个meow方法 这既没有必要又浪费系统资源因为所有meow方法都是同样的行为完全应该共享 这种缺点的解决方法即是JS的原型对象prototype
2 prototype 属性作用
JS继承机制的设计思想是原型对象的所有属性和方法都能被实例对象共享
也即若属性和方法定义在原型上则所有的实例对象都可以共享从而节省内存并且体现实例对象之间的联系
下面是为对象指定原型 JS规定每个函数都有一个prototype属性用于指向一个对象
function f() {}
typeof f.prototype // object上面代码中函数f默认具有prototype属性指向一个对象 对于普通函数来说该属性基本无用 但是对于构造函数来说生成实例的时候该属性会自动成为实例对象的原型
function Animal(name) {this.name name;
}
Animal.prototype.color white;var cat1 new Animal(LH);
var cat2 new Animal(EH);cat1.color // white
cat2.color // white上面代码中构造函数Animal的prototype属性就是实例对象cat1和cat2的原型对象 原型对象上添加一个color属性则实例对象都共享该属性
原型对象的属性不是实例对象自身的属性 只要修改原型对象变动就立刻会体现在所有实例对象上
Animal.prototype.color yellow;cat1.color // yellow
cat2.color // yellow原型对象的color属性的值变为yellow两个实例对象的color属性立刻跟着变了 这是因为实例对象其实没有color属性都是读取原型对象的color属性 也就是说当实例对象本身没有某个属性或方法的时候它会到原型对象去寻找该属性或方法否则直接使用本身的属性和方法 这就是原型对象的美妙之处
cat1.color black;cat1.color // black
cat2.color // yellow
Animal.prototype.color // yellow总结一下原型对象的作用就是定义所有实例对象共享的属性和方法 这也是被称为原型对象的原因而实例对象可以视作从原型对象衍生出来的子对象
Animal.prototype.walk function () {console.log(this.name is walking);
};上面代码中Animal.prototype对象上面定义了一个walk方法这个方法将可以在所有Animal实例对象上面调用
3 原型链 JavaScript 规定所有对象都有自己的原型对象prototype 一方面任何一个对象都可以充当其他对象的原型 另一方面由于原型对象也是对象所以它也有自己的原型 由此形成一个原型链prototype chain
如果一层层地上溯所有对象的原型最终都可以上溯到Object.prototype也即Object构造函数的prototype属性 所有对象都继承Object构造函数的prototype属性 这是所有对象都有valueOf和toString方法的原因因为都是从Object.prototype继承的
而Object.prototype对象的原型是null null没有任何属性与方法也没有自己的原型 Object.getPrototypeOf方法返回参数对象的原型
Object.getPrototypeOf(Object.prototype)
// null读取对象的某个属性时JavaScript 引擎先寻找对象本身的属性如果找不到就到它的原型去找如果还是找不到就到原型的原型去找 如果直到最顶层的Object.prototype还是找不到则返回undefined
如果对象自身和它的原型都定义了一个同名属性那么优先读取对象自身的属性这叫做覆盖overriding
注意一级级向上在整个原型链上寻找某个属性对性能是有影响的
举例来说如果让构造函数的prototype属性指向一个数组就意味着实例对象可以调用数组方法
var MyArray function () {};MyArray.prototype new Array();
MyArray.prototype.constructor MyArray;var mine new MyArray();
mine.push(1, 2, 3);
mine.length // 3
mine instanceof Array // true上面代码中mine是构造函数MyArray的实例对象 由于MyArray.prototype指向一个数组的实例使得mine可以调用数组方法这些方法定义在数组实例的prototype对象上面 最后那行instanceof表达式用来比较一个对象是否为某个构造函数的实例结果就是证明mine为Array的实例
4 constructor属性 prototype对象有constructor属性默认指向prototype对象所在的构造函数
function P() {}
P.prototype.constructor P // true由于constructor属性定义在prototype对象上面意味着可以被所有实例对象继承
function P() {}
var p new P();p.constructor P // true
p.constructor P.prototype.constructor // true
p.hasOwnProperty(constructor) // false上面代码中p是构造函数P的实例对象但是p自身没有constructor属性该属性其实是读取原型链上面的P.prototype.constructor属性
constructor属性的作用是可以得知某个实例对象到底是哪一个构造函数产生的
function F() {};
var f new F();f.constructor F // true
f.constructor RegExp // false 上面代码中constructor属性确定了实例对象f的构造函数是F而不是RegExp
此外由于constructor属性我们可以从一个实例对象新建另一个实例
function Constr() {}
var x new Constr();var y new x.constructor();
y instanceof Constr // true上面代码中x是构造函数Constr的实例可以从x.constructor间接调用构造函数 这也使得在实例方法中调用自身的构造函数成为可能
Constr.prototype.createCopy function () {return new this.constructor();
};上面代码createCopy方法调用构造函数新建另一个实例
constructor属性表示原型对象与构造函数之间的关联关系 如果修改了原型对象一般会同时修改constructor属性防止引用的时候出错
function Person(name) {this.name name;
}Person.prototype.constructor Person // truePerson.prototype {method: function () {}
};Person.prototype.constructor Person // false
Person.prototype.constructor Object // true上面代码中构造函数Person的原型对象改掉了但是没有修改constructor属性 导致这个属性不再指向Person 由于Person的新原型是一个普通对象而普通对象的constructor属性指向Object构造函数 导致Person.prototype.constructor变成了Object
修改原型对象时一般要同时修改constructor属性的指向
// 坏的写法
C.prototype {method1: function (...) { ... },// ...
};// Zane的写法
C.prototype {constructor: C,method1: function (...) { ... },// ...
};// 更好的Zane写法
C.prototype.method1 function (...) { ... };如果不能确定constructor属性是什么函数还有一个办法通过name属性从实例得到构造函数的名称
funciton Foo() {};
var f new Foo();
f.constructor.name // Fooinstanceof运算符
instanceof运算符返回一个布尔值表示对象是否为某个构造函数的实例
var v new Vehicle();
v instanceof Vehicle // trueinstanceof运算符的左边是实例对象右边是构造函数 instanceof检查右边构造函数的原型对象prototype是否在左边对象的原型链上 下面两种写法等价
v instanceof Vehicle
// 等同于
Vehicle.prototype.isPrototypeOf(v)上面代码中Vehicle是对象v的构造函数它的原型对象是Vehicle.prototype isPrototypeOf()方法是 JavaScript 提供的原生方法用于检查某个对象是否为另一个对象的原型
由于instanceof检查整个原型链因此同一个实例对象可能会对多个构造函数都返回true
var d new Date();
d instanceof Date // true
d instanceof Object // true由于任意对象除了null都是Object的实例所以instanceof运算符可以判断一个值是否为非null的对象
有一种特殊情况就是左边对象的原型链上只有null对象。这时instanceof判断会失真
var obj Object.create(null);
typeof obj // object
obj instanceof Object // false上面代码中Object.create(null)返回一个新对象obj它的原型是null 右边的构造函数Object的prototype属性不在左边的原型链上因此instanceof就认为obj不是Object的实例 这是唯一的instanceof运算符判断会失真的情况
instanceof运算符的一个用处是判断值的类型
var x [1, 2, 3];
var y {};
x instanceof Array // true
y instanceof Object // true上面代码中instanceof运算符判断变量x是数组变量y是对象 注意instanceof运算符只能用于对象不适用原始类型的值 此外对于undefined和nullinstanceof运算符总是返回false。
var s hello;
s instanceof String // falseundefined instanceof Object // false
null instanceof Object // false上面代码中字符串不是String对象的实例因为字符串不是对象所以返回false
利用instanceof运算符还可以巧妙地解决调用构造函数时忘了加new命令的问题
function Fubar(foo, bar) {if (this instanceof Fubar) {this._foo foo;this._bar bar;} else {return new Fubar(foo, bar);}
}上面代码使用instanceof运算符在函数体内部判断this关键字是否为构造函数Fubar的实例 如果不是就表明忘了加new命令
构造函数的继承
构造函数的继承是非常常见的需求分为两步
第一步是在子类构造函数中调用父类构造函数
function Sub(value) {Super.call(this);this.prop value;
}上面代码中Sub是子类的构造函数this是子类的实例 在实例上调用父类的构造函数Super就会让子类实例具有父类实例的属性
第二步是让子类的原型指向父类的原型子类继承父类原型
Sub.prototype Object.create(Super.prototype);
Sub.prototype.constructor Sub;
Sub.prototype.method ...;上面代码中Sub.prototype是子类的原型要将它赋值为Object.create(Super.prototype)而不是直接等于Super.prototype 否则后面两行对Sub.prototype的操作会连父类的原型Super.prototype一起修改
另外还可以写成Sub.prototype等于一个父类实例
Sub.prototype new Super();上面这种写法也有继承的效果但是子类会具有父类实例的方法 有时这可能不是我们需要的所以不推荐使用这种写法
举个栗子
function Shape() {this.x 0;this.y 0;
}Shape.prototype.move function (x, y) {this.x x;this.y y;console.info(Shape moved);
};让Rectangle构造函数继承Shape
// 1. 子类继承父类实例
function Rectangle() {Shape.call(this); // 调用父类构造函数
}
// or
function Rectangle() {this.base Shape;this.base();
}// 2. 子类继承父类原型
Rectangle.prototype Object.create(Shape.prototype);
Rectangle.prototype.constructor Rectangle;采用这样的写法以后instanceof运算符会对子类和父类的构造函数都返回true
var rect new Rectangle();rect instanceof Rectangle // true
rect instanceof Shape // true上面代码中子类是整体继承父类 有时只需要单个方法的继承可以采用下面的写法
ClassB.prototype.print function() {ClassA.prototype.print.call(this);// some code
}上面代码中子类B的print方法先调用父类A的print方法再部署自己的代码 这就等于继承了父类A的print方法
多重继承
JavaScript 不提供多重继承功能即不允许一个对象同时继承多个对象 但是可以通过变通方法实现这个功能
function M1() {this.hello hello;
}function M2() {this.world world;
}function S() {M1.call(this);M2.call(this);
}// 继承 M1
S.prototype Object.create(M1.prototype);
// 继承链上加入 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 代码越来越庞大复杂 网页越来越像桌面程序需要一个团队分工协作、进度管理、单元测试等等开发者必须使用软件工程的方法管理网页的业务逻辑
这就需要JS的模块化编程 在理想的情况下开发者只需要实现核心的业务逻辑而其他都可以加载他人写好的模块 但是JS刚开始不是一种模块化编程语言到ES6才开始支持类和模块 下面是使用传统方法利用对象实现模块效果
1 基本实现 模块是实现特定功能的一组属性和方法的封装 简单的做法是把模块写成一个对象所有的模块成员都放到这个对象里面
var module1 new Object({_count : 0,m1 : function () {// ...},m2 : function () {// ...}
});上面的函数m1和m2都封装在module1对象中 使用时直接调用该对象的属性
module1.m1();但是这样的写法会暴露所有模块成员内部状态可以被外部改写 比如外部代码可以直接改变内部计数器的值
module1._count 5;2 封装私有变量构造函数写法 使用构造函数封装私有变量
function StringBuilder() {var buffer [];this.add function (str) {buffer.push(str);};this.toString function () {return buffer.join();};
}上面代码中buffer是模块的私有变量 一旦生成实例对象外部是无法直接访问buffer的 但是这种方法将私有变量封装在构造函数中导致构造函数与实例对象是一体的总是存在于内存之中无法在使用完成后清除 这意味着构造函数有双重作用既用来塑造实例对象又用来保存实例对象的数据违背了构造函数与实例对象在数据上相分离的原则 即实例对象的数据不应该保存在实例对象以外 同时耗费内存
function StringBuilder() {this._buffer [];
}StringBuilder.prototype {constructor: StringBuilder,add: function (str) {this._buffer.push(str);},toString: function () {return this._buffer.join();}
};这种方法将私有变量放入实例对象中好处是看上去更自然但是它的私有变量可以从外部读写不是很安全
3 封装私有变量IIFE写法 使用IIFEImmediately-Invoked Function Expression 将相关属性和方法封装在一个函数作用域中从而不暴露私有成员
var module1 (function () {var _count 0;var m1 function () {// some code};var m2 function () {// some code};return {m1: m1,m2: m2};
})();使用上面的方法外部代码无法读取内部的_count变量
console.info(module1._count); // undefined上面的module1就是 JavaScript 模块的基本写法
下面是对该模块写法的优化
4 模块的放大模式 如果一个模块很大必须分成几个部分或者一个模块需要继承另一个模块这时就有必要采用放大模式augmentation
var module1 (function (mod) {mod.m3 function () {// ...};return mod;
})(module1);上面的代码为module1模块添加了一个新方法m3()然后返回新的module1模块
在浏览器环境中模块的各个部分通常都是从网上获取的有时无法知道哪个部分会先加载 如果采用上面的写法第一个执行的部分有可能加载一个不存在空对象这时就要采用宽放大模式Loose augmentation
var module1 (function (mod) {// ...return mod;
})(window.module1 || {});与放大模式相比宽放大模式就是立即执行函数的参数可以是空对象
5 输入全局变量 独立性是模块的重要特点模块内部最好不与程序的其他部分直接交互 为了在模块内部调用全局变量必须显式地将其他变量输入模块
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 scopewindow.finalCarousel {init : initialize,destroy: dieCarouselDie}})( jQuery, window, document );上面代码中finalCarousel对象输出到全局 对外暴露init和destroy接口内部方法go、handleEvents、initialize、dieCarouselDie都是外部无法调用的