2018-06-27

JavaScript(面向对象+原型理解+继承+作用域链和闭包+this使用总结)

一、面向对象

1、什么是面向对象

☞ 面向对象就是把构成问题事物分解成多个对象,建立对象不是为了完成某个步骤,而是描述某个事物在这个解决问题的步骤中的行为。

1.面向对象是一种思维方法

2.面向对象是一种编程方法

3.面向对象并不只针对某一种编程语言

2、面向对象和面向过程的区别和联系

1.面向过程过程侧重整个问题的解决步骤,着眼局部或者具体

2.面向对象侧重具体的功能,让某个对象具有这样的功能。更加侧重于整体。

各自的优缺点:

面向过程的优点:
流程化使得编程任务明确,在开发之前基本考虑实现的方法和最终结果;
效率高,面向过程强调代码的短小精悍,善于结合数据结构来开发高效率程序;
流程明确,具体步骤清楚,便于节点分析。

  面向过程的缺点:
      需要深入的思考,耗费精力,代码重用性低,扩展能力差,维护起来难度比较高,
      对复杂业务来说,面向过程的模块难度较高,耦合度也比较高。


面向对象的优点:
结构清晰,程序便于模块化,结构化,抽象化,更加符合人类的思维方式;
封装性,将事务高度抽象,从而便于流程中的行为分析,也便于操作和自省;
容易扩展,代码重用率高,可继承,可覆盖;
实现简单,可有效地减少程序的维护工作量,软件开发效率高。

面向对象的缺点是:
效率低,面向对象在面向过程的基础上高度抽象,从而和代码底层的直接交互非常少机会,从而不适合底层开发和游戏甚至多媒体开发;
复杂性,对于事务开发而言,事务本身是面向过程的,过度的封装导致事务本身的复杂性提高。

3、面向对象的实现方式

☞ 面向对象的实现主流有两种方式:基于类的面向对象和基于原型的面向对象。

☞ 面向对象三大特征:

● 封装

也就是把客观事物封装成抽象的类或具体的对象,并且类或对象可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

● 继承

可以让某个类型的对象获得另一个类型的对象的属性的方法

● 多肽

不同实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。

3.1 基于类的面向对象

☞ 典型的语言:Java、C#

对象(object)依靠 类(class)来产生

3.2 基于原型的面向对象

☞ 典型的语言:JavaScript

对象(object)则是依靠 构造器(constructor)利用 原型(prototype)构造出来的

4、多种创建对象的方式

4.1 使用new Object()创建

4.2 工厂模式创建

☞ 工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程,考虑到在 ECMAScript 中无法创建类,开发人员就发明了一种函数,用函数来封装以特定接口创建对象的细节。

4.3 构造函数模式创建

☞ 为了解决对象类型识别问题,又提出了构造函数模式。这种模式,其实在我们创建一些原生对象的时候,比如Array、Object都是调用的他们的构造函数。
<script type="text/javascript">
function Person (name, age, sex) {
this.name = name;

      this.age = age;
      this.sex = sex;
      
      this.eat = function () {
          alert(this.name + " Eat food");
      }
  }
  var p1 = new Person("Jack", 20, "man");
  p1.eat();   //Jack Eat food
  var p1 = new Person("Mark", 30, "man");
  p1.eat();   //Mark Eat food
  alert(p1 instanceof Person);    

</script>

5、构造函数与普通函数的关系

1.

他们都是函数。构造函数也是函数,也可以像普通的函数一样进行调用。 做普通函数调用的时候,因为没有创建新的对象,所以this其实指向了window对象。

function Person(){
this.name = "Jack"; // 把name属性添加到了window对象上面
alert(this === window); //如果不作为构造方法调用,则 是true
}
Person(); // 把构造函数当做普通方法调用。这个时候内部的this指向了weindow
alert(window.name); //Jack
function Human(){
this.name = "Mark";
alert(this instanceof window); // false
alert(this instanceof Human); //true
}
var h = new Human(); //当做构造函数来调用,创建一个对象
alert(h.name);

