【一次面试】再谈javascript中的继承
前言
面向对象编程是每次面试必问的知识点,而前端js如何实现继承每次命中率高达80%
这不,近两天我们面试时候,同事就问道面试者此问题,但是,不论之前自己做的回答,还是面试者的回答,基本都不太令人满意
很大的原因是多数时候前端并不需要实现继承,就jquery来说也基本上是一码到底,没有实现继承,据我所知,也就prototype与ext实现过继承
所以继承对前端来说似乎不太适用
近两年来情况有所变化,SPA的兴起以及前端逻辑的复杂化,让前端代码愈发的多,愈发的重,所以继承慢慢的进入了一些初级一点的前端视野
所以,好好的了解如何实现继承,继承的几个用法,是非常有意义的,就算只是为面试都是很有用的
文章只是个人见解,有误请提出,demo未做检测,有误请提出
实现继承
当一个函数被创建时,Function构造函数产生的函数会隐式的被赋予一个prototype属性,prototype包含一个constructor对象
而constructor便是该新函数对象(constructor意义不大,但是可以帮我们找到继承关系)
每个函数都会有一个prototype属性,该属性指向另一对象,这个对象包含可以由特定类型的所有实例共享的属性和方法
每次实例化后,实例内部都会包含一个[[prototype]](__proto__)的内部属性,这个属性指向prototype
① 我们通过isPrototypeOf来确定某个对象是不是我的原型
② hasOwnPrototype 可以检测一个属性是存在实例中还是原型中,该属性不是原型属性才返回true
var Person = function (name, age) {
this.name = name;
this.age = age;
};
Person.prototype.getName = function () {
return this.name;
};
var y = new Person('叶小钗', 30);

通俗一点来说,prototype是一模板,新创建对象就是对他一个拷贝,里面的属性或者方法都会赋值给实例
这里说是模板赋值其实不太合理,反正由类产生的所有实例的__proto__都会共享一个prototype,这里我做一个例子
我们在断点情况下是没有name2属性的,但是我们如果在断点下加上这个代码的话,a.name2,就有值了
Klass.prototype.name2 = '222';


