重点知识点(2)

目录

一、js下new命令的用法

1.对象

(1)对象是单个实物的抽象。

(2)对象是一个容器,封装了属性(property)和方法(method)。

2.构造函数

3.new命令

(1)基本用法

 (2)new命令重要性

4.Object.create() 创建实例对象

二、this关键字

1.含义

2.实质

3.使用场合

4.使用注意点

(1)避免多层 this

(2)避免数组处理方法中的 this

(3)避免回调函数中的 this

5.绑定this的方法

(1)Function.prototype.call()

(2)Function.prototype.apply()

(3)Function.prototype.bind()

三、JavaScript闭包和this

1.闭包

(1)读取函数内部的局部变量

(2)闭包概念

2.this

(1)四类调用方法

①作为对象方法的调用

②纯粹的函数调用

③作为[构造函数]调用

④使用apply、call、bind调用

(2) 箭头函数中的this

(3)举例

四、定时器

1.setTimeout()

2.setInterval()

3.clearTimeout(),clearInterval()

五、宏任务微任务 同步异步

1.执行顺序

2.案例

六、prototype

1.原型对象概述

(1)构造函数的缺点

(2)prototype属性作用

(3)原型链


一、js下new命令的用法

1.对象

面向对象编程将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。

而类是一类事物,比如学生就是一个类,如果学生这个类通过new这个关键词生成一个实例对象,这个实例对象就是一个具体的人。通常在类中都含有一个构造函数,构造函数可以传递相应的属性,类在实例化的瞬间构造函数会自动执行把属性赋值给实例对象。假设在学生类中有一个学生,在实例化时构造函数可以给他赋值姓名为张三年龄为18,即就是将抽象的一类事物具体到某一个对象上。

在js中不存在类的概念,可以将构造函数视为一个类。

(1)对象是单个实物的抽象。

一本书、一辆汽车、一个人都可以是对象,一个数据库、一张网页、一个远程服务器连接也可以是对象。

(2)对象是一个容器,封装了属性(property)和方法(method)。

属性是对象的状态,方法是对象的行为(完成某种任务)。比如,我们可以把动物抽象为animal对象,使用“属性”记录具体是哪一种动物,使用“方法”表示动物的某种行为(奔跑、捕猎、休息等等)。

2.构造函数

面向对象编程的第一步,就是要生成对象。对象是单个实物的抽象。通常需要一个模板,表示某一类实物的共同特征,然后对象根据这个模板生成。

JavaScript 语言使用构造函数(constructor)作为对象的模板。所谓”构造函数”,就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。

var Vehicle = function () {
  this.price = 1000;
};

上面代码中,Vehicle就是构造函数。为了与普通函数区别,构造函数名字的第一个字母通常大写。

构造函数的特点有两个:

  • 函数体内部使用了this关键字,代表了所要生成的对象实例。

  • 生成对象的时候,必须使用new命令。

3.new命令

(1)基本用法

new命令的作用,就是执行构造函数,返回一个实例对象。

var Vehicle = function () {
  this.price = 1000;
};

var v = new Vehicle();
v.price // 1000

上面代码中,new命令让构造函数Vehicle生成了一个实例对象保存在变量v中,这个实例对象从Vehicle中得到了price属性。new命令执行时,构造函数内部this代表新生成的实例对象,this.price代表实例对象具有price属性,值为1000。

 (2)new命令重要性

如果忘记使用new命令,构造函数则变成普通函数,也不会生成实例对象,this此时也代表全局对象。

var Student = function(name) {

   this.name = name

}

var Stu = Student('zhangsan')

console.info(Stu)

console.info(name)

控制台会返回:

undefined

zhangsna

原因:

由于忘记使用new命令,构造函数Student变成了一个普通函数,普通函数的调用是在全局调用,此时的this也指向全局,而全局中的顶层变量为window,window.name为'zhangsan';而出现undefined是由于构造函数被当成普通函数执行,但却没有普通函数应该具有的return返回值,所以zhangsan赋值过来时undefined。

4.Object.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 张三.

上面代码中,对象person1person2的模板,后者继承了前者的属性和方法。

