Java中方法参数的总结

方法参数方法得到的都是所有参数值的拷贝,方法不能修改传递给它的任何参数变量的内容。参数共有两种类型: 基本数据类型对象引用除了基本数据类型的都是对象引用,包括数组等等。 很容易得知,一个方法不可能改变一个基本数据类型的参数,下面讨论参数的对象的情况。下面这个方法可以将一个雇员的薪金提高两倍 public static void tripleSalary(Employee x){ x.raiseSalary(200);}当调用 harry = new Employee(...);tripleSalary(harry);时,执行过程如下: x被初始化为harry值的拷贝,x是对象的引用。x和harry同时指向相同的对象。raiseSalary方法应用于这个对象引用。x和harry同时引用的那个Employee对象的薪金提高了200%。方法结束之后,x不再使用。harry继续引用那个增长了薪金的对象Java中都是值传递,而不是引用传递下面这个方法用来交换两个雇员对象(其实做不到): public static void swap(Employee x, Emploee y)//doesn't work{ Employee temp = x; x = y; y = temp;}如果Java对对象采用的是按引用(地址)调用那么这个方法可以实现两个对象交换数据的效果。 Employee a = new Employee("Alice",...);Employee b = new Employee("Bob",...);swap(a,b);但是执行完该方法后,a和b所引用的对象并没有改变。x和y在方法执行过程中所引用的对象改变了,但是a和b引用的对象并没有改变,因为x和y只是“复制品”。这个过程说明,对象引用也是按值传递的。

July 5, 2019 · 1 min · jiezi

9JavaScript-面向对象高级对象创建模式

JavaScript面向对象高级——对象创建模式一、Object构造函数模式方式1: Object构造函数模式 流程: 先创建空Object对象, 再动态添加属性/方法。适用场景: 起始时不确定对象内部数据。问题: 语句太多。/*一个人: name:"Tom", age: 12 */// 先创建空Object对象var p = new Object()p = {} //此时内部数据是不确定的// 再动态添加属性/方法p.name = 'Tom'p.age = 12p.setName = function (name) { this.name = name}//测试console.log(p.name, p.age) // Tom 12p.setName('Bob')console.log(p.name, p.age) // Bob 12二、对象字面量模式方式2: 对象字面量模式 流程: 使用{}创建对象, 同时指定属性/方法。适用场景: 起始时对象内部数据是确定的。问题: 如果创建多个对象, 有重复代码。var p = { name: 'Tom', age: 12, setName: function (name) { this.name = name }}//测试console.log(p.name, p.age) // Tom 12p.setName('JACK')console.log(p.name, p.age) // JACK 12var p2 = { //如果创建多个对象代码很重复 name: 'Bob', age: 13, setName: function (name) { this.name = name }}三、工厂模式方式3: 工厂模式 ...

June 27, 2019 · 2 min · jiezi

支持嵌套对象多级数组的Vue动态多级表单组件-vuedynamicformcomponent

方便不想看完全篇文章的童鞋,简单总结一下,这是篇软广,主要是推广自己在业务中沉淀的一个开源组件 vue-dynamic-form-component 。基于 element-ui 实现的 vue组件,只需编写类似 async-validator 的规则,自动生成对应的表单,支持常见输入类型的同时,支持嵌套对象、hashmap、多维数组等复杂类型。有需要的童鞋欢迎使用和贡献代码,顺便给个star(我也不知道为什么字体自动加黑了,不关我事) 前言几个月前,我在github开源了一个前端解析手机应用安装包(IPA 或 APK 文件)信息的工具 app-info-parser ,算是第一次正儿八经的做开源这件事,之后就有了半夜三四点回复issue、修bug的体验,说实话,上完班还要处理issue是挺累的,但也是乐在其中。正所谓开源一时爽,一直开源一直爽。 对于程序员而言,最不喜欢的事情,除了和产品经理 吵架 (交流,是交流,不是吵架,要peace)外,估计就是一直做重复的事情了。在程序界,有相当一部分开源工具都是为了把人从重复的事情中解放出来,去做更有趣、更能体现个人价值的事情。比如 AI智能回复老婆消息 (请勿随意尝试,老婆没了我不负责)。 之前开源的工具 app-info-parser 是减少重复工作,提高生产效率,接下来的主角 vue-dynamic-form-component 也是如此。 先贴一个展示大概功能的 gif ,动图有点大,如果加载不出来的话可以到 组件首页 查看。 左边是你需要编写的主要代码,右边是对应生成的表单。 背景(Why)为什么要做这个组件?其实在前言中已经提到:因为不想一直做重复、没有技术含量的事情。 对于本篇文章而言,这件重复、没有技术含量的事情就是:简单的表单代码 我所在的小组主要负责公司的公共服务系统搭建及维护,随之而来的便是一套接一套的CURD系统,目前业界已经存在很多优秀的UI库,比如为 Vue 而生的 element-ui, iView,基于 react 的 ant-design 等,已经在很大程度上提高了PC管理系统的开发效率,减少了很多重复工作。 但是对于表单功能,UI库出于通用性的考虑,实际使用中,对于简单的数据对象,我们仍然需要编写大量的表单代码来实现,因为出现了很多优秀的动态表单组件,比如 vue-form-generator,vue-form-making 那么为什么我还要再造一个类似的轮子?这其实要结合组内的技术栈来说: 由于组内的人员配置问题:前端1人(没错,就是孤独的我)、后端8人+,在技术栈上,选用了后端同事相对容易上手的 Vue ,基于 element-ui 开发管理系统。而目前已有的动态表单组件存在以下不适用的问题: vue-form-generator:设计思想很好,但是组件样式比较old school,同时对多级对象、多维数组等复杂数据支持不是很好,需要自己实现 field 组件,使用成本较高vue-form-making:也是基于element-ui, 样式统一,但是基于组件类型生成表单的方式不够灵活,只能利用已存在的输入组件,因此不支持多级对象等复杂类型其他的组件相对而言存在更多的问题,就不一一列举了。 以上就是为什么我会想要再造一个Vue的动态表单轮子,其实里面就已经包含了接下来我们要讲的: vue-dynamic-form-component 有什么作用? 功能(What)动态生成表单基于 async-validator 的规则来生成表单,只需要编写简单的声明配置,即可自动生成表单,只需要关注数据类型,无需关注数据类型对应何种输入组件 <template> <dynamic-form v-model="data" :descriptors="descriptors"> </dynamic-form></template>export default { data () { return { data: {}, descriptors: { date: { type: 'date', label: 'date \'s label', required: false }, number: { type: 'number', label: 'number \'s label', required: true, placeholder: 'please input the number' }, string: { type: 'string', label: 'string \'s label', required: true, pattern: /^test$/g }, url: { type: 'url', label: 'url \'s label', required: true, placeholder: 'please input the url' }, email: { type: 'email', label: 'email \'s label', required: false }, enum: { type: 'enum', label: 'enum\'s label', enum: ['value-1', 'value-2'] } } } }} ...