2.

构造函数和普通函数仅仅也仅仅是调用方式的不同。也就是说,随便一个函数你如果用new 的方式去使用,那么他就是一个构造函数。

3.

为了区别,如果一个函数想作为构造函数,作为国际惯例,最好把这个构造函数的首字母大写。

4、闭包

<script type="text/javascript">
function createSumFunction(num1, num2){
return function () {
return num1 + num2;
};
}
var sumFun = createSumFunction(3, 4);
var sum = sumFun();
alert(sum);//7
</script>

在上面的代码中,createSumFunction函数返回了一个匿名函数,而这个匿名函数使用了createSumFunction函数中的局部变量(参数),即使createSumFunction这个函数执行结束了,由于作用域链的存在,他的局部变量在匿名函数中仍然可以使用,这个匿名函数就是闭包。

​ 闭包是指有权访问另一个函数作用域中的变量的函数。

​ 闭包是一种特殊的对象。它由两部分构成: 函数,以及创建该函数的环境 。环境由闭包创建时在作用域中的任何局部变量组成。在我们的例子中,sumFun 是一个闭包,由 匿名 函数和闭包创建时存在的num1和num2 两个局部变量组成。

5、闭包的应用

5.1 返回外部函数的局部变量

<script type="text/javascript">
function outer () {
var num = 5;
//定义一个内部函数
function inner () {
//内部函数的返回值是外部函数的一个局部变量
return num;
}
//把局部变量的值++
num++;
// 返回内部函数
return inner;
}
var num = outer()(); // 6
alert(num);
</script>

说明:

1.

这例子中,虽然函数的声明在num++之前,但是函数返回的时候num已经++过了,所以只是num自增之后的值。

2.

结论:闭包中使用的局部变量的值,一定是局部变量的最后的值。

5.2 使用函数自执行和闭包封装对象

封装一个能够增删改查的对象

<script type="text/javascript">
var person = (function () {
//声明一个对象,增删改查均是针对这个对象
var personInfo = {
name : "李四",
age : 20
};
//返回一个对象,这个对象中封装了一些对personInfor操作的方法
return {
//根据给定的属性获取这个属性的值
getInfo : function (property) {
return personInfo[property];
},
//修改一个属性值
modifyInfo : function (property, newValue) {
personInfo[property] = newValue;

          },
          //添加新的属性
          addInfo : function (property, value) {
              personInfo[property] = value;
              
          },
           //删除指定的属性
          delInfo : function (property) {
              delete personInfo[property];
              
          }
      }
  })();
  alert(person.getInfo("name"));
  person.addInfo("sex", "男");
  alert(person.getInfo("sex"));

</script>

五、this使用总结

1、this使用总结

<script>
/*
* 默认绑定:
* 当直接调用一个函数的时候,就是默认绑定
* 1、非严格模式下,默认绑定到window上
*
* 隐式绑定:
* 当使用 对象.方法() 这种方式调用,称之为隐式绑定
* this绑定到前面的那个对象上
*
* new 绑定:
* 使用new来调用构造函数的方式
* this是绑定在新创建的那个对象上
*
* 显示绑定:
* call,apply:
* 都是一锤子买卖,仅仅这一次调用的时候使用了显示绑定,对原函数没有如何的影响
*
* call和apply的区别:就是参数的传递方式
* call:一个一个的传递
* apply:把要传递的参数封装到一个数组中去传递
*
* bind:固定绑定 es6新增
* 调用函数对象的bind方法,返回一个固定this绑定的新的函数
* 对原来的函数没有影响
*
*
*
* 优先级:bind > call,apply > new > 隐式
*/


//1、默认绑定
function foo() {
console.log(this);//this指 obj 谁调用指向谁
}


//2、隐式绑定
var name = "Mark";
var obj = {
name : "Jack",
foo : foo,
foo1 : function () {
console.log(this.name);//this指 window
}
}
obj.foo();//{name: "Jack", foo: function, foo1: function}
var foo1 = obj.foo1;
foo1();//Mark