二、this关键字

1.含义

this就是属性或方法“当前”所在的对象。

var person = {
  name: '张三',
  describe: function () {
    return '姓名:'+ this.name;
  }
};

person.describe()
// "姓名:张三"

上面代码中,this.namedescribe方法中调用,describe方法所在的当前对象是person,因此this指向personthis.name就是person.name。 相当于person调用了describe,describe调用了this.name,this.name的大环境在person这个对象里,所以指向了person中的属性name。

2.实质

由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。

var f = function () {};
var obj = { f: f };

// 单独执行   this指向全局
f()

// obj 环境执行 this指向obj
obj.f()

实例:

var f = function(){

   console.log(x);

};

f()

此时结果为:

x is not defined

修改为:

var f = function(){

   console.log(x);

};

var x = ‘aaa’

f()

此时结果为:

aaa

原因:f()运行在全局环境下,x指向全局里的window,而window中x未定义,所以第一次为undefined,第二次全局定义了x,即可以输出aaa。

3.使用场合

(1)全局环境

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

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

上面代码说明,不管是不是在函数内部,只要是在全局环境下运行,this就是指顶层对象window

(2)构造函数

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

var Obj = function (p) {
  this.p = p;
};

上面代码定义了一个构造函数Obj。由于this指向实例对象,所以在构造函数内部定义this.p,就相当于定义实例对象有一个p属性。this指的是o。

var o = new Obj('Hello World!');
o.p // "Hello World!"

(3)对象的方法

如果对象的方法里面包含thisthis的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this的指向。

var obj ={
  foo: function () {
    console.log(this);
  }
};
​
obj.foo() // obj

上面代码中,obj.foo方法执行时,它内部的this指向obj

但是,下面这几种用法,都会改变this的指向。

// 情况一
(obj.foo = obj.foo)() // window
// 情况二
(false || obj.foo)() // window
// 情况三
(1, obj.foo)() // window

上面代码中,obj.foo就是一个值。这个值真正调用的时候,运行环境已经不是obj了,而是全局环境,所以this不再指向obj

可以这样理解,JavaScript 引擎内部,objobj.foo储存在两个内存地址,称为地址一和地址二。obj.foo()这样调用时,是从地址一调用地址二,因此地址二的运行环境是地址一,this指向obj。但是,上面三种情况,都是直接取出地址二进行调用,这样的话,运行环境就是全局环境,因此this指向全局环境。上面三种情况等同于下面的代码。

// 情况一
(obj.foo = function () {
  console.log(this);
})()
// 等同于
(function () {
  console.log(this);
})()
​
// 情况二
(false || function () {
  console.log(this);
})()
​
// 情况三
(1, function () {
  console.log(this);
})()

如果this所在的方法不在对象的第一层,这时this只是指向当前一层的对象,而不会继承更上面的层。

4.使用注意点

(1)避免多层 this

由于this的指向是不确定的,所以切勿在函数中包含多层的this

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

上面代码包含两层this,结果运行后,第一层指向对象o,第二层指向全局对象,因为实际执行的是下面的代码。

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

一个解决方法是在第二层改用一个指向外层this的变量。

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

上面代码定义了变量that,固定指向外层的this,然后在内层使用that,就不会发生this指向的改变。

事实上,使用一个变量固定this的值,然后内层函数调用这个变量,是非常常见的做法,请务必掌握。

JavaScript 提供了严格模式,也可以硬性避免这种问题。严格模式下,如果函数内部的this指向顶层对象,就会报错。

var counter = {
  count: 0
};
counter.inc = function () {
  'use strict';
  this.count++
};
var f = counter.inc;
f()
// TypeError: Cannot read property 'count' of undefined

上面代码中,inc方法通过'use strict'声明采用严格模式,这时内部的this一旦指向顶层对象,就会报错。

(2)避免数组处理方法中的 this

数组的mapforeach方法,允许提供一个函数作为参数。这个函数内部不应该使用this

var o = {
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    this.p.forEach(function (item) {
      console.log(this.v + ' ' + item);
    });
  }
}
​
o.f()
// undefined a1
// undefined a2

