深入理解js中this和对象原型

86 阅读8分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 20天,点击查看活动详情

误解

第一误解是this是指向函数本身。 函数本身直接用函数名表示就可以。this不行。

第二个误解是 this是受到作用域的影响。不会受到作用域的影响。

this的四条规则

1,默认绑定window,如果是严格模式下是绑定undefined

2,强制绑定(call,bind,apply).如果是基础类型则会被装箱。

3,隐式绑定(就是谁点它,就是谁)

  • 参数的传递 。foo.bar这样的参数传递,参数接收已经改变了变量了。setTimout就是传递回调函数,然后在里边执行这个回调函数。我们把这种情况叫做丢失this指向。

4,new绑定

  • 其实所有的函数都是普通函数,当使用new时就都变成了构造函数。其实也可以说是new的构造操作。new总共分成四个步骤,第一是 创建一个全新的对象 , 第二个是新对象和函数之间的原型 第三是 新对象绑定this 第四是 如果函数没有返回其他值,那么就返回this对象。

this四种绑定的优先级

在new里边 >显示绑定 >隐士绑定>默认绑定

绑定例外

被忽略的this

如果是null 和 undefined,传入硬绑定中作为this的绑定值得话,这些值会被忽略。

为了不使全局污染,则使用{}来替代null和undefined。不然会默认绑定的全局的window上,这样会导致全局污染。

间接绑定

间接绑定的注意事项:

o.foo();o是隐式绑定。但是如果是 p.foo=o.foo ,那么p.foo()不是隐式绑定,p.foo就是一个函数的引用。

箭头函数

箭头函数的this就是当前作用域的this。

对象

对象有两种构造形式,一种是文字的构造形式,一种是构造函数的形式。

{} 和 new Object();

六种类型

每一种类型都对应了一个对象。

虽然我们在操作字面量,但是在操作字面量的方法时,其实字面量是转换成了对象的。

六中类型中 null和undefined是没有构造形式的,只有文字字面量的形式。相反Date只有构造的形式。

内置对象:

Number,String,Boolean 有字面量,其实实现的还是构造对象
Date,只有构造函数一种创建方式。
Array,Function,Object,RegExp,不管是字面量创建还是构造函数创建,都是一个对象。
Error. 一般自动创建

. 和 [] 的访问:

.是属性访问,[] 是键访问。区别是 .是需要符合命名规则的,并且必须是字符串,如果不是字符串的会转换成字符串。

但是键访问不需要。例如:"---",变量中间是空格等不规则的字符。 还要一个重要的区别是,键访问可以是变量。但是属性访问是不可以的。

Es6中还增加了键访问的计算特性。可以在[]中进行简单计算。

属性和方法

Object.defineProperty(obj,{

value:"",

writable:是否可写(就是不能够进行修改)

configurable:"",不可配置

enumrable:枚举

        

});

TypeError 无法修改一个不可写的属性。

上述设定的内容,如果在严格模式下则会报错。

configurable:不可修改,同时也不可删除。并且这操作是单向的,不能够修改回来。不管硬性操作会报错。

不变性

对象的常量:

设置wribale和configurable 设置为false则这个就是常量,不能再改变的。

get 和 set

get和set 可以在字面量对象中设置,也可以在Object.defineProperty中进行设置。

在字面量对象中设置get是{get a(){} }这样的方式,属性是写在get函数名的后边的。

注意:如果只设置get,不设置set,那么会导致对象每次返回的都是get里边的值。而没有改变原来的值。

存在性

属性的存在性主要是两个方式,一个是使用in来进行判断一个是使用hasOwnProperty

in是包括原型链上的

hasOwnProperty是不包括原型链上的。但是是其实它自己是通过property这个原型链访问的。

Object.create()的理解和分析

作用是返回一个对象,给该对象增加一条原型链的属性,指向该对象。

         //增加一条原型链 指向 传递的参数
				let obj={a:1}
        //下边这两句话几乎是对等的
        let obj2=Object.create(obj);
        obj3.__proto__=obj;

Object.create(null); 如果是这么写的话,那么就是原型链是指向空的。

Object.keys()和Object.getOwnPropertyNames()

Object.keys();包含可枚举的属性。

Object.getOwnProertyNames();包含所有的属性,不管是不是可枚举的。

这个两个的区别是都只会查找对象直接包含的属性。