//3、new 绑定
function foo2() {
this.name = "Lucy";
console.log(this);//this 指向foo2 foo2 {name: "Lucy"}
}
var obj2 = new foo2();
console.log(obj2);//this 指向foo2 foo2 {name: "Lucy"}

var foo3 = obj.foo1;
foo3();//Mark

var obj3 = new foo;//foo {}
console.log(obj3);//foo {}


//4、显示绑定

//call apply
function foo4(a,b) {
console.log(this.name,a,b);
}
foo4.call({name:"Jack"},10,20)//Jack 10 20
foo4.apply({name:"Mark"},[20,10])//Mark 20 10

//apply求max
var arr = [23,65,2,45,3,57,4567];
console.log(Math.max.apply(Math,arr)); //max = 4567

//定义Math 求和的方法
Math.sun = function () {
return Array.prototype.reduce.call(arguments,function (sun,ele) {
return sun + ele;
},0)
}
console.log(Math.sun.apply(Math,arr));//sum = 4762

//bind
var obj4 = {
name : "Jack"
}
function foo5() {
console.log(this.name);
}
var f = foo5.bind(obj4);//这种绑定方式优先级最高
f();//Jack

var obj5 = {
name : "Mark",
foo6 : f
}
obj5.foo6()//Jack
</script>

2、绑定的丢失问题
<script>
/*
* 回调函数的this绑定丢失问题:this会绑定到window
*
* 定时器
*
*
* 显示绑定丢失问题
* 显示绑定传入null、undefined时,this就成了默认绑定
*
*/

//定时器绑定丢失
var name = "Jack";
var obj = {
name : "Mark",
show : function () {
setInterval(function () {
console.log(this.name);// 此时this指向window
},1000)
}
}
obj.show();//Jack++

//解决方法一
var obj1 = {
name : "Mark",
show : function () {
var self = this;
setInterval(function () {
console.log(self.name);// 此时this指向obj1
},1000)
}
}
obj1.show();//Mark++

//解决方法二
var obj2 = {
name : "Joe",
show : function () {
setInterval(function () {
console.log(this.name);// 此时this指向obj2
}.bind(this),1000)
}
}
obj2.show();//Mark++

//显示绑定丢失
function foo() {
console.log(this.name);
}
var f = foo.bind(undefined);// 此时this指向window
f();//Jack
</script>

五、this使用总结

1、this使用总结

<script>
/*
* 默认绑定:
* 当直接调用一个函数的时候,就是默认绑定
* 1、非严格模式下,默认绑定到window上
*
* 隐式绑定:
* 当使用 对象.方法() 这种方式调用,称之为隐式绑定
* this绑定到前面的那个对象上
*
* new 绑定:
* 使用new来调用构造函数的方式
* this是绑定在新创建的那个对象上
*
* 显示绑定:
* call,apply:
* 都是一锤子买卖,仅仅这一次调用的时候使用了显示绑定,对原函数没有如何的影响
*
* call和apply的区别:就是参数的传递方式
* call:一个一个的传递
* apply:把要传递的参数封装到一个数组中去传递
*
* bind:固定绑定 es6新增
* 调用函数对象的bind方法,返回一个固定this绑定的新的函数
* 对原来的函数没有影响
*
*
*
* 优先级:bind > call,apply > new > 隐式
*/


//1、默认绑定
function foo() {
console.log(this);//this指 obj 谁调用指向谁
}


//2、隐式绑定
var name = "Mark";
var obj = {
name : "Jack",
foo : foo,
foo1 : function () {
console.log(this.name);//this指 window
}
}
obj.foo();//{name: "Jack", foo: function, foo1: function}
var foo1 = obj.foo1;
foo1();//Mark


//3、new 绑定
function foo2() {
this.name = "Lucy";
console.log(this);//this 指向foo2 foo2 {name: "Lucy"}
}
var obj2 = new foo2();
console.log(obj2);//this 指向foo2 foo2 {name: "Lucy"}