上面代码中,foreach方法的回调函数中的this,其实是指向window对象,因此取不到o.v的值。原因跟上一段的多层this是一样的,就是内层的this不指向外部,而指向顶层对象。

解决这个问题的一种方法,就是前面提到的,使用中间变量固定this

var o = {
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    var that = this;
    this.p.forEach(function (item) {
      console.log(that.v+' '+item);
    });
  }
}
​
o.f()
// hello a1
// hello a2

另一种方法是将this当作foreach方法的第二个参数,固定它的运行环境。

var o = {
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    this.p.forEach(function (item) {
      console.log(this.v + ' ' + item);
    }, this);
  }
}
​
o.f()
// hello a1
// hello a2

(3)避免回调函数中的 this

回调函数中的this往往会改变指向,最好避免使用。

var o = new Object();
o.f = function () {
  console.log(this === o);
}
​
// jQuery 的写法
$('#button').on('click', o.f);

上面代码中,点击按钮以后,控制台会显示false。原因是此时this不再指向o对象,而是指向按钮的 DOM 对象,因为f方法是在按钮对象的环境中被调用的。这种细微的差别,很容易在编程中忽视,导致难以察觉的错误。

为了解决这个问题,可以采用下面的一些方法对this进行绑定,也就是使得this固定指向某个对象,减少不确定性。

5.绑定this的方法

(1)Function.prototype.call()

函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。

var obj = {};
​
var f = function () {
  return this;
};
​
f() === window // true
f.call(obj) === obj // true

上面代码中,全局环境运行函数f时,this指向全局环境(浏览器为window对象);call方法可以改变this的指向,指定this指向对象obj,然后在对象obj的作用域中运行函数f

(2)Function.prototype.apply()

apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下。

func.apply(thisValue, [arg1, arg2, ...])

apply方法的第一个参数也是this所要指向的那个对象,如果设为nullundefined,则等同于指定全局对象。第二个参数则是一个数组,该数组的所有成员依次作为参数,传入原函数。原函数的参数,在call方法中必须一个个添加,但是在apply方法中,必须以数组形式添加。

function f(x, y){
  console.log(x + y);
}
​
f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2

上面代码中,f函数本来接受两个参数,使用apply方法以后,就变成可以接受一个数组作为参数。

(3)Function.prototype.bind()

bind()方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。

var d = new Date();
d.getTime() // 1481869925657
​
var print = d.getTime;
print() // Uncaught TypeError: this is not a Date object.

上面代码中,我们将d.getTime()方法赋给变量print,然后调用print()就报错了。这是因为getTime()方法内部的this,绑定Date对象的实例,赋给变量print以后,内部的this已经不指向Date对象的实例了。

bind()方法可以解决这个问题。

var print = d.getTime.bind(d);
print() // 1481869925657

上面代码中,bind()方法将getTime()方法内部的this绑定到d对象,这时就可以安全地将这个方法赋值给其他变量了。

三、JavaScript闭包和this

1.闭包

在JS中,通俗来讲,闭包就是能够读取外层函数内部变量的函数

(1)读取函数内部的局部变量

①在函数内部再定义一个函数

function f1() {
    let code = 200;
​
    function f2() {
        console.log(code);
    }
}

函数f1内部的函数f2可以读取f1中所有的局部变量。因此,若想在外部访问函数f1中的局部变量code,可通过函数f2间接访问。

②为外部程序提供访问函数局部变量的入口

function f1() {
    let code = 200;
​
    function f2() {
        console.log(code);
    }
​
    return f2;
}
​
f1()();  // 200

(2)闭包概念

(1)中的函数f2,就是闭包,其作用就是将函数内部与函数外部进行连接。

  • 闭包访问的变量,是每次运行上层函数时重新创建的,是相互独立的。

  • 不同的闭包,可以共享上层函数中的局部变量

2.this

(1)四类调用方法

