why-js-day2 this和对象原型

关于this

this提供一种更优雅的方式来隐式传递一个对象引用

记录函数的调用次数,第一种错误的理解,将this理解为指向函数自身

function foo(num) {
    console.log("foo: "+num);
    this.count++;
}
foo.count=0;
for (let i=0;i<10;i++){
    if (i>5){
        foo(i);
    }
}
console.log(foo.count);
console.log(count);

通过词法作用域改正

function foo(num) {
    console.log("foo: "+num);
    data.count++;
}
let data={
    count:0
};
for (let i=0;i<10;i++){
    if (i>5){
        foo(i);
    }
}
console.log(data.count);

通过函数名来引用函数对象(匿名函数只有通过arguments.callee,但已经弃用),但也回避了this,也依赖了foo的词法作用域

function foo(num) {
    console.log("foo: "+num);
    foo.count++;
}
foo.count=0;
for (let i=0;i<10;i++){
    if (i>5){
        foo(i);
    }
}
console.log(foo.count);

强制this指向foo对象

function foo(num) {
    console.log("foo: "+num);
    this.count++;
}
foo.count=0;
for (let i=0;i<10;i++){
    if (i>5){
        foo.call(foo,i);
    }
}
console.log(foo.count);

第二种错误的理解,this指向函数的作用域

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

this是在运行时绑定的,它的上下文取决于函数调用时的条件,this的绑定也函数声明的位置没有任何关系,只取决于函数的调用方式

this全面解析

调用栈和调用位置

调用栈:全局作用域-->baz-->bar-->foo

function baz() {
    console.log('baz');
    bar();
}
function bar() {
    console.log('bar');
    foo();
}
function foo() {
    console.log('foo');
}
baz();

绑定规则

  • 默认绑定,不带修饰的函数调用,会将this绑定到全局对象上,严格模式(strict mode)绑定到undefined
function foo() {
    console.log(this.a);
}
let a=2;
foo();
function foo() {
    "use strict";
    console.log(this.a);
}
let a=2;
foo();

严格模式下与函数调用位置无关

function foo() {
    console.log(this.a);
}
let a=2;
(function () {
    "use strict";
    foo()
})();
  • 隐式绑定,调用位置是否有上下文对象,或者说是否被某个对象拥有或包含
function foo() {
    console.log(this.a);
}
let obj={
    a:2,
    foo:foo
};
obj.foo();

对象属性引用链中只有最顶层或者说最后一层会影响调用位置

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

隐式丢失,即隐式绑定会丢失绑定对象,然后应用默认绑定

function foo() {
    console.log(this.a);
}
let obj={
    a:2,
    foo:foo
};
let bar=obj.foo;
let a="oops";
bar();
function foo() {
    console.log(this.a);
}
let obj={
    a:2,
    foo:foo
};
let a="oops";
setTimeout(obj.foo,100);
  • 显示绑定,使用call()和apply()强制把第一个参数(原始值会转换为对象,如string,number)对象绑定到this上
function foo() {
    console.log(this.a);
}
let obj={
    a:2
};
foo.call(obj);

硬绑定

function foo() {
    console.log(this.a);
}
let obj={
    a:2
};
let bar=function () {
    foo.call(obj);
};
bar();
setTimeout(bar,100);
bar.call(window);

包裹函数

function foo(something) {
    console.log(this.a,something);
    return this.a+something;
}
let obj={
    a:2
};
let bar=function () {
    return foo.apply(obj,arguments);
};
let b=bar(3);
console.log(b);

可重复使用的辅助函数

function foo(something) {
    console.log(this.a,something);
    return this.a+something;
}
function bind(fn,obj) {
    return function () {
        return fn.apply(obj,arguments);
    };
}
let obj={
    a:2
};
let bar=bind(foo,obj);
let b=bar(3);
console.log(b);

ES5后提供内置的Function.prototype.bind方法

function foo(something) {
    console.log(this.a,something);
    return this.a+something;
}
let obj={
    a:2
};
let bar=foo.bind(obj);
let b=bar(3);
console.log(b);

内置函数中的一个可选参数context(上下文),其作用和bind一样,确保回调使用指定的this

function foo(el) {
    console.log(el,this.id);
}
let obj={
    id:"awesome"
};
[1,2,3].forEach(foo,obj);
  • new绑定

