你不知道的javascript(二)

第二部分:this和对象原型

1.关于this

1.1 为什么要使用this

this上下文对象

function identify(){
	return this.name.toUpperCase();
}
function speak(){
	var greeting = "hello,i'm "+identify.call(this);
	console.log(greeting)
}
var me = {
	name:"kyle"
};
var you = {
	name :"reader"
};
identify.call(me); // KYLE
identify.call(you); // READER
speak.call(me); // hello,i'm KYLE
speak.call(you); // hello,i'm READER

这段代码使得可以在不同的上下文中重复使用函数identify和speak,不用针对每个对象编写不同版本的函数

否则需要为函数identify和speak显示传递一个上下文对象
this就提供了一个更优雅的方式隐式传递一个对象引用

1.2 误解

  • 误解一:1.this指向函数自身
    函数内部引用函数自身,最常见的使用是递归,或者写一个在第一次被调用后自己接触绑定的事件处理
function foo(num){
	console.log("foo:"+num);
	this.count++
	// 记录foo被调用的次数
}
foo.count = 0;
var i;
for(i = 0;i<10;i++){
	if(i>5){
		foo(i)
	}
}

// foo:6
// foo:7
// foo:8
// foo:9
// foo 被调用的次数
console.log(foo.count)   // 0

因此this并不是对自身的引用

function foo(num){
	console.log("foo:"+num);
	data.count++
	// 记录foo被调用的次数
}
var data = {
	count = 0;
}

var i;
for(i = 0;i<10;i++){
	if(i>5){
		foo(i)
	}
}

// foo:6
// foo:7
// foo:8
// foo:9
// foo 被调用的次数
console.log(data.count)   // 4

上述方法虽然解决了计数问题,但是从本质上,是使用了词法作用域的方式来进行解决

function foo(){
	foo.count = 4;
	// foo指向它的自身
}
setTimeout(function(){
	// 匿名,函数无法指向自身
},10)

从函数内部引用自身,需要一个指向函数对象的词法标识符(变量)来引用它
第一个函数称为具名函数,,内部使用foo来引用自身
但是第二个函数中,回调函数没有名称标识符

  • 传统的方式:
    arguments,和callee来引用当前正在运行的函数对象
function foo(num){
	console.log("foo:"+num);
	this.count++
	// 记录foo被调用的次数
	// 在当前的调用方式下,this确实指向foo
}
foo.count = 0;
var i;
for(i = 0;i<10;i++){
	if(i>5){
		foo.call(foo,i)
	}
}

// foo:6
// foo:7
// foo:8
// foo:9
// foo 被调用的次数
console.log(foo.count)   // 4 
  • 误解二:2.this作用域
    第二种常见误解,就是this指向函数的作用域,这种说法,时而正确,时而不正确
    作用域和对象确实相似,可见标识符都是他们的属性,但是作用域无法通过js代码访问,存在于js引擎内部

1.3 this到底是什么

this是在运行时进行绑定的,并不是在编写时绑定,
他的上下文取决于函数调用时的各种条件,只取决于函数调用方式

当一个函数被调用时,会创建一个活动记录(执行上下文)
这个记录包含函数在哪里被调用,函数的调用方式,传入的参数信息等

2.this全面解析

2.1 调用位置

调用位置是函数在代码中被调用的位置

主要分析调用栈(为了到达当前执行位置所调用的所有函数)
调用位置是当前正在执行的函数的前一个调用中

function baz(){
	// 当前调用栈是:baz
	// 因此当前调用位置是全局作用域
	console.log("baz")
	bar();    // bar 调用位置
}
function bar(){
	// 当前调用栈是:baz -> bar
	// 因此当前调用位置是baz
	console.log("bar")
	foo();    // foo 调用位置
}
function foo(){
	// 当前调用栈是:baz -> bar ->foo
	// 因此当前调用位置是bar
	console.log("foo")
}

baz();  // baz的调用位置

baz的调用位置可以将调用栈想象成一个函数调用链
使用浏览器的调试工具:
在foo函数的第一行设置一个断点,或者在第一行代码之前插入一条debugger,则运行时,会显示出当前位置的函数调用列表

2.2 绑定规则

1.默认绑定

独立函数调用,可以看成是无法应用其他规则时的默认规则
this指向全局对象

function foo(){
	console.log(this.a)
}
var a = 2;
foo()

foo()调用的时候,是直接不适用任何修饰的函数引用进行调用的,此时是默认绑定,this指向全局对象

但是只有在使用非严格模式下,默认绑定才能绑定到全局对象,在严格模式下,不影响默认绑定

2.隐式绑定

考虑调用位置是否有上下文对象,或者是否被某对象拥有或包含

function foo(){
	console.log(this.a)
}
var obj = {
	a:2,
	foo:foo
}
obj.foo()

调用位置使用obj为上下文来引用函数,因此被调用时,obj对象拥有或包含函数引用

当函数拥有上下文对象时,隐式绑定规则会把函数调用中的this绑定在这个上下文对象上

function foo(){
	console.log(this.a)
}
var obj2 = {
	a:42,
	foo:foo
}
var obj1 = {
	a:2,
	foo:foo
}
obj1.obj2.foo(); // 42

对象属性引用链中,只有上一层或者说最后一层在调用位置中起作用

function foo(){
	console.log(this.a)
}
var obj = {
	a:2,
	foo:foo
}
var bar = obj.foo;  //函数别名
var a = "opps,global";  //a是一个全局对象的属性
bar();  //"opps,global"

bar是obj.foo的一个引用,但实际上,引用的是函数本身,此时bar是一个不带任何修饰的函数调用,为默认绑定

