面向对象

面向对象方法的三大基本特征:封装(Encapsulation),继承(Inheritance)和多态(Polymorphism)

封装:将对象的实现细节隐藏起来,然后通过一些公用方法暴露该对象的功能

继承:是实现代码复用的重要手段。Java的继承具有单继承的特点,即只能继承自一个父类,每个子类只有一个直接父类,但是其父类又可以继承于另一个类,从而实现了子类可以间接继承多个父类,但其本质上划分仍然是一个父类和子类的关系。

多态:子类对象可以直接赋给父类变量,但运行时依然表现出子类的行为特征,这意味着同一类型的对象在执行同一方法时,可能表象出多种行为特征。

1. 类:

类是具备某些共同特征的实体的集合,它是一种抽象的概念。

类是对象的抽象,对象是类的实例。而类是整个软件系统最小的程序单元,类的封装性将各种信息细节隐藏起来,并通过公用方法来暴露该类对外所提供的功能,从而提高了类的内聚性,降低了对象之间的耦合性。

2. 对象:

对象是类的一个实例。

3. 消息:

一个实例与另一个实例之间相互通信的机制。

Javascript 面向对象

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;

  map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。返回值是一个新的数组;

  例子如下:

复制代码
复制代码
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模式,自行谷歌吧!




  6、运用


  撤了这么多,你肯定会说,这TM都是废话啊!囧,那么下面就给你讲一个用处的例子吧,也作为本文的结束,也作为一个思考题留给你,看看那里用到了闭包及好处。


复制代码
 1 function Person(name) {
 2     function getName() {
 3         console.log( name );
 4     }
 5     return {
 6         getName: getName
 7     };
 8 }
 9 var littleMing = Person( "fool" );
10 littleMing.getName();
posted @ 2018-11-18 22:37  alive*  阅读(85)  评论(0编辑  收藏  举报