June 24, 2019 · 2 min · jiezi

Javascript的对象拷贝

翻译:疯狂的技术宅原文:https://smalldata.tech/blog/2…本文首发微信公众号:前端先锋欢迎关注,每天都给你推送新鲜的前端技术文章在开始之前,我先普及一些基础知识。Javascript 的对象只是指向内存中某个位置的指针。这些指针是可变的,也就是说,它们可以重新被赋值。所以仅仅复制这个指针,其结果是有两个指针指向内存中的同一个地址。var foo = { a : “abc”}console.log(foo.a);// abcvar bar = foo;console.log(bar.a);// abcfoo.a = “yo foo”;console.log(foo.a);// yo fooconsole.log(bar.a);// yo foobar.a = “whatup bar?";console.log(foo.a);// whatup bar?console.log(bar.a);// whatup bar? 通过上面的例子可以看到,对象 foo 和 bar 都能随着对方的变化而变化。所以在拷贝 Javascript 中的对象时,要根据实际情况做一些考虑。浅拷贝如果要操作的对象拥有的属性都是值类型,那么可以使用扩展语法或 Object.assign(…)var obj = { foo: “foo”, bar: “bar” };var copy = { …obj };// Object { foo: “foo”, bar: “bar” }var obj = { foo: “foo”, bar: “bar” };var copy = Object.assign({}, obj);// Object { foo: “foo”, bar: “bar” }可以看到上面两种方法都可以把多个不同来源对象中的属性复制到一个目标对象中。var obj1 = { foo: “foo” };var obj2 = { bar: “bar” };var copySpread = { …obj1, …obj2 };// Object { foo: “foo”, bar: “bar” }var copyAssign = Object.assign({}, obj1, obj2);// Object { foo: “foo”, bar: “bar” }上面这种方法是存在问题的,如果对象的属性也是对象,那么实际被拷贝的只是那些指针,这跟执行 var bar = foo; 的效果是一样的,和第一段代码中的做法一样。var foo = { a: 0 , b: { c: 0 } };var copy = { …foo };copy.a = 1;copy.b.c = 2;console.dir(foo);// { a: 0, b: { c: 2 } }console.dir(copy);// { a: 1, b: { c: 2 } }深拷贝(有限制)想要对一个对象进行深拷贝,一个可行的方法是先把对象序列化为字符串,然后再对它进行反序列化。var obj = { a: 0, b: { c: 0 } };var copy = JSON.parse(JSON.stringify(obj));不幸的是,这个方法只在对象中包含可序列化值,同时没有循环引用的情况下适用。常见的不能被序列化的就是日期对象 —— 尽管它显示的是字符串化的 ISO 日期格式,但是 JSON.parse 只会把它解析成为一个字符串,而不是日期类型。深拷贝 (限制较少)对于一些更复杂的场景,我们可以用 HTML5 提供的一个名为结构化克隆的新算法。不过,截至本文发布为止,有些内置类型仍然无法支持,但与 JSON.parse 相比较而言,它支持的类型要多的多:Date、RegExp、 Map、 Set、 Blob、 FileList、 ImageData、 sparse 和 typed Array。 它还维护了克隆对象的引用,这使它可以支持循环引用结构的拷贝,而这些在前面所说的序列化中是不支持的。目前还没有直接调用结构化克隆的方法,但是有些新的浏览器特性的底层用了这个算法。所以深拷贝对象可能需要依赖一系列的环境才能实现。Via MessageChannels: 其原理是借用了通信中用到的序列化算法。由于它是基于事件的,所以这里的克隆也是一个异步操作。class StructuredCloner { constructor() { this.pendingClones_ = new Map(); this.nextKey_ = 0; const channel = new MessageChannel(); this.inPort_ = channel.port1; this.outPort_ = channel.port2; this.outPort_.onmessage = ({data: {key, value}}) => { const resolve = this.pendingClones_.get(key); resolve(value); this.pendingClones_.delete(key); }; this.outPort_.start(); } cloneAsync(value) { return new Promise(resolve => { const key = this.nextKey_++; this.pendingClones_.set(key, resolve); this.inPort_.postMessage({key, value}); }); }}const structuredCloneAsync = window.structuredCloneAsync = StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);const main = async () => { const original = { date: new Date(), number: Math.random() }; original.self = original; const clone = await structuredCloneAsync(original); // different objects: console.assert(original !== clone); console.assert(original.date !== clone.date); // cyclical: console.assert(original.self === original); console.assert(clone.self === clone); // equivalent values: console.assert(original.number === clone.number); console.assert(Number(original.date) === Number(clone.date)); console.log(“Assertions complete.”);};main();Via the history API:history.pushState() 和 history.replaceState() 都会给它们的第一个参数做一个结构化克隆!需要注意的是,此方法是同步的,因为对浏览器历史记录进行操作的速度不是很快,假如频繁调用这个方法,将会导致浏览器卡死。const structuredClone = obj => { const oldState = history.state; history.replaceState(obj, null); const clonedObj = history.state; history.replaceState(oldState, null); return clonedObj;};Via notification API: 当创建一个 notification 实例的时候,构造器为它相关的数据做了结构化克隆。需要注意的是,它会尝试向用户展示浏览器通知,但是除非它收到了用户允许展示通知的请求,否则它什么都不会做。一旦用户点击同意的话,notification 会立刻被关闭。const structuredClone = obj => { const n = new Notification(”", {data: obj, silent: true}); n.onshow = n.close.bind(n); return n.data;};用 Node.js 进行深拷贝Node.js 的 8.0.0 版本提供了一个 序列化 api 可以和结构化克隆相媲美. 不过这个 API 在本文发布的时候,还只是被标记为试验性的:const v8 = require(‘v8’);const buf = v8.serialize({a: ‘foo’, b: new Date()});const cloned = v8.deserialize(buf);cloned.b.getMonth();在 8.0.0 版本以下比较稳定的方法,可以考虑用 lodash 的 cloneDeep函数,它的思想多少也基于结构化克隆算法。结论Javascript 中最好的对象拷贝的算法,很大程度上取决于其使用环境,以及你需要拷贝的对象类型。虽然 lodash 是最安全的泛型深拷贝函数,但是如果你自己封装的话,也许能够获得效率更高的实现方法,以下就是一个简单的深拷贝,对 Date 日期对象也同样适用:function deepClone(obj) { var copy; // Handle the 3 simple types, and null or undefined if (null == obj || “object” != typeof obj) return obj; // Handle Date if (obj instanceof Date) { copy = new Date(); copy.setTime(obj.getTime()); return copy; } // Handle Array if (obj instanceof Array) { copy = []; for (var i = 0, len = obj.length; i < len; i++) { copy[i] = deepClone(obj[i]); } return copy; } // Handle Function if (obj instanceof Function) { copy = function() { return obj.apply(this, arguments); } return copy; } // Handle Object if (obj instanceof Object) { copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = deepClone(obj[attr]); } return copy; } throw new Error(“Unable to copy obj as type isn’t supported " + obj.constructor.name);}我很期待可以随便使用结构化克隆的那一天的到来,让对象拷贝不再令人头疼^_^本文首发微信公众号:前端先锋欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章欢迎继续阅读本专栏其它高赞文章:12个令人惊叹的CSS实验项目必须要会的 50 个React 面试题世界顶级公司的前端面试都问些什么11 个最好的 JavaScript 动态效果库CSS Flexbox 可视化手册从设计者的角度看 React过节很无聊?还是用 JavaScript 写一个脑力小游戏吧!CSS粘性定位是怎样工作的一步步教你用HTML5 SVG实现动画效果程序员30岁前月薪达不到30K,该何去何从14个最好的 JavaScript 数据可视化库8 个给前端的顶级 VS Code 扩展插件Node.js 多线程完全指南把HTML转成PDF的4个方案及实现 ...

