JavaScript之this和对象原型

this:动态指向

this的绑定遵循以下规则:

// 1.默认绑定:不适用于其他的默认规则,this绑定到全局对象(严格模式下绑定到undefined)
function foo() {
  console.log(this.a);
}
var a = 2;
foo(); // 2


// 2.隐式绑定:调用位置有上下文对象,this绑定到引用链中的最后一层
function foo() {
  console.log(this.a);
}
var obj2 = { a: 42, foo: foo };
var obj1 = { a: 2, obj2: obj2 };
obj1.obj2.foo(); // 42
// 隐式丢失:虽然foo_指向foo,但是实际调用时未带任何上下文对象,所以应用默认绑定
var a = 442;
var foo_ = obj1.obj2.foo;
foo_(); // 442


// 3.显式绑定:使用call、bind、bind将this绑定到指定对象中
function foo(a, b) {
  console.log(this.a, this.b, a, b);
}
var a = "global_a";
var b = "global_b";
var obj = {
  a: "obj_a",
  b: "obj_b"
};
foo(a, b); // undefined undefined global_a global_b
foo.call(obj, a, b); // obj_a obj_b global_a global_b
foo.apply(obj, [a, b]); // obj_a obj_b global_a global_b
var foo_ = foo.bind(obj);
foo_(a, b); // obj_a obj_b global_a global_b

// 4.new绑定:new的过程中,新对象会被绑定到函数调用的this
function foo(a) {
  this.a = a;
}
var bar = new foo(2); // this被绑定到bar上
console.log(bar.a); // 2

但 ES6 新增的箭头函数例外,箭头函数内部的this会绑定到调用箭头函数时所处环境的this上。同时,箭头函数中的this无法被修改。

显式绑定原理

callapplybind的原理:

// call
Function.prototype.myCall = function(obj) {
  var obj = obj || window;
  var args = [...arguments].slice(1);
  var func = Symbol();
  obj.func = this;
  var result = obj.func(...args);
  delete obj.func;
  return result;
};

// apply
Function.prototype.myApply = function(obj) {
  var obj = obj || window;
  var args = [...arguments][1];
  var func = Symbol();
  obj.func = this;
  var result = obj.func(...args);
  delete obj.func;
  return result;
};

// bind:支持函数柯里化
Function.prototype.myBind = function(obj) {
  var obj = obj || window;
  var args = [...arguments].slice(1);
  var func = Symbol();
  obj.func = this;
  return function() {
    var newArgs = [...arguments];
    obj.func(...args, ...newArgs);
  };
};

对象

在 JavaScript 中,有 5 种简单基本类型,1 种引用类型,即object。而StringNumberBooleanObjectFunctionArrayDateRegExpError等都是object的子类型。

可以使用typeofinstanceof 判断变量的类型和对象子类型:

// a的类型:string
var a = "hello string.";
typeof a; // 'string'
a instanceof String; // false
// b的类型:[Object String]
var b = new String("hello string object");
typeof b; // 'object'
b instanceof String; // true

复制对象

// a和a_的值都是5
var a = 5;
var a_ = a;
a_ += 5;
console.log(a, a_); // 5 10
// b和b_都是对同一个对象的引用,这称为浅复制
var b = { attr: "attr" };
var b_ = b;
b_.attr = "attr_";
console.log(b, b_); // {attr:'attr_'} {attr:'attr_'}
// 如果想要克隆对象,即深复制,可以
var b_clone = JSON.parse(JSON.stringify(b));
b_clone.attr = "attr_clone";
console.log(b, b_clone); // {attr:'attr_'} {attr:'attr_clone'}

对象中的每个属性都有其描述对象,这称为属性描述对象。获取属性描述对象

var obj = { a: "a" };
Object.getOwnPropertyDescriptor(obj, "a");
// { value: 2, writable: true, enumerable: true, configurable: true };

修改属性描述对象:

// 接上
Object.defineProperty(obj, "a", {
  value: "6",
  writable: true,
  enumerable: true,
  configurable: false
});
obj.a = 32; // 赋值还是能正常进行的

如果想要设置一个常量:

Object.defineProperty(obj, "a", {
  writable: false,
  configurable: false
});

如果想要限制一个对象:

