面向对象、JS遍历、function、scope
【一】 面向对象的基本概念
面向对象的英文全称叫做Object Oriented,简称OO。OO其实包括OOA(Object Oriented Analysis,面向对象分析)、OOD(Object Oriented Design,面向对象设计)和OOP(Object Oriented Programming,面向对象的程序设计)。
通常所说的面向对象是指OOP, OOP是一种围绕真实世界的概念来组织模型的程序设计方法,它采用对象来描述问题空间的实体。在使用计算机解决问题时,对象是作为计算机模拟真实世界的一个抽象,一个对象就是一个物理实体或逻辑实体,它反映了系统为之保存信息和(或)与它交互的能力。使其具有自己的属性和行为, 从而简化对复杂事物的描述,更有利于工程的可维护性和扩展性。
OOP同结构化程序设计相比最大的区别就在于: 前者首先关心的是所要处理的数据,而后者首先关心的是功能。
【二】 面向对象三个基本特征
封装 (Encapsulation) 将数据以及相关的操作组织在一起,成为独立的构件。外部无法直接访问这些封装了的数据,从而保证了这些数据的正确性。封装的目的是为了内部数据表现形式和实现细节的隐藏,信息隐藏是为了减少系统各部分间的依赖性,各部分间必须通过明确的通道传送信息,也就是对象间的接口.这样一来,隐藏了部分内部的细节,极大方便系统的开发,维护和扩展。
继承 (Inheritance) 继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。一个新类可以从现有的类中派生,这个过程称为类的继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且派生类可以修改或增加新的方法使之更适合特殊的需求。继承性很好地解决了软件的可重用性问题。
多态 (Polymorphism) 多态是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是允许类与类之间相同方法名的指针得以调用, 这样很好地解决了应用程序函数同名问题。实现多态,有二种方式,覆盖,重载。
【三】 Javascript 面向对象
javascript本身是一种基于对象(object-based)的语言,我们日常编码过程中用到的所有东西几乎都是对象(Number, String, Boolean, etc.)。但是,相对于一些流行的面向对象语言(C++, C#, java),它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class的概念。
Keyword: class, object, `this`, closure, constructor, prototype
几种对象封装的方法
- 继承
- 多态体现
之一、几种对象封装的方法
1. 对象封装 – 原始模式
假定我们把猫看成一个对象,它有”name”和”color”两个属性, “etc” 行为。
var Cat = {
name: ''
color: '',
eat: function() {}
};
现在,我们需要根据这个原型对象的规格(schema),生成两个实例对象。
function eat() { console.log('I\'m eta fish'); } var cat1 = {name: 'Kitty', color: 'white', eat: eat}; var cat2 = {name: 'Smokey', color: 'black', eat: eat}; // var cat3, cat4 ,...
不方便创建多个实例对象,扩展性差, 实例(cat1, cat2)之间找不到联系。…
2. 对象封装 – 构造函数模式
“构造函数”,就是一个普通函数,但是内部使用了 `this` 变量。对函数使用 `new` 运算符,就能生成实例,并且 `this` 变量会绑定在实例对象上。
使用构造器创建出来的对象会有一个 `constructor` 属性,指向它们的构造函数。
`Class` 只是一个模板,创建出来的来实例都是由模板生成。
比如,猫的原型对象现在可以这样写:
function Cat(name,color){
this.name = name;
this.color = color;
this.eat = function() { console.log('eat fish'); };
}
var cat1 = new Cat('Kitty', 'black');
console.log(cat1.name); // Kitty
console.log(cat1 instanceof Cat); // TRUE
// 这时 cat1 实例会自动含有一个 `constructor` 属性,指向它们的构造函数 `Cat`。
var cat2 = Cat('Smokey', 'white');
console.log(cat2); // undefined
一、一般的遍历数组的方法:
var array = [1,2,3,4,5,6,7]; for (var i = 0; i < array.length; i) { console.log(i,array[i]); }
二、用for in的方遍历数组
for(let index in array) { console.log(index,array[index]); };
三、forEach
array.forEach(v=>{ console.log(v); });
array.forEach(function(v){ console.log(v); });
四、用for in不仅可以对数组,也可以对enumerable对象操作
var A = {a:1,b:2,c:3,d:"hello world"}; for(let k in A) { console.log(k,A[k]); }
五、在ES6中,增加了一个for of循环,使用起来很简单
for(let v of array) { console.log(v); };
let s = "helloabc";
for(let c of s) {
console.log(c);
}
总结来说:for in总是得到对像的key或数组,字符串的下标,而for of和forEach一样,是直接得到值
结果for of不能对象用
对于新出来的Map,Set上面
var set = new Set(); set.add("a").add("b").add("d").add("c"); var map = new Map(); map.set("a",1).set("b",2).set(999,3); for (let v of set) { console.log(v); } console.log("--------------------"); for(let [k,v] of map) { console.log(k,v); }
javascript遍历对象详细总结
1.原生javascript遍历
(1)for循环遍历
let array1 = ['a','b','c']; for (let i = 0;i < array1.length;i++){ console.log(array1[i]); // a b c }
(2)JavaScript 提供了 foreach() map() 两个可遍历 Array对象 的方
forEach和map用法类似,都可以遍历到数组的每个元素,而且参数一致;
Array.forEach(function(value , index , array){ //value为遍历的当前元素,index为当前索引,array为正在操作的数组 //do something },thisArg) //thisArg为执行回调时的this值
不同点:
forEach() 方法对数组的每个元素执行一次提供的函数。总是返回undefined;
例子如下:
var array1 = [1,2,3,4,5]; var x = array1.forEach(function(value,index){ console.log(value); //可遍历到所有数组元素 return value + 10 }); console.log(x); //undefined 无论怎样,总返回undefined var y = array1.map(function(value,index){ console.log(value); //可遍历到所有数组元素 return value + 10 }); console.log(y); //[11, 12, 13, 14, 15] 返回一个新的数组
对于类似数组的结构,可先转换为数组,再进行遍历
let divList = document.querySelectorAll('div'); //divList不是数组,而是nodeList //进行转换后再遍历 [].slice.call(divList).forEach(function(element,index){ element.classList.add('test') }) Array.prototype.slice.call(divList).forEach(function(element,index){ element.classList.remove('test') }) [...divList].forEach(function(element,index){ //<strong>ES6写法</strong> //do something })
(3)for ··· in ··· / for ··· of ···
for...in
语句以任意顺序遍历一个对象的可枚举属性。对于每个不同的属性,语句都会被执行。每次迭代时,分配的是属性名
补充 : 因为迭代的顺序是依赖于执行环境的,所以数组遍历不一定按次序访问元素。 因此当迭代那些访问次序重要的 arrays 时用整数索引去进行 for
循环 (或者使用 Array.prototype.forEach()
或 for...of
循环) 。
let array2 = ['a','b','c'] let obj1 = { name : 'lei', age : '16' } for(variable in array2){ //variable 为 index console.log(variable ) //0 1 2 } for(variable in obj1){ //variable 为属性名 console.log(variable) //name age }
ES6新增了 遍历器(Iterator)机制,为不同的数据结构提供统一的访问机制。只要部署了Iterator的数据结构都可以使用 for ··· of ··· 完成遍历操作 ( Iterator详解 : http://es6.ruanyifeng.com/#docs/iterator ),每次迭代分配的是 属性值
原生具备 Iterator 接口的数据结构如下:
Array Map Set String TypedArray 函数的arguments对象 NodeList对象
let array2 = ['a','b','c'] let obj1 = { name : 'lei', age : '16' } for(variable of array2){ //<strong>variable 为 value</strong> console.log(variable ) //'a','b','c' } for(variable of obj1){ //<strong>普通对象不能这样用</strong> console.log(variable) // 报错 : main.js:11Uncaught TypeError: obj1[Symbol.iterator] is not a function }<br><br>let divList = document.querySelectorAll('div');<br><br>for(element of divlist){ //可遍历所有的div节点<br> //do something <br>}
如何让普通对象可以用for of 进行遍历呢? http://es6.ruanyifeng.com/#docs/iterator 一书中有详细说明了!
除了迭代时分配的一个是属性名、一个是属性值外,for in 和 for of 还有其他不同 (MDN文档: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/for...of)
for...in循环会遍历一个object所有的可枚举属性。
for...of会遍历具有iterator接口的数据结构
for...in
遍历(当前对象及其原型上的)每一个属性名称,而 for...of遍历(当前对象上的)每一个属性值
Object.prototype.objCustom = function () {}; Array.prototype.arrCustom = function () {}; let iterable = [3, 5, 7]; iterable.foo = "hello"; for (let i in iterable) { console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom" } for (let i of iterable) { console.log(i); // logs 3, 5, 7 }
箭头函数感性认识
箭头函数 是在es6 中添加的一种规范
x => x * x 相当于 function(x){return x*x}
箭头函数相当于 匿名函数, 简化了函数的定义。 语言的发展都是倾向于简洁 对人类友好的, 减轻工作量的。 就相当于我最钟情的python, 有很多类似之处, 在 关于promise 文章中我会和Python 框架中的tornado 中的异步进行对比的, 很相似。
箭头函数有两种格式, 一种只包含一个表达式,没有{…} 和 return 。 一种包含多条语句, 这个时候{} return 就不能省略
x => {
if (x>0){
return x*x
}else{
return x
}
}
如果有多个参数就要用():
// 两个参数返回后面的值
(x, y) =>x*y + y*y
//没有参数
() => 999
// 可变参数
(x, y, ...rest) =>{
var i,sum = x+y;
for (i=0;i<rest.length;i++){
sum += rest[i];
}
return sum;
}
scope和closure是javascript中两个非常关键的概念,前者JS用多了还比较好理解,closure就不一样了。
1、function
在开始之前呢,先澄清一点(废话咋这么多捏),函数在JavaScript中是一等公民。函数在JavaScript中不仅可以调用来调用去,它本身也可以当做值传递来传递去的。
2、scope及变量查询
作用域,也就是我们常说的词法作用域,说简单点就是你的程序存放变量、变量值和函数的地方。
块级作用域
如果你接触过块级作用域,那么你应该非常熟悉块级作用域。简单说来就是,花括号{}括起来的代码共享一块作用域,里面的变量都对内或者内部级联的块级作用域可见。
基于函数的作用域
在JavaScript中,作用域是基于函数来界定的。也就是说属于一个函数内部的代码,函数内部以及内部嵌套的代码都可以访问函数的变量。如下:
上面定义了一个函数foo,里面嵌套了函数bar。图中三个不同的颜色,对应三个不同的作用域。①对应着全局scope,这里只有foo②是foo界定的作用域,包含、b、bar③是bar界定的作用域,这里只有c这个变量。在查询变量并作操作的时候,变量是从当前向外查询的。就上图来说,就是③用到了a会依次查询③、②、①。由于在②里查到了a,因此不会继续查①了。
这里顺便讲讲常见的两种error,ReferenceError和TypeError。如上图,如果在bar里使用了d,那么经过查询③、②、①都没查到,那么就会报一个ReferenceError;如果bar里使用了b,但是没有正确引用,如b.abc(),这会导致TypeError。
严格的说,在JavaScript也存在块级作用域。如下面几种情况:
①with
1 var obj = {a: 2, b: 2, c: 2}; 2 with (obj) { //均作用于obj上 3 a = 5; 4 b = 5; 5 c = 5; 6 }
②let
let是ES6新增的定义变量的方法,其定义的变量仅存在于最近的{}之内。如下:
var foo = true; if (foo) { let bar = foo * 2; bar = something( bar ); console.log( bar ); } console.log( bar ); // ReferenceError
③const
与let一样,唯一不同的是const定义的变量值不能修改。如下:
1 var foo = true; 2 if (foo) { 3 var a = 2; 4 const b = 3; //仅存在于if的{}内 5 a = 3; 6 b = 4; // 出错,值不能修改 7 } 8 console.log( a ); // 3 9 console.log( b ); // ReferenceError!
3、scope的如何确定
无论函数是在哪里调用,也无论函数是如何调用的,其确定的词法作用域永远都是在函数被声明的时候确定下来的。理解这一点非常重要。
4、变量名提升
这也是个非常重要的概念。理解这个概念前,需要了解的是,JS代码的执行过程分为编译过程和执行。举例如下:
1 var a = 2;
以上代码其实会分为两个过程,一个是 var a; 一个是 a = 2; 其中var a;是在编译过程中执行的,a =2是在执行过程中执行的。理解了这个,那么你就应该知道下面为何是这样的结果了:
1 console.log( a );//undefined 2 var a = 2;
其执行效果如下:
1 var a; 2 console.log( a );//undefined
3 a = 2;
我们看到,变量声明提前了,这就是为什么叫变量名提升了。所以在编译阶段,编译器会将函数里所有的声明都提前到函数体内的上部,而真正赋值的操作留在原来的位置上,这也就是上面的代码打出undefined的原因。需要注意的是,变量名提升是以函数为界的,嵌套函数内声明的变量不会提升到外部函数体的上部。希望你懂这个概念了,如果不懂,可以参考我之前写的《也谈谈规范JS代码的几个注意点》及评论回答部分。
5、闭包
了解这些了后,我们来聊聊闭包。什么叫闭包?简单的说就是一个函数内嵌套另一个函数,这就会形成一个闭包。这样说起来可能比较抽象,那么我们就举例说明。但是在距离之前,我们再复习下这句话,来,跟着大声读一遍,“无论函数是在哪里调用,也无论函数是如何调用的,其确定的词法作用域永远都是在函数被声明的时候确定下来的”。
1 function foo() { 2 var a = 2; 3 function bar() { 4 console.log( a ); // 2 5 } 6 bar(); 7 } 8 foo();
我们看到上面的函数foo里嵌套了bar,这样bar就形成了一个闭包。在bar内可以访问到任何属于foo的作用域内的变量。好,我们看下一个例子:
1 function foo() { 2 var a = 2; 3 function bar() { 4 console.log( a ); 5 } 6 return bar; 7 } 8 var baz = foo(); 9 baz(); // 2
在第8行,我们执行完foo()后按说垃圾回收器会释放foo词法作用域里的变量,然而没有,当我们运行baz()的时候依然访问到了foo中a的值。这是因为,虽然foo()执行完了,但是其返回了bar并赋给了baz,bar依然保持着对foo形成的作用域的引用。这就是为什么依然可以访问到foo中a的值的原因。再想想,我们那句话,“无论函数是在哪里调用,也无论函数是如何调用的,其确定的词法作用域永远都是在函数被声明的时候确定下来的”。
来,下面我们看一个经典的闭包的例子:
1 for (var i=1; i<=9; i++) { 2 setTimeout( function timer(){ 3 console.log( i ); 4 },1000 ); 5 }
运行的结果是啥捏?你可能期待每隔一秒出来1、2、3...10。那么试一下,按F12,打开console,将代码粘贴,回车!咦???等一下,擦擦眼睛,怎么会运行了10次10捏?这是肿么回事呢?咋眼睛还不好使了呢?不要着急,等我给你忽悠!
现在,再看看上面的代码,由于setTimeout是异步的,那么在真正的1000ms结束前,其实10次循环都已经结束了。我们可以将代码分成两部分分成两部分,一部分处理i++,另一部分处理setTimeout函数。那么上面的代码等同于下面的:
1 // 第一个部分 2 i++; 3 ... 4 i++; // 总共做10次 5 6 // 第二个部分 7 setTimeout(function() { 8 console.log(i); 9 }, 1000); 10 ... 11 setTimeout(function() { 12 console.log(i); 13 }, 1000); // 总共做10次
看到这里,相信你已经明白了为什么是上面的运行结果了吧。那么,我们来找找如何解决这个问题,让它运行如我们所料!
因为setTimeout中的匿名function没有将 i 作为参数传入来固定这个变量的值, 让其保留下来, 而是直接引用了外部作用域中的 i, 因此 i 变化时, 也影响到了匿名function。其实要让它运行的跟我们料想的一样很简单,只需要将setTimeout函数定义在一个单独的作用域里并将i传进来即可。如下:
1 for (var i=1; i<=9; i++) { 2 (function(){ 3 var j = i; 4 setTimeout( function timer(){ 5 console.log( j ); 6 }, 1000 ); 7 })(); 8 }
不要激动,勇敢的去试一下,结果肯定如你所料。那么再看一个实现方案:
1 for (var i=1; i<=9; i++) { 2 (function(j){ 3 setTimeout( function timer(){ 4 console.log( j ); 5 }, 1000 ); 6 })( i ); 7 }
啊,居然这么简单啊,你肯定在这么想了!那么,看一个更优雅的实现方案:
1 for (let i=1; i<=9; i++) { 2 setTimeout( function timer(){ 3 console.log( i ); 4 }, 1000 ); 5 }
咦?!肿么回事呢?是不是出错了,不着急,我这里也出错了。这是因为let需要在strict mode中执行。具体如何使用strict mode模式,自行谷歌吧!
转自:https://www.cnblogs.com/cleverle/p/9943082.html