April 17, 2019 · 3 min · jiezi

ES5的用对象实现类的作用

// js实现类 ES5// 由于函数始对象 所以写法不是唯一// function Pf(){}更符合固有思想// 由于ES6添加了类,所以书写变得跟方便var Pf = function(name,age){ // 私有属性方法外面不能调用,只有对象方法可以操作,从而起到保护数据的作用 // 私有属性 var secret = ‘小秘密’ // 私有方法 function secretfn(){ console.log(‘私有方法’) console.log(“my secret is”+secret) } // 公共属性方法是每创建一个对象就会创建一个该属性或方法(耗费一定把内存) // 共有实例属性 this.name = name this.age = age //共有实例方法 this.say = function(){ console.log(“my name is”+this.name,“my age is”+this.age) console.log(‘可以操作私有属性与方法’) secretfn() }}// 静态方法Pf.f1 = function(){ console.log(‘我是静态方法,只能用类直接调用,实例对象不能调用’)}Pf.prototype = { constructor:Pf,// 这种添加原型方法需要重置制定对象。 // 原型链上的方法为公有方法,由类创建出来的对象会指向该原型,不会重新创建该方法,但是优先级没有对象方法高 // 其优点是节省内存 say:function(){ console.log(“原型上的say”) // 原型链上可以拿到共有属性,拿不到私有属性与方法 console.log(‘我也能拿到数据’+this.name) } }var a = new Pf(‘ss’,22)a.say()Pf.f1()运行直接node ...

