清风陪你学习-JavaScript高级(五)this、面向对象、继承、变量提升、

这几天清风,整理了一下对JS高级的一些理解和知识点,分享出来同时也是对自己的检测,希望各位大佬,多多提出意见

多多评论,多多关注。JS高级的东西,比较杂,也比较难理解,尤其像闭包,原型链,this的指向这类,总之希望多多讨论研究吧,每个人的见解和想法都不同。

(接之前的几篇,总结的JS高级知识点)


5.8.2 使用场合

1)全局环境

全局环境使用this,它指的就是顶层对象window


this===window// true

function f() {
 
 console.log(this===window);
}
f() // true

2)构造函数

构造函数中的this,指的是实例对象。


function F(){
 
   this.hello=function(){
 
       console.log('Hello'+this.name);
 
  }
}
var f1=newF();
f1.name='张三';
f1.hello();


var f2=newF();
f2.name='刘能';
f2.hello();

(3)对象的方法

方法在哪个对象下,this就指向哪个对象。


var o1= {
 
   s1:'123',
 
   f1:function (){
 
       console.log(this.s1)
 
  }
}

var o2= {
 
   s1:'456',
 
   f1:o1.f1
}

o2.f1();

5.8.3 使用this时的注意事项

(1) 避免包含多层this


var o= {
 
 f1: function () {
 
   console.log(this);
 
   var f2=function () {
 
     console.log(this);
 
  }
    f2();
 
}
}

o.f1()
// Object  
// Window  

如果要在内层函数中使用外层的this指向,一般的做法是:


var o= {
 
 f1: function () {
 
   console.log(this);
 
   var that=this;
 
   var f2=function () {
 
     console.log(that);
 
  }
    f2();
 
}
}

o.f1()
// Object  
// Object  

(2)不在循环数组中使用this


var ar= ['a','b','c'];
ar.forEach(function(v,k,ar){
 
   console.log(this[k])
})

this的动态切换,固然为JavaScript创造了巨大的灵活性,但也使得编程变得困难和模糊。

有时,需要把this固定下来,避免出现意想不到的情况;JavaScript提供了callapplybind这三个方法,来切换/固定this的指向。

5.9 call()方法、apply()方法、bind()方法

call()方法


var lisi= {names:'lisi'};
var zs= {names:'zhangsan'};
function f(age){
 
   console.log(this.names);
 
   console.log(age);
 
   
}
f(23);//undefined
f.call(zs,32);//zhangsan

call方法使用的语法规则

函数名称.call(obj,arg1,arg2...argN);

参数说明:

obj:函数内this要指向的对象,

arg1,arg2...argN:参数列表,参数与参数之间使用一个逗号隔开

apply()方法

函数名称.apply(obj,[arg1,arg2...,argN])

参数说明:

obj :this要指向的对象

[arg1,arg2...argN]: 参数列表,但是要求格式为数组


var lisi= {name:'lisi'};
var zs= {name:'zhangsan'};
function f(age,sex){
    console.log(this.name+age+sex);
}
f.apply(zs,[23,'nan']);

bind()方法

bind方法用于将函数体内的this绑定到具体的某个对象上


this.x=9;
var module= {
 
 x: 81,
 
 getX: function() { returnthis.x; }
};

module.getX(); // 返回 81

var retrieveX=module.getX;
retrieveX(); // 返回 9, 在这种情况下,"this"指向全局作用域

// 创建一个新函数,将"this"绑定到module对象
// 新手可能会被全局的x变量和module里的属性x所迷惑
var boundGetX=retrieveX.bind(module);
boundGetX(); // 返回 81

6再谈面向对象

学习目标:

·       了解ES6中新的对象语法

·       正确使用继承

6.1 对象

6.1.1 谁说JS没有类

JS中,想要获取一个对象,有多种方式:

var o1 = {}var o2 = new Object()

自定义构造函数方式


function Point(x, y) {
 
 this.x=x;
 
 this.y=y;
 
 this.toString=function () {
 
  returnthis.x+', '+this.y;
 
};
}

var p=newPoint(1, 2);
console.log(p.toString());

但是上面这种使用构造函数获取对象的写法跟传统的面向对象语言(比如 C++ Java)差异很大,很容易让新学习这门语言的程序员感到困惑。

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。