所以,这里说模板,不如说是指针指向,都是共享一个对象;继承的情况的话就是这样
(function () {
var Person = function (name) {
this.name = name;
};
//Person.prototype = {};//这句将影响十分具有constructor属性
Person.prototype.getName = function () {
return this.name;
};
var Student = function (name, sex, id) {
this.name = name || '无名氏';
this.sex = sex || '不明';
this.id = id || '未填'; //学号
};
//相当于将其prototype复制了一次,若是包含constructor的话将指向Person
Student.prototype = new Person();
Student.prototype.getId = function () {
return this.id;
}
var y = new Person();
var s = new Student;
var s1 = y instanceof Person;
var s2 = s instanceof Student;
var s3 = s instanceof Person;
var s4 = Student.prototype.constructor === Person;
var s5 = Student.constructor === Person;
var s6 = Student.constructor === Function;
var s = '';
})();
prototype实现继承
我们在具体项目中,真正复杂一点的项目可能就会对继承进行封装,让自己更好的使用,我们下面就来看看prototype怎么干的
PS:我这里做了一点小的修改:
var Class = (function () {
function subclass() { };
//我们构建一个类可以传两个参数,第一个为需要继承的类,
//如果没有的话就一定会有第二个对象,就是其原型属性以及方法,其中initialize为构造函数的入口
function create() {
//此处两个属性一个是被继承的类,一个为原型方法
var parent = null;
var properties = $A(arguments);
if (Object.isFunction(properties[0]))
parent = properties.shift();
//新建类,这个类最好会被返回,构造函数入口为initialize原型方法
function klass() {
this.initialize.apply(this, arguments);
}
//扩展klass类的“实例”对象(非原型),为其增加addMethods方法
Object.extend(klass, Class.Methods);
//为其指定父类,没有就为空
klass.superclass = parent;
//其子类集合(require情况下不一定准确)
klass.subclasses = [];
//如果存在父类就需要继承
if (parent) {
//新建一个空类用以继承,其存在的意义是不希望构造函数被执行
//比如 klass.prototype = new parent;就会执行其initialize方法
subclass.prototype = parent.prototype;
klass.prototype = new subclass;
parent.subclasses.push(klass);
}
//遍历对象(其实此处这样做意义不大,我们可以强制最多给两个参数)
//注意,此处为一个难点,需要谨慎,进入addMethods
for (var i = 0, length = properties.length; i < length; i++)
klass.addMethods(properties[i]);
if (!klass.prototype.initialize)
klass.prototype.initialize = Prototype.emptyFunction;
klass.prototype.constructor = klass;
return klass;
}
/**
由于作者考虑情况比较全面会想到这种情况
var Klass = Class.create(parent,{},{});
后面每一个对象的遍历都会执行这里的方法,我们平时需要将这里直接限定最多两个参数
*/
function addMethods(source) {
//当前类的父类原型链,前面被记录下来了
var ancestor = this.superclass && this.superclass.prototype;
//将当前对象的键值取出转换为数组
var properties = Object.keys(source);
//依次遍历各个属性,填充当前类(klass)原型链
for (var i = 0, length = properties.length; i < length; i++) {
//property为键,value为值,比如getName: function(){}的关系
var property = properties[i], value = source[property];
/****************
这里有个难点,用于处理子类中具有和父类原型链同名的情况,仍然可以调用父类函数的方案(我这里只能说牛B)
如果一个子类有一个参数叫做$super的话,这里就可以处理了,这里判断一个函数的参数使用了正则表达式,正如
var argslist = /^\s*function\s*\(([^\(\)]*?)\)\s*?\{/i.exec(value.toString())[1].replace(/\s/i, '').split(',');
****************/
if (ancestor && Object.isFunction(value) && value.argumentNames()[0] == "$super") {
//将当前函数存下来
var method = value;
/****************
第一步:
这里是这段代码最难的地方,需要好好阅读,我们首先将里面一块单独提出
value = (function (m) {
return function () { return ancestor[m].apply(this, arguments); };
})(property)
这里很牛B的构建了一个闭包(将方法名传了进去),任何直接由其父类原型中取出了相关方法
然后内部返回了该函数,此时其实重写了value,value
***这里***有一个特别需要注意的地方是,此处的apply方法不是固定写在class上的,是根据调用环境变化的,具体各位自己去理解了
第二步:
首先value被重新成其父类的调用了,此处可以简单理解为(仅仅为理解)$super=value
然后下面会调用wrap操作vaule将,我们本次方法进行操作
wrap: function (wrapper) {
var __method = this;
return function () {
return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
}
}
可以看出,他其实就是将第一个方法(value)作为了自己方法名的第一个参数了,后面的参数不必理会
****************/
value = (function (m) {
return function () { return ancestor[m].apply(this, arguments); };
})(property).wrap(method);
}
//为其原型赋值
this.prototype[property] = value;
}
return this;
}
return {
create: create,
Methods: {
addMethods: addMethods
}
};
})();
下面来一个简单的例子:
var Person = Class.create({
initialize: function (name) {
this.name = name;
},
getName: function () {
console.log('我是父类');
return this.name;
},
getAge: function () {
return this.age;
}
});
var Employee = Class.create(Person, {
initialize: function ($super, name, age) {
$super(name);
this.age = age;
},
getName: function ($super) {
return '我是子类:' + $super();
}
});
var C = Class.create(Employee, {
getAge: function ($super) {
return $super();
}
});
var y = new C("叶小钗", 25);
console.log(y.getName() + ': ' + y.getAge());
这里,我们根据自己的需求重写写了下继承相关代码,表现基本与上述一致,各位可以自己试试
PS:当然如果有问题请指出
简化prototype继承
var arr = [];
var slice = arr.slice; function create() {
if (arguments.length == 0 || arguments.length > 2) throw '参数错误'; var parent = null;
//将参数转换为数组
var properties = slice.call(arguments); //如果第一个参数为类(function),那么就将之取出
if (typeof properties[0] === 'function')
parent = properties.shift();
properties = properties[0]; function klass() {
this.initialize.apply(this, arguments);
} klass.superclass = parent;
klass.subclasses = []; if (parent) {
var subclass = function () { };
subclass.prototype = parent.prototype;
klass.prototype = new subclass;
parent.subclasses.push(klass);
} var ancestor = klass.superclass && klass.superclass.prototype;
for (var k in properties) {
var value = properties[k]; //满足条件就重写
if (ancestor && typeof value == 'function') {
var argslist = /^\s*function\s*\(([^\(\)]*?)\)\s*?\{/i.exec(value.toString())[1].replace(/\s/i, '').split(',');
//只有在第一个参数为$super情况下才需要处理(是否具有重复方法需要用户自己决定)
if (argslist[0] === '$super' && ancestor[k]) {
value = (function (methodName, fn) {
return function () {
var scope = this;
var args = [function () {
return ancestor[methodName].apply(scope, arguments);
} ];
return fn.apply(this, args.concat(slice.call(arguments)));
};
})(k, value);
}
} klass.prototype[k] = value;
} if (!klass.prototype.initialize)
klass.prototype.initialize = function () { }; klass.prototype.constructor = klass; return klass;
}
如此,我们就完成了自己的继承了
实战继承
知道原型可以实现继承是皮毛,知道各个库是怎样干的也只是入门
因为,要在项目中用到才能算真正掌握继承,这里我们便来点实战的小例子
这里我写一个简单的view用于下面各种继承
var AbstractView = create({
initialize: function (opts) {
opts = opts || {};
this.wrapper = opts.wrapper || $('body');
//事件集合
this.events = {};
this.isCreate = false;
},
on: function (type, fn) {
if (!this.events[type]) this.events[type] = [];
this.events[type].push(fn);
},
trigger: function (type) {
if (!this.events[type]) return;
for (var i = 0, len = this.events[type].length; i < len; i++) {
this.events[type][i].call(this)
}
},
createHtml: function () {
throw '必须重写';
},
create: function () {
this.root = $(this.createHtml());
this.wrapper.append(this.root);
this.trigger('onCreate');
this.isCreate = true;
},
show: function () {
if (!this.isCreate) this.create();
this.root.show();
this.trigger('onShow');
},
hide: function () {
this.root.hide();
}
});
var Alert = create(AbstractView, {
createHtml: function () {
return '<div class="alert">这里是alert框</div>';
}
});
var AlertTitle = create(Alert, {
initialize: function ($super) {
this.title = '';
$super();
},
createHtml: function () {
return '<div class="alert"><h2>' + this.title + '</h2>这里是带标题alert框</div>';
},
setTitle: function (title) {
this.title = title;
this.root.find('h2').html(title)
}
});
var AlertTitleButton = create(AlertTitle, {
initialize: function ($super) {
this.title = '';
$super();
this.on('onShow', function () {
var bt = $('<input type="button" value="点击我" />');
bt.click($.proxy(function () {
alert(this.title);
}, this));
this.root.append(bt)
});
}
});
var v1 = new Alert();
v1.show();
var v2 = new AlertTitle();
v2.show();
v2.setTitle('我是标题');
var v3 = new AlertTitleButton();
v3.show();
v3.setTitle('我是标题和按钮的alert');
http://sandbox.runjs.cn/show/ddlp7nlt
结语
希望这次继承的文章对各位有帮助,此外文中错误请指出
亲爱的道友们,我其实在我们团队只是中等水平,我们上海携程无线是一个优秀的团队,
如果你现在正在找工作,请加入我们吧!!!
在我们团队,你可以肆无忌惮的黑自己的老大,你会体会到和谐的氛围,当然妹子多多的! 要求:靠谱前端,吃苦耐劳,待遇刚刚的!!! 需要的朋友可以私信我 顺便推广道友的jquery技术交流群:239147101
【一次面试】再谈javascript中的继承的更多相关文章
- 浅谈JavaScript中的继承
引言 在JavaScript中,实现继承的主要方式是通过原型链技术.这一篇文章我们就通过介绍JavaScript中实现继承的几种方式来慢慢领会JavaScript中继承实现的点点滴滴. 原型链介绍 原 ...
- 浅谈 JavaScript 中的继承模式
最近在读一本设计模式的书,书中的开头部分就讲了一下 JavaScript 中的继承,阅读之后写下了这篇博客作为笔记.毕竟好记性不如烂笔头. JavaScript 是一门面向对象的语言,但是 ES6 之 ...
- 再谈JavaScript中的闭包
一.什么是闭包 闭包就是有权访问另一个函数作用域中变量的函数,因此,闭包的本质是一个函数.当一个内部函数被保存到外部时,就会生成闭包. 二.闭包的作用 1.实现公有变量,即通过局部变量实现全局变量的效 ...
- 浅谈javaScript中的继承关系<一>
// JavaScript Document //创建三个构造函数 function Shape(){ this.name='ahape'; this.toString=function(){retu ...
- 浅谈JavaScript中的闭包
浅谈JavaScript中的闭包 在JavaScript中,闭包是指这样一个函数:它有权访问另一个函数作用域中的变量. 创建一个闭包的常用的方式:在一个函数内部创建另一个函数. 比如: functio ...
- 谈 JavaScript 中的强制类型转换 (2. 应用篇)
这一部分内容是承接上一篇的, 建议先阅读谈 JavaScript 中的强制类型转换 (1. 基础篇) 前两章讨论了基本数据类型和基本包装类型的关系, 以及两个在类型转换中十分重要的方法: valueO ...
- 再谈JavaScript的数据类型问题
JavaScript的数据类型问题已经讨论过很多次了,但许多人还有许多书仍然沿用着错误的.混乱的一些观点,所以就再细讲一回. 提及这个讨论的原因在于argb同学在我的MSN博客上的一段回复,又更早的起 ...
- Unity教程之再谈Unity中的优化技术
这是从 Unity教程之再谈Unity中的优化技术 这篇文章里提取出来的一部分,这篇文章让我学到了挺多可能我应该知道却还没知道的知识,写的挺好的 优化几何体 这一步主要是为了针对性能瓶颈中的”顶点 ...
- 再谈javascript原型继承
Javascript原型继承是一个被说烂掉了的话题,但是自己对于这个问题一直没有彻底理解,今天花了点时间又看了一遍<Javascript模式>中关于原型实现继承的几种方法,下面来一一说明下 ...
随机推荐
- 搭建LNAMP环境(三)- 源码安装Apache2.4
上一篇:搭建LNAMP环境(二)- 源码安装Nginx1.10 1.yum安装编译apache需要的包(如果已经安装,可跳过此步骤) yum -y install pcre pcre-devel zl ...
- 让IE系列支持HTML5的html5shiv.js和respond.min.js
HTML5越来越成为主流,被广大搜索引擎所使用,但IE对HTML5的支持却常被人唾弃. 解决方案有两种: 1.为网站创建多套模板,通过程序对User-Agent的判断给不同的浏览器用户显示不同的页面, ...
- 解密jQuery内核 Sizzle引擎筛选器 - 位置伪类(一)
本章开始分析过滤器,根据API的顺序来 主要涉及的知识点 jQuery的组成 pushStack方法的作用 sizzle伪类选择器 首页我们知道jQuery对象是一个数组对象 内部结构 jQuery的 ...
- OpenCASCADE BRepTools
OpenCASCADE BRepTools eryar@163.com Abstract. OpenCASCADE BRepTools provides utilities for BRep data ...
- VXLAN 概念(Part I) - 每天5分钟玩转 OpenStack(108)
除了前面讨论的 local, flat, vlan 这几类网络,OpenStack 还支持 vxlan 和 gre 这两种 overlay network. overlay network 是指建立在 ...
- DOM-Text类型、Comment类型、CDATASection类型、DocumentType类型、DocumentFragment类型、Attr类型
Text类型 文本节点由Text类型表示,包含的是可以照字面解释的纯文本内容.Text节点具有以下特征: nodeType的值为3 nodeName的值为"text" nodeVa ...
- Vagrant使用
常用命令 命令 说明 vagrant up 运行vm vagrant status 查看当前虚拟机运行状态 vagrant suspend 暂停虚拟机 vagrant ssh ssh方式登录虚拟机 v ...
- iPhone 6/plus iOS Safari fieldset border 边框消失
问题:iPhone6 plus 手机浏览网页,fieldset border 边框消失. 示例代码: <div> <fieldset style="border: 1px ...
- [C#]浅析ref、out参数
转载:http://www.cnblogs.com/vd630/p/4601919.html#top 按引用传递的参数算是C#与很多其他语言相比的一大特色,想要深入理解这一概念应该说不是一件容易的事, ...
- 如何使用免费PDF控件从PDF文档中提取文本和图片
如何使用免费PDF控件从PDF文档中提取文本和图片 概要 现在手头的项目有一个需求是从PDF文档中提取文本和图片,我以前也使用过像iTextSharp, PDFBox 这些免费的PD ...