April 14, 2019 · 1 min · jiezi

让前端面试不在难(三)

今天聊一下clone这个前端面试高频问题,由此引出typeof、instanceof、Object.prototype.toString这些javascript Api。下面我们先详细的聊一下,完了解决下面试官的问题。typeoftypeof能获取一个变量或表达式的类型。原始类型BooleanNullUndefinedStringSymbolNumberSymbol和 引用类型 Object,Function下面看一些栗子 //基础类型也可以说非引用类型 let str = ‘hello word!’ console.log(typeof str) //string let num = 12 console.log(typeof num) //number let udf = undefined console.log(typeof udf) //undefined let bl = true console.log(typeof bl) //boolean let nl = null console.log(nl) //null let sl = Symbol() console.log(typeof sl) //symbol //复合类型也可以说是引用类型, let obj = { a: 1 } console.log(typeof obj) //object let arr = [1, 2, 3] console.log(typeof arr) //object //函数也属于引用类型,不过typeof却能准确的返回类型 function fn() {} console.log(typeof fn) //function从以上的执行结果可以看出,typeof不能够准确分返回所有类型instenceof我们从instenceof的实现来了解下instenceof是干什么的 // 模拟instenceof实现 // 1、instenceof接收两个参数 // 2、返回true或false function myInstenceof(X, Y) { let L = X.proto let R = Y.prototype if (L !== R) { return false } return true } // test function Fn() { } let newFn = new Fn() console.log(newFn) console.log(new Fn()) console.log(myInstenceof(newFn, new Fn())) //true console.log(myInstenceof([], new Array())) //true console.log(myInstenceof([], new Object())) //true以上demo我们就能看出,instenceof也不够靠谱,模拟实现就是判断X的原型知否在Y的原型链上。数组之所以instenceof Object为true是因为 [].prototype->new Array->new Object->null上边说了typeof和instenceof其实就是想说这两个对于深度clone的实现来说不够严谨要不就是多层判断。Object.prototype.toString.call()接下里我们说个靠谱的 Object.prototype.toString.call(’’); // [object String] Object.prototype.toString.call(1); // [object Number] Object.prototype.toString.call(true); // [object Boolean] Object.prototype.toString.call(undefined); // [object Undefined] Object.prototype.toString.call(null); // [object Null] Object.prototype.toString.call(new Function()); // [object Function] Object.prototype.toString.call(new Date()); // [object Date] Object.prototype.toString.call([]); // [object Array] Object.prototype.toString.call(new RegExp()); // [object RegExp] Object.prototype.toString.call(new Error()); // [object Error] Object.prototype.toString.call(document); // [object HTMLDocument] Object.prototype.toString.call(window); //[object Window]靠谱!接下来我们就用Object.prototype.toString.call()来解答一下面试题 function clone(obj, o) { //Object.prototype.toString.call(obj)返回类似[Object Array] 利用slice来截取我们需要的字符串 let type = Object.prototype.toString.call(obj).slice(8, -1) // 如果是Object if (type === ‘Object’) { o = {} for (let k in obj) { o[k] = clone(obj[k]); } // 如果是对象 } else if (type === ‘Array’) { o = [] for (let i = 0; i < obj.length; i++) { o.push(clone(obj[i])); } } else { // 非引用类型 o = obj } return o } let obj1 = { a: [1, 2, 3, 4, 5, 6], b: { c: 2 }, d: 1, f: function() { return 1 } } let obj2 = clone(obj1) obj2.f = function() { return 2 } obj2.a = 1 console.log(obj1) console.log(obj2)测试打印结果,obj2的改变不会影响到obj1。欢迎吐槽! ...

January 29, 2019 · 2 min · jiezi

Js高级编程笔记--面向对象的程序设计