function foo(){
	console.log(this.a)
}
function doFoo(fn){
	// fn 实际引用的是foo
	fn();  // 调用位置
}

var obj = {
	a:2,
	foo:foo
}
var a = "opps,global";  //a是一个全局对象的属性
doFoo(obj.foo);  //"opps,global"

参数传递,也是一种隐式传递,被隐式赋值,默认绑定

setTimeout的回调函数也会发生丢失this的情况

3.显示绑定

在某个对象上强制调用函数,使用函数的call()和apply()方法,

第一个参数为一个对象,为this准备,在调用的时候将其绑定到this,由于可以直接指定this绑定到的对象,称为显示绑定

function foo(){
	console.log(this.a)
}
var obj = {
	a:2,
}
foo.call(obj);  // 2

调用call函数,将this绑定到obj中

如果此时传入的是一个原始值(字符串类型,布尔类型,或者数字类型)作为this的绑定对象,则这些原始值会被转换成它的对象形式,(new String(),new Boolean()等),这种行为称为装箱

显示绑定的变种:
硬绑定

function foo(){
	console.log(this.a)
}
var obj = {
	a:2,
}
var bar = function(){
	foo.call(obj)
};
bar(); // 2
setTimeout(bar,100);  // 2
// 硬绑定的bar不可能再修改它的this
bar.call(window); // 2

bar()函数中,foo.call(obj):强制把foo的this绑定到了obj,后面再调用bar函数,则总在obj上调用foo

上述方法为显示的强制绑定,称为硬绑定

使用场景:

  • 包裹函数:负责接收参数并返回值
function foo(something){
	console.log(this.a,something)
	return this.a+something
}
var obj = {
	a:2,
}
var bar = function(){
	return foo.apply(obj,arguments);
};
var b = bar(3);  // 2 3
console.log(b)  // 5
  • 创建一个可重复使用的辅助函数
function foo(something){
	console.log(this.a,something)
	return this.a+something
}
function bind(fn,obj){
	return function(){
		return fn.apply(obj,arguments)
	} ;
}
var obj = {
	a:2,
}
var bar = bind(foo,obj)
var b = bar(3);  // 2 3
console.log(b)  // 5

ES5提供了内置的方法:Function.prototype.bind
bind会返回一个硬编码的新函数,把你指定的参数设置为this的上下文并调用原始函数

API调用的上下文
第三方库的很多函数,以及js语言和宿主环境中许多新的内置函数,都提供了一个可选参数,称为上下文,和bind的作用一致,确保回调函数使用指定的this

function foo(el){
	console.log(el,this.id);
}
var obj = {
	id:"awesome"
};
// 调用foo(...)时将this绑定到obj
[1,2,3].forEach(foo,obj);
// 1 awesome 2 awesome 3 awesome
4.new绑定

在js中,构造函数是一些使用new操作符时被调用的函数,并不属于某一个类,也不会实例化一个实例

包括内置对象函数(比如Number)在内的所有函数都可以使用new来调用,这种函数调用被称为构造函数调用

实际上不存在所谓的构造函数,只存在函数的构造调用

使用new来调用函数,会自动执行下面的操作

  • 创建(或称构造)一个全新的对象
  • 这个新对象执行Protype连接
  • 这个新对象会绑定到函数调用的this
  • 如果函数没有其他的返回对象,则new表达式中的函数调用会自动返回这个新对象

2.3优先级

如果某一个调用位置可以应用多条规则,这是需要为这些规则设置一个优先级

  • 默认绑定,是优先级最低的
function foo(){
	console.log(this.a)
}
var obj1 = {
	a:2,
	foo:foo
}
var obj2 = {
	a:3,
	foo:foo
}

obj1.foo(); // 2
obj2.foo(); // 3
obj1.foo.call(obj2); // 3
obj2.foo.call(obj1); // 2
  • 由此可见,显示绑定优先级更高
    优先级:
    1.new绑定
    2.显示绑定
    3.隐式绑定
    4.默认绑定

2.4绑定例外

  • 被忽略的this
    若把null或undefined作为this的绑定对象传入call,apply,bind,在调用时,会被忽略,执行的是默认绑定规则
    当并不关心第一个参数的时候,使用null进行占位
function foo(a,b){
	console.log("a:"+a+"b:"+b)
}
// 把数组展开成参数
foo.apply(null,[2,3]) // a:2,b:3
// 使用bing(...)进行柯里化
var bar = foo.bind(null,2)
bar(3); // a:2,b:3

但此时this是绑定到全局对象中的,容易造成一定的副作用,产生难以追踪和分析的bug

  • 更安全的this
    传入一个特殊的对象,将this绑定到这个对象,不会对程序产生任何副作用
    “DMZ”(demilitarized zone)对象是一个空的非委托对象

  • 间接引用
    函数的间接引用,会使用默认绑定规则
    间接引用最容易在赋值时发生

  • 软绑定
    硬绑定会降低函数的灵活性,使用硬绑定之后就无法使用隐式绑定或显示绑定修改this
    如果可以给默认绑定指定一个全局对象,和undefined之外的值,可以实现和硬绑定相同的效果,同时保留隐式绑定或者显示绑定来修改this的能力 ——软绑定

2.5 this的词法

箭头函数不使用this的四种规则,二是根据外层(函数或全局)作用域来决定this

箭头函数的绑定不会被修改

箭头函数最常用于回调函数中,例如事件处理器或者定时器
这个ES6之前代码中的self = this机制相同

3.对象

4.混合对象“类”

5.原型

6.行为委托

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

this_is_Azou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值