var foo3 = obj.foo1;
foo3();//Mark

var obj3 = new foo;//foo {}
console.log(obj3);//foo {}


//4、显示绑定

//call apply
function foo4(a,b) {
console.log(this.name,a,b);
}
foo4.call({name:"Jack"},10,20)//Jack 10 20
foo4.apply({name:"Mark"},[20,10])//Mark 20 10

//apply求max
var arr = [23,65,2,45,3,57,4567];
console.log(Math.max.apply(Math,arr)); //max = 4567

//定义Math 求和的方法
Math.sun = function () {
return Array.prototype.reduce.call(arguments,function (sun,ele) {
return sun + ele;
},0)
}
console.log(Math.sun.apply(Math,arr));//sum = 4762

//bind
var obj4 = {
name : "Jack"
}
function foo5() {
console.log(this.name);
}
var f = foo5.bind(obj4);//这种绑定方式优先级最高
f();//Jack

var obj5 = {
name : "Mark",
foo6 : f
}
obj5.foo6()//Jack
</script>

2、绑定的丢失问题
<script>
/*
* 回调函数的this绑定丢失问题:this会绑定到window
*
* 定时器
*
*
* 显示绑定丢失问题
* 显示绑定传入null、undefined时,this就成了默认绑定
*
*/

//定时器绑定丢失
var name = "Jack";
var obj = {
name : "Mark",
show : function () {
setInterval(function () {
console.log(this.name);// 此时this指向window
},1000)
}
}
obj.show();//Jack++

//解决方法一
var obj1 = {
name : "Mark",
show : function () {
var self = this;
setInterval(function () {
console.log(self.name);// 此时this指向obj1
},1000)
}
}
obj1.show();//Mark++

//解决方法二
var obj2 = {
name : "Joe",
show : function () {
setInterval(function () {
console.log(this.name);// 此时this指向obj2
}.bind(this),1000)
}
}
obj2.show();//Mark++

//显示绑定丢失
function foo() {
console.log(this.name);
}
var f = foo.bind(undefined);// 此时this指向window
f();//Jack
</script>

五、this使用总结

1、this使用总结

<script>
/*
* 默认绑定:
* 当直接调用一个函数的时候,就是默认绑定
* 1、非严格模式下,默认绑定到window上
*
* 隐式绑定:
* 当使用 对象.方法() 这种方式调用,称之为隐式绑定
* this绑定到前面的那个对象上
*
* new 绑定:
* 使用new来调用构造函数的方式
* this是绑定在新创建的那个对象上
*
* 显示绑定:
* call,apply:
* 都是一锤子买卖,仅仅这一次调用的时候使用了显示绑定,对原函数没有如何的影响
*
* call和apply的区别:就是参数的传递方式
* call:一个一个的传递
* apply:把要传递的参数封装到一个数组中去传递
*
* bind:固定绑定 es6新增
* 调用函数对象的bind方法,返回一个固定this绑定的新的函数
* 对原来的函数没有影响
*
*
*
* 优先级:bind > call,apply > new > 隐式
*/


//1、默认绑定
function foo() {
console.log(this);//this指 obj 谁调用指向谁
}


//2、隐式绑定
var name = "Mark";
var obj = {
name : "Jack",
foo : foo,
foo1 : function () {
console.log(this.name);//this指 window
}
}
obj.foo();//{name: "Jack", foo: function, foo1: function}
var foo1 = obj.foo1;
foo1();//Mark


//3、new 绑定
function foo2() {
this.name = "Lucy";
console.log(this);//this 指向foo2 foo2 {name: "Lucy"}
}
var obj2 = new foo2();
console.log(obj2);//this 指向foo2 foo2 {name: "Lucy"}

var foo3 = obj.foo1;
foo3();//Mark

var obj3 = new foo;//foo {}
console.log(obj3);//foo {}


//4、显示绑定