理解对象属性类型1.数据属性特性:Configurable : 表示能否通过 delete 删除属性,能否修改属性特性,能否把属性改为访问器属性Enumerable : 表示能否通过 for in 循环返回Writable : 表示能否修改属性的值Value : 包含属性的值,读取或写入实际上操作的是这个值2.访问器属性特性:Configurable : 表示能否通过 delete 删除属性,能否修改属性特性,能否把属性改为访问器属性Enumerable : 表示能否通过 for in 循环返回Get : 读取时调用的参数.默认值为 undefinedSet : 写入时调用的参数。 默认值为 undefined3.注意:访问器属性不能直接定义,必须使用 Object.defineProperty()定义。修改属性默认的特性,必须使用 Object.defineProperty()方法get,set,并不一定要定义,只定义 get 为只读,只定义 set 为只写不可读。定义多个属性可以使用 Object.defineProperties()方法读取属性的特性,使用 Object.getOwnPropertyDescriptor()创建对象1.工厂模式定义一个方法接受参数,用于创建对象,并将其返回function createPerson(name, age) { var o = new Object(); o.name = name; o.age = age; return o;}var person1 = createPerson(‘andy_chen’, 18);var person2 = createPerson(‘andy_chen’, 18);工厂模式可以创建多个相似对象的问题,却没解决对象识别的问题。例如person1的类型是什么2.构造函数模式 :function Person(name, age) { this.name = name; this.age = age; this.sayName = function() { alert(this.name); };}var person1 = new Person(‘andy_chen’, 18);var person2 = new Person(‘andy_chen’, 18);person1.sayName();person2.sayName();使用 new 操作符。实际上有以下 4 个步骤:创建一个新对象将构造函数的作用域赋给对象(使 this 指向新对象)执行构造方法(为这个对象添加属性)返回新对象构造函数的问题在于,每个方法都要在每个实例中重新创建一遍。即例子中,person1和person2的sayName的不相等的。但是,完成同样的功能的方法,却每个实例都要创建一遍,这显然不合理,所以,又出现了下面的原型模式3.原型模式:理解原型对象一图胜千言:只要创建了一个新函数,就会根据一组特定规则为该函数创建一个 prototype,这个属性指向函数的对象原型。对象原型中,则默认有一个 constructor 属性,指向该新函数。通过新函数创建的实例,有一个[[prototype]]属性(在 chrome,firefox,safari 中该属性即为proto),指向了新函数的 prototype。注意:该属性仅仅是执行构造函数的 prototype,也即是说,他们与构造函数没有直接联系了读取某个对象的属性时,会先在实例上找,如果没找到,则进一步在实例上的 prototype 属性上找为实例添加属性的时候会屏蔽掉原型上属性。这个时候即使置为 null 也没法访问到原型上的属性,只有通过 delete 删掉之后才可以XXX.prototype.isPrototype(xxx), 可以用这个方法判定对象是否是该实例的原型对象Object.getPrototypeOf() 用这个可以获取实例对应的原型对象 (ES5 新增方法)in 操作符单独使用时: in 操作符 可以确定属性是否存在于对象上(无论是存在于实例上还是原型上)用于 for 循环中时,返回的是所有能够通过对象访问的,可枚举的属性。(IE8 中,如果开发者自定义 toString 类似的系统不可枚举的方法,浏览器还是不会将它遍历出来)ES5:Object.keys() 可以返回一个包含所有可枚举属性的字符串数组Object.getOwnPropertyNames() 可以返回所有实例属性,无论是否可枚举//原型模式的实现:function Person() {}Person.prototype.name = ‘andy chen’;Person.prototype.sayName = function() { alert(this.name);};更简单的原型语法重写整个 prototype,不过会导致 constructor 改变。所以需要重新指定 constructor.//更简单的原型语法function Person() {}Person.prototype = { constructor: Person, //因为这种写法会覆盖掉原来的Person.prototype,需要重新为constructor赋值 name: ‘andy chen’, sayName: function() { alert(this.name); }};var person1 = new Person();var person2 = new Person();原型模式的问题:所有实例都共享一个prototype,类似上面的例子,person1,person2的name属性是共享的。如果修改其中一个,会导致另一个也受影响。所以,才会出现下面构造函数与原型模式组合使用4.组合使用构造函数和原型模式创建自定义类型最常见的方式就是组合使用构造函数和原型模式构造函数定义实例属性,而原型模式用于定义方法和共享的属性. 所以,上面的例子可以改写成这样:function Person(name) { this.name = name;}Person.prototype = { constructor: Person, sayName: function() { alert(this.name); }};var person1 = new Person(‘andy chen’);var person2 = new Person(‘andy chen’);除了使用组合模式创建对象,还有以下几种方式,可以针对不同的情况选择。5.动态原型模式在构造方法中,判断是否是第一次进入使用构造方法,如果是,则添加一系列的方法到原型上6.寄生构造函数模式类基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码然后再返回新创建的对象。7.稳妥构造函数模式:稳妥对象指的是没有公共属性,而且其方法也不引用 this 对象。最适合用于一些安全的环境或者在防止数据被其他程序改动时使用稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:新创建的对象实例不引用 this.不使用 new 操作符调用构造函数继承OO 语言一般拥有两种继承方式:接口继承(只继承方法签名)以及实现继承(继承实际方法)ES 无法像其他 OO 语言一样支持接口继承,只能依靠原型链实现 实现继承1. 原型链要了解原型链的概念,先回顾一下构造函数,原型和实例之间的关系(参考图 6-1)每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针.每个实例都包含一个指向原型对象的内部指针的内部属性(在 chrome 中一般为proto属性)那么,如果我们有个新的构造函数,并让它的原型对象等于另一个类型的实例,结果会怎样.对于这个新的构造函数,它的原型对象就变成了另一个类型的实例,而这个实例中,又包含一个内部属性,指向了另一个原型对象(该原型对象内部 constructor 指向另一个构造函数),如果这个原型对象又是另一个类型的实例,则它又包含了一个内部属性,继续指向上层的原型对象。这样层层递进,就形成了原型链。如下图:特点在实例中搜索属性的时候,便是基于原型链来搜索的,先搜索实例,再在原型链上一层层往上搜,直到找到或者到原型链末端才会停下来由于所有引用类型都继承了 Object,所以原型链的最顶层是 Object使用原型链实现继承时,不能使用对象字面量创建原型方法,因为这样会重写原型链原型链实现继承的方式:function Animal() { this.name = ‘animal’;}Animal.prototype.getName = function() { return this.name;};function Cat() { this.catName = ‘cat’;}Cat.prototype = new Animal();var cat1 = new Cat();var cat2 = new Cat();alert(cat1.getName()); //由于第10行,将Cat的原型指向Animal的实例,因为实例中有指向Animal.prototype的指针。所以,这里可以访问到getName()cat1.name = ‘changed name’;alert(cat2.getName());原型链的问题:使用原型链,由于是使用新的实例作为子类型的原型,实例中却包含了父类型的属性,所以原来父类型的属性,就都到了子类型的原型上了。这就会造成子类型的不同实例会共享同个属性.如上例子中,第 15 行,改变 cat1 实例的 name 属性影响到了 cat2 的 name 属性创建子类型的时候,不能向父类型传递参数2. 借用构造函数由于原型链存在问题,所以便出现了借用构造函数的方法在子类型的构造方法中,调用父类型的构造方法:SuperType.call(this); 将父类型的属性添加到子类型上,并且可以传递参数给父类型借用构造函数实现继承的方式:function Animal() { this.name = ‘animal’;}function Cat() { Animal.call(this);}var cat1 = new Cat();var cat2 = new Cat();cat1.name = ‘changed name’;alert(cat1.name); //changed namealert(cat2.name); //animal //借用构造函数的方式,各实例之间的属性便不会互相影响借用构造函数问题:类似创建对象单纯使用构造方法一样,也会造成公有的方法无法公用。所以一般也很少单独使用此方式3. 组合继承组合原型链以及借用构造函数使用原型链实现对原型属性和方法的继承借用构造函数来实现对实例中属性的继承。function Animal() { this.name = ‘animal’;}Animal.prototype.getName = function() { return this.name;};function Cat() { Animal.call(this); //借用构造函数}Cat.prototype = new Animal(); //原型链方式Cat.prototype.constructor = Cat;//这里可以var cat1 = new Cat();var cat2 = new Cat();cat1.name = ‘changed name’;alert(cat1.getName()); //changed namealert(cat2.getName()); //animal组合继承的问题:父类的属性会存在于子类型的原型上,导致被不同实例共享。虽然由于借用构造函数之后,导致实例上又重写了这些属性,所以每个实例有各自的属性。另外,instanceof 和 isPrototypeOf 能够识别基于组合继承创建的对象组合继承,并不完美因为我们只需要继承父类型原型上的属性而已,不需要父类型实例的属性。还有更好的方法,但我们首先要先了解一下其他继承方式4. 原型式继承//如果o为某个对象的prototype,则object返回的 对象,包含了该对象原型上的所有方法function object(o) { function F() {} F.prototype = o; return new F();}Es5 新增的 Object.create() ,类似这样。 在没有必要创建构造函数,只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。不过,包含引用类型值的属性始终会共享5. 寄生式继承在复制新对象后,继续以某种方式增强对象,即为寄生式继承。function createAnother(original) { var clone = object(original); clone.sayHi = function() { doSomeThing(); }; return clone;}在主要考虑对象而不是自定义类型和构造函数的时候,适合使用寄生式继承缺点: 类似单纯的构造函数模式使用,函数不能复用6. 寄生组合式继承通过原型式继承,继承父类的原型方法。再通过构造函数方法,继承父类的属性。function Animal() { this.name = ‘animal’;}Animal.prototype.getName = function() { return this.name;};function Cat() { Animal.call(this); //借用构造函数}//原型继承方式function object(superProto) { function F() {} F.prototype = superProto; return new F();}Cat.prototype = object(Animal.prototype); //通过一个空的函数作为媒介,将空函数的原型指向父类型原型,并将子类型的原型指向这个空函数的实例。便只继承父类原型上的属性及方法Cat.prototype.constructor = Cat;//这里可以之后添加子类的方法Cat.prototype.run = function() { alert(‘cat run’);};var cat1 = new Cat();var cat2 = new Cat();cat1.name = ‘changed name’;alert(cat1.getName()); //changed namealert(cat2.getName()); //animal最后,寄生组合式继承是引用类型最理想的继承范式。上述代码还能再进一步优化。//原型继承方式function object(superProto) { function F() {} F.prototype = superProto; return new F();}//公用的继承方法function inheritPrototype(subType, superType) { subType.prototype = object(superType.prototype); subType.prototype.constructor = subType;}function Animal() { this.name = ‘animal’;}Animal.prototype.getName = function() { return this.name;};function Cat() { Animal.call(this); //借用构造函数}inheritPrototype(Cat, Animal); //调用此方法继承原型//这里可以之后添加子类的方法Cat.prototype.run = function() { alert(‘cat run’);};var cat1 = new Cat();var cat2 = new Cat();cat1.name = ‘changed name’;alert(cat1.getName()); //changed namealert(cat2.getName()); //animal小结这是 js 对象的创建以及继承,es6 中新增了关键字class和extend。方便我们进行面向对象的编程。但是理解背后的继承原理对我们编程过程中也是极有帮助的:)喜欢就收藏或者点个赞呗 !! ...