①作为对象方法的调用
function f() {
    console.log( this.code );
}
let obj = {
    code: 200,
    f: f
};
obj.f(); // 200
②纯粹的函数调用
function f() {
    console.log( this.code );
}
// 此处,通过var(函数作用域)声明的变量code会绑定到window上;如果使用let(块作用域)声明变量code,则不会绑定到window上,因此下面的2次函数调用f(),会输出undefined
// let code = 200;
var code = 200;
f(); // 200
code = 404;
f(); // 404
③作为[构造函数]调用
code = 404
function A() {
    this.code = 200
    this.callA = function() {
        console.log(this.code)
    }
}
​
var a = new A()
a.callA() // 200, callA在new A返回的对象里
④使用apply、call、bind调用

apply

var code = 404;
let obj = {
    code: 200,
    f: function() {
        console.log(this.code);
    }
}
​
obj.f(); // 200, 实际上是作为对象的方法调用
obj.f.apply(); // 404,参数为空时,默认使用全局对象global,在此处为对象window
obj.f.apply(obj); //200,this指向参数中设置的对象

call

function f() {
    console.log( this.code );
}
var obj = {
    code: 200
};
f.call( obj ); // 200

bind

// bind返回一个新的函数
function f(b) {
    console.log(this.a, b);
    return this.a + b;
}
var obj = {
    a: 2
};
var newF = f.bind(obj);
var result = newF(3); // 2 3
console.log(result); // 5

(2) 箭头函数中的this

箭头函数中的this是定义函数时绑定的,而不是在执行函数时绑定。若箭头函数在简单对象中,由于简单对象没有执行上下文,所以this指向上层的执行上下文;若箭头函数在函数、类等有执行上下文的环境中,则this指向当前函数、类。

(3)举例

function f() {
    //宏任务
    setTimeout(() => {
        console.log(">>>" + this); // >>>[object object],语句5
        this.code = 401;
    }, 0)       定时器为0表示不会延迟,详见(3.定时器)
    //同步
    console.log( this.code );
}

let obj = {
    ">>>" + this
    code: 200,
    foo: f
};

var code = 500;

同步和异步 关键词说的很好(详见4.宏任务微任务 同步异步)
宏任务 微任务
1.宏任务 微任务 同步异步
2.箭头函数this指向问题
3.字符串+ this  改写为[object object]的形式
obj.foo(); // 200,语句1  


console.log("--" + obj.code); // --200,语句3


//宏任务
setTimeout(()=>{console.log("---" + obj.code);}, 0); // ---401,语句4

obj.foo(); (语句1):调用 obj 对象的 foo 方法。
输出:200
解释:在 foo 方法内部的 console.log(this.code) 打印出 obj 对象的 code 属性,其值为 200。

console.log("--" + obj.code); (语句3):打印 obj 对象的 code 属性。
输出:--200
解释:在全局作用域中,code 被赋值为 500,但这里的 obj.code 指向的是 obj 对象的 code 属性,其值仍然是 200。

setTimeout(() => { console.log("---" + obj.code); }, 0); (语句4):设置一个零延迟的定时器,其中的箭头函数在调用。
输出:---401
解释:在调用 obj.foo() 的过程中,foo 方法中的 setTimeout 在当前宏任务结束后执行。由于是箭头函数,this 的值保持与父作用域一致(也就是 obj 对象)。所以在箭头函数内部,this.code 被设置为 401。

setTimeout(() => { console.log(">>>" + this); this.code = 401; }, 0) (语句5):设置一个零延迟的定时器,其中的箭头函数在调用。
输出:>>>[object Object]
解释:在调用 obj.foo() 的过程中,foo 方法中的 setTimeout 在当前宏任务结束后执行。箭头函数的 this 始终指向它被创建时的外部作用域,所以 this 指向了 obj 对象,而在控制台中打印 this 时会将其转换为字符串。所以输出为 >>>[object Object]。

四、定时器

1.setTimeout()