// 禁止扩展:禁止对象添加新属性
var obj_1 = {};
Object.preventExtensions(obj_1);
obj_1.a = "a"; // 失败啦
// 密封:禁止对象添加新属性,以及配置、删除现有属性(现有属性的值是可以修改的)
var obj_2 = {};
Object.seal(obj_2); // 其实相当于禁止扩展+把现有属性的configurable都设为false
// 冻结:禁止对象添加新属性,以及修改、配置、删除现有属性
var obj_3 = {};
Object.freeze(obj_3); // 这个更狠,密封以及writable=false

属性描述对象中,还有两个关键的属性gettersetter

var obj = { a: 5 };
Object.defineProperty(obj, "a_", {
  get: function() {
    return this.a + 5;
  },
  set: function(val) {
    this.a *= val;
  }
});
console.log(obj.a_); // 10,因为a+5=10
obj.a_ = 10; // a=5*10=50
console.log(obj.a_); // 55,因为a+5=55
valuewritableenumerableconfigurable被称为属性描述符。  gettersetter被称为访问描述符。

如何判断某属性是否存在于对象中呢:

var obj = { a: 2 };
obj.hasOwnProperty("a"); // true
obj.hasOwnProperty("b"); // false

原型链

对象都有一个特殊的[[Prototype]]属性,其中包含对于其他对象的引用。当访问一个对象的属性时,如果在当前对象没找到,会继续在其[[Prototype]]链中寻找,直至尽头(尽头通常是Object.prototype)。

// 假设obj的原型链上层一对象有属性a
var obj = {};
// 取值:会返回原型链中的a
obj.a;
// 赋值:情况比较复杂
// 1.如果原型链上的属性a可写,则会为obj添加属性a,并赋值
// 2.如果原型链上的属性a只读,则会报错(严格模式下)
// 3.如果原型链上的属性a有setter,则会调用这个setter
obj.a = "a";

举一个例子:

var obj = { a: 2 };
var obj_ = Object.create(obj); // 使obj_关联于obj
obj_.a; // 2,实际访问obj.a
obj.hasOwnProperty("a"); // true
obj_.hasOwnProperty("a"); // false
obj.a++; // 触发第一种情况,等价于obj.a_=obj.a;obj.a_++;
obj.a; // 2
obj_.a; // 3
obj_.hasOwnProperty("a"); // true

另外,附上Object.create()的实现:

function objectCreate(obj) {
  function F() {}
  F.prototype = obj;
  return new F();
}

构造函数

所有函数默认有一个prototype(不可枚举的)属性,它指向的对象称为原型对象。而通过构造函数创建的变量,其原型链上会被添加上与该原型对象的关联:

function Foo() {}
var foo = new Foo();
Object.getPrototype(foo) === Foo.prototype; // true
Foo.prototype.constructor === Foo; // true
foo.constructor === Foo; // true,实际上是foo.__proto__.constructor===Foo

构造函数本质也就是个普通的函数,它的特殊性不在于其本身,而在于new调用的过程中会构造一个对象并赋值给“实例”:

function Foo(name) {
  this.name = name;
}
Foo.prototype.sayHello = function() {
  return this.name;
};
var a = new Foo("aaa");
a.sayHello(); // 'aaa'

new的过程具体发生了什么呢?

function new_(func) {
  var args = [...arguments].slice(1);
  var obj = {};
  Object.setPrototypeOf(obj, func.prototype);
  func.apply(obj, args);
  return obj;
}
var obj = new_(Foo, arg1, arg2);

继承

JavaScript 中的继承更像是关联,而不是父子继承。

首先,定义一个父类:

function Animal(name) {
  this.name = name || "anonymous";
}
Animal.prototype.sayHello = function() {
  console.log(this.name + ": hello!");
};

然后定义子类和设置继承:

// 1.原型继承:Animal私有、公有属性均被克隆了一份
function Cat() {}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
// 2.借用构造函数继承:Animal的公有属性未被继承
function Cat() {
  Animal.call(this);
}
// 3.组合继承:原型继承+借用构造函数继承
function Cat() {
  Animal.call(this);
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
// 4.寄生组合式继承:最佳
function Cat() {
  Animal.call(this);
}
Cat.prototype = Object.assign(Object.create(Animal.prototype), Cat.prototype);
Cat.prototype.constructor = Cat;

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值