this全面解析
调用栈
函数调用位置
就是函数在代码中被调用的位置(而不是声明的位置),寻找调用位置最重要的就是分析调用栈
。
this绑定规则
- 默认绑定(独立函数调用 fn)
这个时候的this是window
(注意:如果使用严格模式,则不能将全局对象用于默认绑定
)
虽然this的绑定规则完全取决于调用位置,但是只有函数运行在非strict mode下时,默认绑定才能绑定到全局对象;在严格模式下调用函数则不影响默认绑定。
function foo(){
'use strict'
console.log(this.a)
}
var a = 2;
foo(); // TypeError : this is undefined
function foo(){
console.log(this.a)
}
var a = 2;
(function(){
'use strict'
foo() // 2
})()
- 隐式绑定(obj.fn)
调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含,这时候隐式绑定规则会把函数调用中的this绑定到这个上下文对象。
function foo(){
console.log(this.a)
}
var obj = {
a:2,
foo:foo
}
obj.foo() // 2
隐式规则会有时候丢失绑定对象。
1、定义函数别名导致丢失
function foo(){
console.log(this.a)
}
var obj = {
a:2,
foo:foo
}
var bar = obj.foo;
var a = 'xx'
bar() // 'xx' // 这个时候导致this 指向的全局
2、当做回调函数
function foo(){
console.log(this.a)
}
var obj = {
a:2,
foo:foo
}
var a = 'xx'
setTimeout(obj.foo,100) // 'xx'
- 显示绑定(obj.bind/apply/call,
bind实现考虑作为构造函数
)
传递的第一个参数是this对象,如果传入的是一个原始值来当做this的绑定对象,这个原始值会被转换成它的对象形式(也就是new String()、new Boolean()或者new Number()),就是装箱。
function foo(){
console.log(this.a)
}
var obj = {
a:2
}
foo.call(obj) // 2
function foo(something){
console.log(this.a,something);
return this.a + something;
}
// 辅助函数bind
function bind(fn,obj){
return function(){
return fn.apply(obj,arguments)
}
}
var obj = {
a:2
}
var bar = bind(foo,obj)
var b = bar(3) ;
console.log(b) // 5
- new绑定(var a = new A())
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
1、创建一个全新的对象。
2、这个新对象会被执行[[prototype]]连接
3、这个新对象会绑定到函数调用的this
4、如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
function foo(a){
this.a = a;
}
var bar = new foo(2);
console.log(bar.a) // 2
优先级
new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
绑定例外
如果显示绑定的this传递的null
或者undefined
,实际应用的是默认绑定规则。
创建一个安全的空对象的最简单的方法就是使用Object.create(null),它创建的空对象不会创建Object.prototype这个委托,所以他比{}更空。
赋值表达式p.foo = o.foo 的返回值是目标函数的引用
,因此调用位置是foo()而不是p.foo() 或者 o.foo() ,因此这里会应用默认绑定。
function foo(){
console.log(this.a)
}
var a = 2;
var o = {a:3,foo:foo}
var p = {a:4}
o.foo() //3
(p.foo = o.foo)() // 2
对于默认绑定来说,决定this绑定对象并不是调用位置是否处于严格模式
,而是函数体是否处于严格模式
。如果函数体处于严格模式,this会被绑定到undefined,否则this会被绑定到全局。
箭头函数的this
箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。
function foo(){
return (a) => {
// this 继承自foo()
console.log(this)
}
}
var obj1 = {
a:2
}
var obj2 = {
a:3
}
var bar = foo.call(obj1);
bar.call(obj2) // 2
对象
类型
主要类型
- string
- number
- boolean
- null
- undefined
- object
内置对象
- String
- Number
- Boolean
- Object
- Function
- Array
- Date
- RegExp
- Error
内容
1、在对象中,属性名永远都是字符串
,如果你使用string(字面量)以外的其他值作为属性名,那它首先会被转换为一个字符串,即使是数字也不例外。
2、数组也是对象,所以虽然每个下标都是整数
,你仍然可以给数组添加属性.但是数组的length值并未发生改变
3、如果你试图向数组添加一个属性,但是属性名是一个数字
,那么就会变成数值下标(因此会修改数组的内容而不是添加一个属性)
var myArray = ['foo',42,"bar"];
myArray.baz = "baz";
myArray.length // 3
myArray.baz // "baz"
属性描述符(数据描述符)
对象默认的属性描述符
var myObject = {
a:2
}
Object.getOwnPropertyDescriptor(myObject,a)
/*
{
value:2,
writable:true, // 可写
configurable:true, // 可配置
enumerable:true // 可枚举
}
*/
自己设置属性描述符
var myObject = {
}
Object.defineProperty(myObject,a,{
value:2,
writable:true,
configurable:true,
enumerable:true
})
writable
决定是否可以修改属性的值,true为可以,false为不可以
configurable
决定属性是否可配置,如果通过defineProperty定义一个属性configurable为false,则不可以在进行配置(defineProperty),configurable:false还会禁止删除这个属性
注意:即使属性是configurable:false ,我们还是可以把writable的状态由true改为false,但是无法由false改为true
enumerable
控制属性是否会出现在对象的属性枚举中,比如说for..in循环。
不变性
1、对象常量
结合writable:false和configurable:false就可以创建一个真正的常量属性(不可修改、重定义或者删除)
2、禁止扩展
如果想禁止一个对象添加新属性并且保留已有属性,可以使用Object.preventExtensions()
Object.preventExtensions(myObject)
3、密封 Object.seal()(Object.preventExtensions加所有属性标记为configurable:false)
4、冻结 Object.freeze() (Object.seal加把所有的"数据访问"属性标记为writable:false)
[[Put]]
[[Put]]被触发时,实际的行为取决于许多因素,包括对象中是否已经存在这个属性(这是最重要的因素),如果已经存在这个属性,[[Put]]算法大致会检查下面这些内容。
1、属性是否是访问描述符
?如果是并且存在setter就调用setter。
2、属性的数据描述符
中writable是否为false?如果是,在严格模式下静默失败,在严格模式下抛出TypeError异常。
3、如果都不是,将该值设置为属性的值。
Getter和Setter
在ES5中可以使用getter和setter部分改写默认操作,但是只能应用在单个属性上,无法应用在整个对象上。getter是一个隐藏函数,会在获取属性时
调用。setter也是一个隐藏函数,会在设置属性时
调用
当你给一个属性定义getter、setter或者两者都有时,这个属性会被定义为"访问描述符"(和"数据描述符"相对)。对于访问描述符来说,Javascript会忽略它们的value和writable特性,取而代之的是关心set和get特性。
var myObject = {
// 给a定义一个getter
get a() {
return 2
}
}
Object.defineProperty(
myObject,
"b",
{
get:function(){
return this.a *2
},
enumerable:true
}
)
myObject.a = 3 // 没有设置成功 因为没有set,所以一般get和set要成对出现
myObject.a // 2
myObject.b // 4
存在性
in操作符会检查属性是否在对象及其[[Prototype]] 原型链中。(实际上检查的是某个属性名
是否存在)
hasOwnProperty只会检查属性是否在myObject对象中,不会检查[[Prototype]]链。
enumerable:true 就相当于"可以出现在对象属性的遍历中"。
propertyIsEnumerable会检查给定的属性名是否直接存在于对象中
(而不是在原型链上)并且满足enumerable:true。
Object.keys会返回一个数组,包含所有可枚举属性。
Object.getOwnPropertyNames会返回一个数组,包含所有属性,无论他们是否可枚举。
in和hasOwnProperty的区别在于是否查找[[Prototype]]链。
object.keys和object.getOwnPropertyNames都只会查找对象直接包含的属性。
遍历
如果想直接遍历数组的值可以使用for..of循环语法。(如果对象本身定义了迭代器的话也可以遍历对象)
for..of循环首先会向被访问对象请求一个迭代器对象,然后通过调用迭代器对象的next()方法来遍历所有返回值。
var myArray = [1,2,3]
for(var v of myArray){
console.log(v) // 1,2,3
}
数组有内置的@@iterator,因此for..of可以直接应用在数组上。我们使用内置的@@iterator来手动遍历数组,
var myArray = [1,2,3];
var it = myArray[Symbol.iterator]();
it.next() // {value:1,done:false}
it.next() // {value:2,done:false}
it.next() // {value:3,done:false}
it.next() // {done:true} value为当前的遍历值 done是一个布尔值,表示是否还有可以遍历的值
普通的对象如何实现for..of遍历(需要自己定义Symbol.iterator)
var myObject = {
a:2,
b:3
}
Object.defineProperty(myObject,Symbol.iterator,{
enumerable:false,
writable:false,
configurable:true,
value:function(){
var o = this;
var idx = 0;
var ks = Object.keys(o);
return {
next:function(){
return {
value:o[ks[idx++]],
done:(idx > ks.length)
}
}
}
}
})
// 手动遍历myObject
var it = myObject[Symbol.iterator]();
it.next(); // {value:2,done:false}
it.next();// {value:3,done:false}
it.next();// {value:undefined,done:true}
// 用for..of遍历myObject
for(var v of myObject){
console.log(v)
}