//call apply
function foo4(a,b) {
console.log(this.name,a,b);
}
foo4.call({name:"Jack"},10,20)//Jack 10 20
foo4.apply({name:"Mark"},[20,10])//Mark 20 10

//apply求max
var arr = [23,65,2,45,3,57,4567];
console.log(Math.max.apply(Math,arr)); //max = 4567

//定义Math 求和的方法
Math.sun = function () {
return Array.prototype.reduce.call(arguments,function (sun,ele) {
return sun + ele;
},0)
}
console.log(Math.sun.apply(Math,arr));//sum = 4762

//bind
var obj4 = {
name : "Jack"
}
function foo5() {
console.log(this.name);
}
var f = foo5.bind(obj4);//这种绑定方式优先级最高
f();//Jack

var obj5 = {
name : "Mark",
foo6 : f
}
obj5.foo6()//Jack
</script>

2、绑定的丢失问题
<script>
/*
* 回调函数的this绑定丢失问题:this会绑定到window
*
* 定时器
*
*
* 显示绑定丢失问题
* 显示绑定传入null、undefined时,this就成了默认绑定
*
*/

//定时器绑定丢失
var name = "Jack";
var obj = {
name : "Mark",
show : function () {
setInterval(function () {
console.log(this.name);// 此时this指向window
},1000)
}
}
obj.show();//Jack++

//解决方法一
var obj1 = {
name : "Mark",
show : function () {
var self = this;
setInterval(function () {
console.log(self.name);// 此时this指向obj1
},1000)
}
}
obj1.show();//Mark++

//解决方法二
var obj2 = {
name : "Joe",
show : function () {
setInterval(function () {
console.log(this.name);// 此时this指向obj2
}.bind(this),1000)
}
}
obj2.show();//Mark++

//显示绑定丢失
function foo() {
console.log(this.name);
}
var f = foo.bind(undefined);// 此时this指向window
f();//Jack
</script>

五、this使用总结

1、this使用总结

<script>
/*
* 默认绑定:
* 当直接调用一个函数的时候,就是默认绑定
* 1、非严格模式下,默认绑定到window上
*
* 隐式绑定:
* 当使用 对象.方法() 这种方式调用,称之为隐式绑定
* this绑定到前面的那个对象上
*
* new 绑定:
* 使用new来调用构造函数的方式
* this是绑定在新创建的那个对象上
*
* 显示绑定:
* call,apply:
* 都是一锤子买卖,仅仅这一次调用的时候使用了显示绑定,对原函数没有如何的影响
*
* call和apply的区别:就是参数的传递方式
* call:一个一个的传递
* apply:把要传递的参数封装到一个数组中去传递
*
* bind:固定绑定 es6新增
* 调用函数对象的bind方法,返回一个固定this绑定的新的函数
* 对原来的函数没有影响
*
*
*
* 优先级:bind > call,apply > new > 隐式
*/


# //1、默认绑定
function foo() {
console.log(this);//this指 obj 谁调用指向谁
}


#//2、隐式绑定
var name = "Mark";
var obj = {
name : "Jack",
foo : foo,
foo1 : function () {
console.log(this.name);//this指 window
}
}
obj.foo();//{name: "Jack", foo: function, foo1: function}
var foo1 = obj.foo1;
foo1();//Mark


# //3、new 绑定
function foo2() {
this.name = "Lucy";
console.log(this);//this 指向foo2 foo2 {name: "Lucy"}
}
var obj2 = new foo2();
console.log(obj2);//this 指向foo2 foo2 {name: "Lucy"}

var foo3 = obj.foo1;
foo3();//Mark

var obj3 = new foo;//foo {}
console.log(obj3);//foo {}


# //4、显示绑定

//call apply
function foo4(a,b) {
console.log(this.name,a,b);
}
foo4.call({name:"Jack"},10,20)//Jack 10 20
foo4.apply({name:"Mark"},[20,10])//Mark 20 10

//apply求max
var arr = [23,65,2,45,3,57,4567];
console.log(Math.max.apply(Math,arr)); //max = 4567

