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无法被修改。
显式绑定原理
call
、apply
、bind
的原理:
// 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
。而String
、Number
、Boolean
、Object
、Function
、Array
、Date
、RegExp
、Error
等都是object
的子类型。
可以使用typeof
和instanceof
判断变量的类型和对象子类型:
// 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
属性描述对象中,还有两个关键的属性getter
和setter
:
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
value
、writable
、enumerable
、configurable
被称为属性描述符。getter
、setter
被称为访问描述符。
如何判断某属性是否存在于对象中呢:
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;