January 2, 2019 · 3 min · jiezi

js数据类型--object

系列文章1 、从数据类型讲原型原型链内容回顾在JavaScript中,数据类型可以分为原始类型以及引用类型。其中原始类型包括string,number, boolean, null, undefined, symbol(ES6新增,表示独一无二的值),这6种数据类型是按照值进行分配的,是存放在栈(stack)内存中的简单数据段,可以直接访问,数据大小确定,内存空间大小可以分配。引用类型包括function,object,array等可以可以使用new创建的数据,又叫对象类型,他们是存放在堆(heap)内存中的数据,如var a = {},变量a实际保存的是一个指针,这个指针指向对内存中的数据 {}。对象类型包含但不限于object、function、array、string、boolean、number、date、regexp。这一篇文章则主要讲object类型的功能及用法。一、开篇在js中,function类型的数据叫函数,又叫方法,array类型的数据叫数组,而object类型的数据就叫对象,需要根据实际情况来区分一些文章中的对象与对象类型。什么是对象?对象就是封装一个事物的属性和功能的程序结构,是内存中保存多个属性和方法的一块存储空间。什么是面向对象编程?面向对象编程(OOP)是一种编程思想,是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。它的特征是封装、继承、多态,优点是易维护、易扩展、质量高、效率高。通俗的讲就是以对象为基础来进行软件开发的思想,举个例子,比如我们做一个雨滴落地的动画,那么可以用面向对象的方法把雨滴看成一个对象,这个对象中包含颜色、形状、大小、速度等属性,同时还包括了开始下落、下落过程、落地后等方法 。二、创建对象1、直接量(字面量)创建因为这种创建方法很直接,所以叫直接量,又叫字面量var obj = {a:1}2、构造函数创建这一方法创建对象又叫工厂函数创建对象,顾名思义就是批量生产,使用这个方法我们需要用到关键字new。运行环境内置了一个对象的构造函数—Object,我们直接使用就可以了var obj = new Object({a:1}); 上面的例子中,我们在Object中加了一个参数,这个参数是初始化的值(将参数赋值给新对象,不是克隆),这个参数的格式需要符合对象的格式要求,如果你传入了一个别的类型的参数,那么创建的就不是object类型的对象了(数据类型还是对象,但不是object)var a = new Object(‘a’); // String {“a”}var b = new Object(’{a:1}’); // String {"{a:1}"}var c = new Object(1); // Number {1}另外,如果没有参数需要传入,则Object后面的()可以省略,即var a = new Object; //{}除了环境自带的构造函数,我们还可以根据实际需求自定义构造函数function Student(name,age){ this.name = name this.age = age}var xm = new Student(‘xiaoM’,12); // Student {name: “xiaoM”, age: 12}var xh = new Student(‘xiaoH’,14); // Student {name: “xiaoH”, age: 14}3、Object.create创建对象我们知道,Object是一个构造函数,而js中函数也是对象类型的数据,所以函数也是有属性的,而create就是其中的一个方法,该方法的作用是根据现有的对象新创建一个子对象。var son=Object.create(父对象);这句话做了2件事:1. 创建空对象son2. 设置son的__proto__指向父对象上面的解释其实是有一点瑕疵的,因为我们不但可以根据现有的对象来创建子对象,还可以根据null来创建子对象,我这么说是想强调下null不是一个对象,虽然Object.prototype继承自null,但null也不是一个对象,你可能会说:typeof null === “object” // trueObject.prototype.toString(null) === “[object Object]” // true但它确实不是一个对象,这是js的历史遗留问题,详细可以看这里 typeof null的前世今生继续回到我们的文章上来,如果我们使用了null作为父对象来创建一个子对象,那么子对象.proto === null //true和Object.prototype一个级别的!!!不过不同的是,Object.prototype是运行环境封装的,里面天生有一些属性及方法,而用null创建的子对象就像一个新生儿一样,里面干干净净的,什么都没有。使用null创建子对象的缺点就是我们不再能够使用Object.prototype自带的一些属性及API,如果需要的话,你就得自己去实现了,好处就是这个子对象是一个干干净净的对象,里面的一些属性及方法可以按照自己的想法来,不用担心与原型链上属性重名,造成变量污染,多用于存储数据。4、ES6 的 class创建对象 class关键字是ES6引入的概念,用于定义类(对象),用法如下://定义类class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return ‘(’ + this.x + ‘, ’ + this.y + ‘)’; }}可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象Point。Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。ES6 的类,完全可以看作构造函数的另一种写法。class Point { // …}typeof Point // “function"Point === Point.prototype.constructor // true上面代码表明,类的数据类型就是函数,类本身就指向构造函数。使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。var point = new Point(2,3);point.toString() // “(2,3)“这里执行new Point(2,3)可以看作是执行了类Point里的constructor方法为x,y赋值,同时把toString函数挂到Point.prototype上去构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。更多class的使用说明及其他es6的特性看一看阮一峰老师的ECMAScript 6 入门,讲的很详细,上面关于class的讲解也都摘抄自阮一峰老师的这本书里。三、对象的遍历遍历就是依次处理该数据结构的所有成员,JavaScript中的遍历是针对表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。其中对象的遍历主要是使用for…in结构,用法如下:var a = {a:1,b:2}for(var k in a){ console.log(k)}//a b在 for…in 的使用中,我们需要小心的是,他不但可以遍历自己的私有属性,还会遍历其原型链上的公有属性。私有属性:对象本身所具有的属性公有属性:包括私有属性在内的其原型链上的可访问到的属性var a = {a:1,b:2}a.proto = {c:4}for(var k in a){ console.log(k)}//a b c我们可以通过obj.hasOwnProperty来判断一个属性是不是对象的自有属性for(var k in a){ if( a.hasOwnProperty( k ) ){ console.log(k) }}//a b以上,我们把所有可以遍历到的属性叫做可枚举属性,那么什么是可枚举属性和不可枚举属性呢?可枚举属性是指那些内部 “可枚举” 标志(enumerable)设置为 true 的属性,对于通过直接的赋值和属性初始化的属性,该标识值默认为即为 true,对于通过 Object.defineProperty、Object.create 等定义的属性,该标识值默认为 false。可枚举的属性可以通过 for…in 循环进行遍历(除非该属性名是一个 Symbol)。相对的,不可枚举属性就是用 for…in 遍历不到的属性,js中内置属性是遍历不到的。四、Object/Object.prototype常用API介绍我们知道无论是object、function、array还是string、boolean、number、date,他们都是对象类型的数据,我们在可以使用console.dir()来查看一个数据的结构。这样我们就可以很全面的看到一个对象上有哪些属性了。1、Object构造函数的方法Object是object的构造函数,我们使用console.dir(Object),可以看到:Object.assign()通过复制一个或多个对象来创建一个新的对象。Object.create()使用指定的原型对象和属性创建一个新对象。Object.defineProperty()给对象添加一个属性并指定该属性的配置。Object.defineProperties()给对象添加多个属性并分别指定它们的配置。Object.entries()返回给定对象自身可枚举属性的[key, value]数组。Object.freeze()冻结对象:其他代码不能删除或更改任何属性。Object.getOwnPropertyDescriptor()返回对象指定的属性配置。Object.getOwnPropertyNames()返回一个数组,它包含了指定对象所有的可枚举或不可枚举的属性名。Object.getOwnPropertySymbols()返回一个数组,它包含了指定对象自身所有的符号属性。Object.getPrototypeOf()返回指定对象的原型对象。Object.is()比较两个值是否相同。所有 NaN 值都相等(这与==和===不同)。Object.isExtensible()判断对象是否可扩展。Object.isFrozen()判断对象是否已经冻结。Object.isSealed()判断对象是否已经密封。Object.keys()返回一个包含所有给定对象自身可枚举属性名称的数组。Object.preventExtensions()防止对象的任何扩展。Object.seal()防止其他代码删除对象的属性。Object.setPrototypeOf()设置对象的原型(即内部[[Prototype]]属性)。Object.values()返回给定对象自身可枚举值的数组。2、Object 实例和Object 原型对象的方法Object.prototype本身是一个对象,所以我们使用console.log(Object.prototype),就可以看到他的结构:Object.prototype.hasOwnProperty()返回一个布尔值 ,表示某个对象是否含有指定的属性,而且此属性非原型链继承的。Object.prototype.isPrototypeOf()返回一个布尔值,表示指定的对象是否在本对象的原型链中。Object.prototype.propertyIsEnumerable()判断指定属性是否可枚举Object.prototype.toLocaleString()直接调用 toString()方法。Object.prototype.toString()返回对象的字符串表示。Object.prototype.valueOf()返回指定对象的原始值。由于 Object 以及 Object.prototype 的属性太多,这里就不详细阐述了,感兴趣的小伙伴可以到 MDN 观看,强烈建议把Object、Object.prototype的属性都过一遍。 ...

September 3, 2018 · 1 min · jiezi