JavaScript中实际不存在构造函数,只有对函数的构造调用,使用new来调用函数时会自动执行以下操作

  1. 创建一个全新的对象
  2. 这个对象会被执行[[原型]]连接
  3. 这个新对象会绑定到函数调用的this
  4. 如果函数没有返回其他对象,那么就会自动返回新对象
function foo(a) {
    this.a=a;
}
let bar=new foo(2);
console.log(bar.a);

优先级

显示绑定高于隐式绑定

function foo(a) {
    console.log(this.a);
}
let obj1= {
    a: 2,
    foo: foo
};
let obj2={
    a:3,
    foo:foo
};
obj1.foo();
obj2.foo();
obj1.foo.call(obj2);
obj2.foo.call(obj1);

new绑定高于隐式绑定

function foo(something) {
    this.a=something;
}
let obj1={
    foo:foo
};
let obj2={};
obj1.foo(2);
console.log(obj1.a);
obj1.foo.call(obj2,3);
console.log(obj2.a);
let bar=new obj1.foo(4);
console.log(obj1.a);
console.log(bar.a);

new绑定高于显示绑定(如果硬绑定函数被new调用,新创建的this就会替换硬绑定this)

function foo(something) {
    this.a=something;
}
let obj1={};
let bar=foo.bind(obj1);
bar(2);
console.log(obj1.a);
let baz=new bar(3);
console.log(obj1.a);
console.log(baz.a);

bind可以把除第一个参数外的其他参数传递给下层函数

function foo(p1, p2) {
    this.val=p1+p2;
}
let bar=foo.bind(null,"p1");
let baz=new bar("p2");
console.log(baz.val);

判断this

  1. 函数是否在new中调用,如果是this是新创建的对象
  2. 函数是否通过call.apply,或者bind调用,如果是this为指定的对象
  3. 函数是否在某个上下文对象中调用如果是,则this是上下文对象
  4. 如果都不是,则默认绑定

绑定例外

把null或undefined作为this绑定对象传入call,apply,bind,这些值会被忽略,实际应用默认绑定

function foo() {
    console.log(this.a);
}
let a=2;
foo.call(null);

会污染全局作用域

function foo(a,b) {
    console.log("a: "+a+",b: "+b);
}
foo.apply(null,[2,3]);
let bar=foo.bind(null,2);
bar(3);

更安全的this,将null换成空对象,Object.create(null)比{},少了prototype委托

function foo(a,b) {
    console.log("a: "+a+",b: "+b);
}
let o=Object.create(null);
foo.apply(o,[2,3]);
let bar=foo.bind(0,2);
bar(3);
function foo(a,b) {
    console.log(a,b);
}
foo(...[1,2]);

间接引用,函数的间接引用,在这种情况下会默认绑定((p.foo=o.foo)的返回值是foo)

function foo() {
    console.log(this.a);
}
let a=2;
let o={
    a:3,
    foo:foo
};
let p={
    a:4
};
o.foo();
(p.foo=o.foo)();

软绑定

if (!Function.prototype.softBind){
    Function.prototype.softBind=function (obj) {
        let fn=this;
        let curried=[].slice.call(arguments,1);
        let bound=function () {
            return fn.apply(
                (!this||this===(window||global))?obj:this,
                curried.concat.apply(curried,arguments)
            );
        };
        bound.prototype=Object.create(fn.prototype);
        return bound;
    };
}
function foo() {
    console.log("name: "+this.name);
}
let obj={name:"obj"},
    obj2={name:"obj2"},
    obj3={name:"obj3"};
let fooOBJ=foo.softBind(obj);
fooOBJ();
obj2.foo=foo.softBind(obj);
obj2.foo();
fooOBJ.call(obj3);
setTimeout(obj2.foo,10);

this词法(=>胖箭头),根据外层作用域来决定this

function foo() {
    return (a)=>{
        console.log(this.a);
    }
}
let obj1={
    a:2
};
let obj2={
    a:3
};
let bar=foo.call(obj1);
bar.call(obj2);

箭头函数的绑定无法被修改

function foo() {
    setTimeout(()=>{
        console.log(this.a);
    },100);
}
let obj={
    a:2
};
foo.call(obj);

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值