//定义Math 求和的方法
Math.sun = function () {
return Array.prototype.reduce.call(arguments,function (sun,ele) {
return sun + ele;
},0)
}
console.log(Math.sun.apply(Math,arr));//sum = 4762

//bind
var obj4 = {
name : "Jack"
}
function foo5() {
console.log(this.name);
}
var f = foo5.bind(obj4);//这种绑定方式优先级最高
f();//Jack

var obj5 = {
name : "Mark",
foo6 : f
}
obj5.foo6()//Jack
</script>

2、绑定的丢失问题

<script>
/*
* 回调函数的this绑定丢失问题:this会绑定到window
*
* 定时器
*
*
* 显示绑定丢失问题
* 显示绑定传入null、undefined时,this就成了默认绑定
*
*/

//定时器绑定丢失
var name = "Jack";
var obj = {
name : "Mark",
show : function () {
setInterval(function () {
console.log(this.name);// 此时this指向window
},1000)
}
}
obj.show();//Jack++

//解决方法一
var obj1 = {
name : "Mark",
show : function () {
var self = this;
setInterval(function () {
console.log(self.name);// 此时this指向obj1
},1000)
}
}
obj1.show();//Mark++

//解决方法二
var obj2 = {
name : "Joe",
show : function () {
setInterval(function () {
console.log(this.name);// 此时this指向obj2
}.bind(this),1000)
}
}
obj2.show();//Mark++

//显示绑定丢失
function foo() {
console.log(this.name);
}
var f = foo.bind(undefined);// 此时this指向window
f();//Jack
</script>

5.3 for循环典型问题

看下面的代码

<body>
<input type="button" value="按钮1" >
<input type="button" value="按钮2" >
<input type="button" value="按钮3" >
<script type="text/javascript">
var btns = document.getElementsByTagName("input");
for (var i = 0; i < 3; i++) {
btns[i].onclick = function () {
alert("我是第" + (i + 1) + "个按钮");
};
}
</script>
</body>

发现在点击三个按钮的时候都是弹出 我是第4个按钮。 为什么呢?闭包导致的! 每循环一次都会有一个匿名函数设置点击事件,闭包总是保持的变量的最后一个值,所以点击的时候,总是读的是 i 的组后一个值4.

解决方案1:给每个按钮添加一个属性,来保存 每次 i 的临时值

<body>
<input type="button" value="按钮1" >
<input type="button" value="按钮2" >
<input type="button" value="按钮3" >
<script type="text/javascript">
var btns = document.getElementsByTagName("input");
for (var i = 0; i < 3; i++) {
//把i的值绑定到按钮的一个属性上,那么以后i的值就和index的值没有关系了。
btns[i].index = i;
btns[i].onclick = function () {
alert("我是第" + (this.index + 1) + "个按钮");
};
}
</script>
</body>

解决方案2:使用匿名函数的自执行

<body>
<input type="button" value="按钮1" >
<input type="button" value="按钮2" >
<input type="button" value="按钮3" >
<script type="text/javascript">
var btns = document.getElementsByTagName("input");
for (var i = 0; i < 3; i++) {
//因为匿名函数已经执行了,所以会把 i 的值传入到num中,注意是i的值,所以num
(function (num) {
btns[i].onclick = function () {
alert("我是第" + (num + 1) + "个按钮");
}
})(i);
}
</script>
</body>

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 105,111评论 12 126
  • 一、你不知道的JavaScript 1、作用域 作用域 LHS RHS RHS查询与简单地查找某个变量的值别无二...
    顶儿响叮当阅读 59评论 0 0
  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    枫叶appiosg阅读 2,447评论 0 12
  • 如何控制alert中的换行?\n alert(“p\np”); 请编写一个JavaScript函数 parseQu...
    heyunqiang99阅读 528评论 0 5
  • 1、使用typeof bar ===“object”来确定bar是否是一个对象时有什么潜在的缺陷?这个陷阱如何避免...
    深海鲫鱼堡阅读 265评论 1 1