setTimeout`函数用来指定某个函数或某段代码,在多少毫秒之后执行。它返回一个整数,表示定时器的编号,以后可以用来取消这个定时器。

var timerId = setTimeout(func|code, delay);

上面代码中,setTimeout函数接受两个参数,第一个参数func|code是将要推迟执行的函数名或者一段代码,第二个参数delay是推迟执行的毫秒数。

console.log(1);
setTimeout('console.log(2)',1000);
console.log(3);
// 1
// 3
// 2

上面代码会先输出1和3,然后等待1000毫秒再输出2。注意,console.log(2)必须以字符串的形式,作为setTimeout的参数。

如果推迟执行的是函数,就直接将函数名,作为setTimeout的参数。

2.setInterval()

setInterval函数的用法与setTimeout完全一致,区别仅仅在于setInterval指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行。

var i = 1
var timer = setInterval(function() {
  console.log(2);
}, 1000)

上面代码中,每隔1000毫秒就输出一个2,会无限运行下去,直到关闭当前窗口。

3.clearTimeout(),clearInterval()

setTimeoutsetInterval函数,都返回一个整数值,表示计数器编号。将该整数传入clearTimeoutclearInterval函数,就可以取消对应的定时器。

var id1 = setTimeout(f, 1000);
var id2 = setInterval(f, 1000);
​
clearTimeout(id1);
clearInterval(id2);

上面代码中,回调函数f不会再执行了,因为两个定时器都被取消了。

五、宏任务微任务 同步异步

1.执行顺序

执行顺序:先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列中,遇到异步微任务则将异步微任务放入微任务队列中,当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,微任务执行完毕后再将异步宏任务从队列中调入主线程执行,一直循环直至所有任务执行完毕。

2.案例

//宏任务 放进队列      
setTimeout(function(){            setTimeout是宏任务
    console.log(1);
});
//微任务
new Promise(function(resolve){      promise是微任务    
    console.log(2);
    resolve();
}).then(function(){           

(promise是一个有回调函数的值,回调函数是resolve,当promise异步函数执行后会调用回调函数,结果会接过来到resolve(),console.log(3)。)
    
    console.log(3);
}).then(function(){
    console.log(4)
});     
//同步代码
console.log(5);
// 2 5 3 4 1

上述代码中,遇到setTimout异步宏任务,先放入宏任务队列中

然后遇到new Promise,new Promise在实例化的过程中所执行的代码都是同步进行的,所以输出2
Promise.then,异步微任务,将其放入微任务队列中
遇到同步任务console.log(5);输出5;主线程中同步任务执行完
从微任务队列中取出任务到主线程中,输出3、 4,微任务队列为空
从宏任务队列中取出任务到主线程中,输出1,宏任
务队列为空

六、prototype

1.原型对象概述

(1)构造函数的缺点

function Cat (name, color) {
  this.name = name;
  this.color = color;
}

var cat1 = new Cat('大毛', '白色');

cat1.name // '大毛'
cat1.color // '白色'

上面代码中,Cat函数是一个构造函数,函数内部定义了name属性和color属性,所有实例对象(上例是cat1)都会生成这两个属性,即这两个属性会定义在实例对象上面。

通过构造函数为实例对象定义属性,虽然很方便,但是有一个缺点。同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费。

function Cat(name, color) {
  this.name = name;
  this.color = color;
  this.meow = function () {
    console.log('喵喵');
  };
}

var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');

cat1.meow === cat2.meow
// false

上面代码中,cat1cat2是同一个构造函数的两个实例,它们都具有meow方法。由于meow方法是生成在每个实例对象上面,所以两个实例就生成了两次。也就是说,每新建一个实例,就会新建一个meow方法。这既没有必要,又浪费系统资源,因为所有meow方法都是同样的行为,完全应该共享。

这个问题的解决方法,就是 JavaScript 的原型对象(prototype)。

(2)prototype属性作用

构造函数的一个方法,或者一个属性
function Animal(name) {
  this.name = name;
}

构造函数理解为一个类,类中有个属性prototype,被称为子类的原型对象,相当于在父类中增加了一个属性,子类可以共享。

Animal.prototype.color = 'white';

var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');

cat1.color // 'white'
cat2.color // 'white'

定义在同一个原型对象上,cat1.color === cat2.color。

(3)原型链

JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……

例如:cat1的原型对象是→Animal.prototype→×××.prototype→object.prototype→null

python最上层的父类是type(type一个类)

每个方法都有valueOf和toString,这两个方法是定义在object.prototype上的,object.prototype.valueOf = function()。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值