通过class关键字,可以定义类。

基本上,ES6 class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

上面的代码用 ES6 class改写,就是下面这样。


//定义类
class Point {
 
 constructor(x, y) {
 
   this.x=x;
 
   this.y=y;
 
}

  toString() {
 
   return  this.x+', '+this.y ;
 
}
}
var p=newPoint(1, 2);
console.log(p.toString());

上面代码定义了一个,可以看到里面有一个constructor方法,这就是构造方法(后面还会讲到),而this关键字则代表实例对象。也就是说,ES5 的构造函数Point,对应 ES6 的类Point

Point类除了构造方法,还定义了一个toString方法。注意,定义的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。

ES6 的类,完全可以看作构造函数的另一种写法。

使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。

类同样也有prototype属性,而属性的值依然是实例对象的原型对象;


class Point {
 
 constructor(x, y) {
 
   this.x=x;
 
   this.y=y;
 
}

  toString() {
 
   return  this.x+', '+this.y ;
 
}
}
Point.prototype.toValue=function(){
 
   console.log('123');
}
var p=newPoint(1, 2);
p.toValue();
console.log(p.toString());

6.1.2constructor 方法

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。


class Point {
}

// 等同于
class Point {
 
 constructor() {}
}

类必须使用new进行实例化调用,否则会报错。

而类一旦实例化,constructor()方法就会被执行,就像人一出生就会哭一样;

constructor方法默认返回实例对象(即this);

但是返回值完全可以指定返回另外一个对象;


var o1= {
 
   f1:function(){
 
       console.log('f1');
 
  }
}
class Point{
 
   constructor (){
 
       returno1;
 
  }
    f2(){
 
       console.log('f2');
 
  }
}

var p=newPoint();
p.f1(); // f1
p.f2(); //Uncaught TypeError: p.f2 is not afunction

constructor方法默认返回值尽量不要修改,一旦修改,我们获取的对象将脱离其原型;

6.1.3 变量提升

我们知道,在JS中,不管是全局变量还是局部变量都存在变量提升的特性,函数也有提升的特性,也可以先调用后声明,构造函数也一样;如下面的代码,完全没问题:


varp=newPoint();
p.f2();

functionPoint(){
 
   this.f2=function(){
 
       console.log('f2');
 
  }
}

但是,需要注意:类不存在变量提升(hoist),这一点与 ES5 完全不同。


newMan();
class Man{}

// Man is notdefined

注意,class只是在原有面向对象的基础上新加的关键字而已,本质上依然没有改变JS依赖原型的面向对象方式;

6.2 再谈继承

6.2.1 原型链继承的问题


//声明构造函数Run
function Run(){
 
 this.p=function(){
 
     console.log(this.name+'');
 
}}
//声明构造函数Man
function Man(name){
 
   this.name=name;
}
//设置构造函数Man的原型为Run,实现继承
Man.prototype=newRun();

varm=newMan('张三');

m.p();

// 由构造函数获取原型
console.log(Man.prototype); // 函数对象 Run
//标准方法获取对象的原型
console.log(Object.getPrototypeOf(m)); // 函数对象 Run
//获取对象的构造函数
console.log(m.constructor); // Run 函数

运行上面的代码,我们发现对象m本来是通过构造函数Man得到的,可是,m对象丢失了构造函数,并且原型链继承的方式,打破了原型链的完整性,不建议使用;

这个问题,在3.5章节也提到过,想解决也很简单,只要在父级中手动指定子级正确的构造函数即可:

修改上面的代码:

6.2.2 冒充方式的继承

前面我们在学习JS面向中的面向对象编程时,谈到了继承;

所谓的继承,其实就是在子类(子对象)能够使用父类(父对象)中的属性及方法;


function f1(){
 
   this.color='黑色';
 
   this.h=function(){
 
       console.log(this.color+this.sex);
 
  }
}

function F2(){
 
   this.sex='';
 
   this.fn=f1;
}

var b=newF2();
b.fn();
b.h();

运行以上代码可知,由构造函数获取的对象b可以调用函数f1中的属性及方法;

有运行结果可知,f1函数中的this实际指向了对象b,对象b实际上已经继承了f1

这种方式称为对象冒充方式继承,ES3之前的代码中经常会被使用,但是现在基本不使用了;