propertyIsEnumrable()

检查在对象本身上属性

原型

Object.property的属性修改

如果属性在对象中没有,那么会通过原型链条进行查找。如果查找不到会,会在对象中新增一个属性。如果能查找到则会修改,如果修改的话,会存在隐士屏蔽,一般是用于在原型链条上进行获取。

还有一种情况是:在原型链条上可以查找但是是只读属性。那么通过=号的操作就会被忽略,同时在这条原型链条上都不能增加同名属性。这样是保护父属性的一种策略。但是这种策略在使用Object.proerty中是可以打破的

隐士屏蔽(五星)

对原型链条行的属性进行操作时,会屏蔽掉原型链条的属性。同时会给自己新增这个属性。

这个是隐士屏蔽

       anther={a:2};
        let myObj=Object.create(anther);
        console.log(myObj.a);//2

        myObj.a++;
        console.log(anther.a);//2
        console.log(myObj.a);//3
        console.log(myObj.hasOwnProperty("a"));//true,同时新增了a属性

property的创建

property是写构造函数时就存在的

new的时候目前我只能记忆起4件事:

1创建对象

2对象的原型链指向构造函数的原型

3绑定对象geithis

4如果没有返回值则返回this

constructor

是指向构造函数。

对象.constructor可以通过原型链找到。

如果原型={...},那么会通过原型链找到Object.property.constructor

如果原型={...}我们也可以通过Object.defineProperty...增加一个constructor属性

constructor是可修改的,只是不可枚举。

Object.create()为啥要用(五星)

        Bar.prototype=Object.create(Foo.prototype);
				vs
        Bar.prototype=Foo.prototype

创建一个新对象,这个对象只有原型链__proto__指向 目标。例如像constructor是没有的。

是因为使用Object.create并不是直接引用Foo的原型,而是给Bar.prototype增加一个__proto__属性(原型链)指向Foo的原型。

那么以后在Bar的原型上增加方法是在Bar自己的原型上增加而不会改变Foo的原型,同时Foo的原型上的方法,通过原型链Bar又可以调用。

如果写成Bar.prototype=Foo.prototype那么以后对Bar的原型上的操作就会直接影响Foo.这样容易出现问题。

setPrototypeof可以替代Object.create()

Object.setPrototypeOf(旧的,新的)

这个方法比上面那个方法更丰富。

Object.create只是增加一个原型链的对象,没有constructor。但是使用Object.setPrototypeOf()则是在原先的对象中改变原型链指向目标对象。之前的constructor等内容都是存在的。并不是创建一个新的对象进行替代。

如下代码

aaa不仅仅能访问到a,还能通过__proto__访问到b.

        let aaa={a:"haha"};
        let bbb={b:"bbbb"};
        Object.setPrototypeOf(aaa,bbb);
        console.log(aaa,bbb);

核心总结:Object.create是生成一个新的具有原型链的对象。setPrototypeOf是改变原型链指向。

instanceof只能判断对象和构造函数的关系

如果是两个对象,那么就不能够使用这个进行判断了。

instanceof其实无法真正的去判断对象是否是构造函数的实例。

如下实例:a并不是Foo的实例,但是却可以返回true:

        let a={};
        let b=Object.create(a);
        
        function isIable(a,b){
            function Foo(){}
            Foo.prototype=a;
            return b instanceof Foo;
        }
        console.log(isIable(a,b));//true,但是其实Foo和b并没有直接的关系。

核心总结:instanceof是判断对象的原型链是否指向函数的原型。但是用来判断实例是否属于构造函数是存在误差的。

Foo.prototype.isPrototypeOf()

这个方法判断的是对象的整个原型链条是否有包含Foo.prototype这个原型链。如果包含返回的是true,否则返回的是false。

这个方法其实就是我们上述内容自己写的isIable的方法。

Object.create()进一步理解

        let a={};
        console.log(a);//是个空对象,但是__proto__会指向Object.prototype
        let b=Object.create(null);//不会有任何的原型链指向。用于存储数据不会受到其他链条的影响
        console.log(b);

行为委托

对象关联

就是把需要的方法写在对象里边。如:

let obj={
	a:function(){},
  b:function(){}
}
let obj2=Object.create(obj);
//这样obj2就可以调用 obj1里边的方法。
并且obj2重写a这个方法也不会影响obj。
这个是非常重要的一点:Object.create();