为什么不使用了呢?

还是要回到上面的代码,本质上讲,我们只是改变了函数f1this的指向,

f1中的this指向谁,谁就会继承f1;

callapply就是专门用来改变函数中this指向的;

callapply 实现继承


function Run(){
 
   this.p=function(){
 
       console.log('ppp');
 
  }
}
function Man(){
//Run函数内部的this指向Man的实例化对象;
 
   Run.call(this);
}
var m=newMan();
m.p();

//获取对象的构造函数
console.log(m.constructor); // Man 函数
// 由构造函数获取原型
console.log(Man.prototype); // 函数对象 Man
//标准方法获取对象的原型
console.log(Object.getPrototypeOf(m)); // 函数对象 Man

callapply 实现的继承依然是使用对象冒充方式实现,此方式即实现了继承的功能,同时也不再出现原型链继承中出现的问题;

6.2.4Object.create() 创建实例对象及原型继承

构造函数作为模板,可以生成实例对象。但是,有时拿不到构造函数,只能拿到一个现有的对象。我们希望以这个现有的对象作为模板,生成新的实例对象,这时就可以使用Object.create()方法。


var person1= {
 
 name: '张三',
 
 age: 38,
 
 greeting: function() {
 
   console.log('Hi! I\'m '+this.name+'.');
 
}
};

var person2=Object.create(person1);

person2.name// 张三
person2.greeting() // Hi! I'm 张三.

console.log(Object.getPrototypeOf(person2) ==person1); //true

上面代码中,对象person1person2的原型,后者继承了前者所有的属性和方法;

Object.create的本质就是创建对象;

var obj1 =Object.create({});

var obj2 =Object.create(Object.prototype);

var obj3 = newObject();

 

//下面三种方式生成的新对象是等价的。

 

如果想要生成一个不继承任何属性(比如没有toStringvalueOf方法)的对象,可以将Object.create的参数设为null


var obj=Object.create(null);

obj.valueOf()
// TypeError:Object [object Object] has no method 'valueOf'

使用Object.create方法的时候,必须提供对象原型,即参数不能为空,或者不是对象,否则会报错。

问题:如果想要实现继承,应该使用哪种继承方式?

具体对象实例继承使用Object.create

构造函数继承使用对象冒充callapply

6.2.5 Class 的继承


class Run{
 
   p(){
 
       console.log('ppp');
 
  }
}
class ManextendsRun{
}
var m=newMan();
m.p();

Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

ES5 的继承,实质是先创造子类的实例对象,然后再将父类的方法添加到上面(Parent.call(this))。

ES6 的继承机制完全不同,是先创造父类的实例对象,然后再用子类的构造函数修改。


class Run{
 
   p(){
 
       console.log('ppp');
 
  }
}
class ManextendsRun{
 
   // 显式调用构造方法
 
   constructor(){}
}
var m=newMan();
m.p();
// UncaughtReferenceError: Must call super constructor in derived class before accessing'this' or returning from derived

上面代码中,Man继承了父类Run,但是子类并没有先实例化Run,导致新建实例时报错。


class Run{
 
   p(){
 
       console.log('ppp');
 
  }
}
class ManextendsRun{
 
   constructor(){
 
       // 调用父类实例
 
       super();
 
  }
}
var m=newMan();
m.p();

6.2.6 继承总结

原型链继承:

构造函数.prototype --- 会影响所有的实例对象

实例对象.__proto__ -------- 只会影响当前对象,需要重新修改constructor属性的指向,ES6以后只在浏览器下部署

Object.setPrototypeOf(子对象,原型) ------ 只会影响当前对象,需要重新修改constructor属性的指向ES6的新语法)

空实例对象 = Object.create(原型) ;创建对象并明确指定原型

call apply 本质上是改变函数内部this 的指向,看起来像继承,但实际上不是继承

class extends 实现继承,这是标准继承方式,但是只能在ES6中使用

"我是Spirit_Breeze,中文<晟世清风>,在这纷纷乱世之中,祈望能有一股清流之风." 本人从事销售,不甘心于口舌之利,突然对代码和框架充满兴趣,遂之研究研究,欢迎研究讨论,转载请备注地址和作者,谢谢


猜你喜欢

转载自blog.csdn.net/spirit_breeze/article/details/80977324
今日推荐