javascript

Javascript – This & Object prototypes

Every line of JavaScript code is run in an “execution context.” The JavaScript runtime environment maintains a stack of these contexts, and the top execution context on this stack is the one that’s actively running.

There are three types of executable code: Global code, function code, and eval code. Roughly speaking, global code is code at the top level of your program that’s not inside any functions, function code is code that’s inside the body of a function, and eval code is global code evaluated by a call to eval.

The object that this refers to is redetermined every time control enters a new execution context and remains fixed until control shifts to a different context. The value of this is dependent upon two things: The type of code being executed (i.e., global, function, or eval) and the caller of that code.

The first rule is simple: this refers to the global object in all global code. Since all programs start by executing global code, and this is fixed inside of a given execution context, we know that, by default, this is the global object.

What happens when control shifts to a new execution context? There are only three cases where the value of this changes: method invocations, functions called with the newoperator, and functions called using call and apply. I’ll explain each of these in turn.

Method invocations

If we call a function as a property of an object using either dot (i.e., obj.foo()) or bracket (i.e., obj["foo"]()) notation, this will refer to the parent object in the body of the function:

var info = {                    | var info = {
  name: 'Ahmed',                |    name: 'Ahmed,
  sayName: function (){         |    sayName: function (){
     var name = 'test';         |      var name = 'test;
     return name;               |      return this.name; // return parent
  }                             |    }
};                              | };
console.log(info.sayName()); 
// 'test'                       | console.log(info.sayName()); // 'Ahmed'

This is our second rule: this refers to the parent object inside function code if the function is called as a property of the parent.

The new operator

Any JavaScript function can be used as a constructor function with new. The new operator creates a new object and sets this to the new object inside the function it was called with. For example:

function Employee (name){          | function Employee (name){
  this.name = name;                |   this.name = name;
  this.getName = function (){      |   this.getName = function (){
   var name = 'test';              |    var name = 'test';
   return name;                    |    return this.name;
 };                                |    };
}                                  | }
var emp = new Employee("Ahmed");   | var emp = new Employee("Ahmed");
console.log(emp.getName()); 
// -- 'test'                       | console.log(emp.getName());// Ahmed

another Example:

function F (v) {
  this.val = v;
}
var f = new F("Woohoo!");
console.log(f.val); // Woohoo!
console.log(val);   // ReferenceError

This leads to our third rule: this in function code invoked using the new operator refers to the newly created object.

Note that there’s nothing special about F. If we call it without using new, this will refer to the global object:

var f = F("Oops!");
console.log(f.val); // undefined
console.log(val); // Oops!

In this case, F("Oops!") is a regular function call, and this doesn’t get set to a new object, because no new object is created since the new operator isn’t used. this remains set to the global object.

Call and apply

All JavaScript functions have two methods, call and apply, which let you call functions and explicitly set the value of this. The apply method takes two arguments: an object to set this to, and an (optional) array of arguments to pass to the function:

var add = function (x, y) {
      this.val = x + y;
    },
    obj = {
      val: 0
    };
add.apply(obj, [2, 8]);
console.log(obj.val); // 10

The call method works exactly the same as apply, but you pass the arguments individually rather than in an array:

add.call(obj, 2, 8);
console.log(obj.val); // 10

This is our fourth rule: this is set to the first argument passed to call or apply inside function code when that function is called with either call or apply


Examples:

function foo() {
 console.log( this.a );
}
var obj = {
 a: 2,
 foo: foo
};
var bar = obj.foo;       // function reference/alias!
var a = "oops, global";  // `a` also property on global object.
bar(); // "oops, global" // rule number 1 applied. 


function foo() {
 console.log( this.a );
}
var obj = {
 a: 2,
 foo: foo
};
var a = "oops, global"; // `a` also property on global object
setTimeout( obj.foo, 100 ); // "oops, global"

Think about this crude theoretical pseudo-implementation of 
`setTimeout()` provided as a built-in from the JavaScript environment:
```js
function setTimeout(fn,delay) {
 // wait (somehow) for `delay` milliseconds
 fn(); // <-- call-site!  // rule number one.
}

Summary
That’s it! You can figure out what object this refers to by following a few simple rules:

  • By default, this refers to the global object.
  • When a function is called as a property on a parent object, this refers to the parent object inside that function.
  • When a function is called with the new operator, this refers to the newly created object inside that function.
  • When a function is called using call or apply, this refers to the first argument passed to call or apply. If the first argument is null or not an object, this refers to the global object.

If you understand and follow those four rules, you will always know what this is.


Objects

Objects come in two forms: the declarative (literal) form, and the constructed form. The literal syntax for an object looks like this:

 var myObj = {
 key: value
 // ...
};

The constructed form looks like this:

var myObj = new Object();
myObj.key = value;

var name = "Ahmed Yehia";
console.log(typeof(name)); // string

so how can we use string like object and call methods like .length ??

we call a property or method on a string primitive, and the engine automatically coerces it to a `String` object, so that the property/method access works.

Arrays *are* objects, so even though each index is a positive integer, you can *also* add properties onto the array:

var myArray = [ "foo", 42, "bar" ];
myArray.baz = "baz";
myArray.length; // 3
myArray.baz;    // "baz"

notice that adding named properties (regardless of `.` or `[ ]` operator syntax) does not change the reported `length` of the array.


if we want to copy object to another object we need to know an important thing is that this will copy object by reference so when we change one of them the change will reflect the another one.

var info = {
 name: "Ahmed",
 age: 24
};
var foo = info;
foo.name = "omar";

console.log(info); // the same as foo
console.log(foo);  // the same as info

One subset solution is that objects which are JSON-safe (that is, can be serialized to a JSON string and then re-parsed to an object with the same structure and values) can easily be *duplicated* with:

var foo = JSON.parse( JSON.stringify( info ) );

Property Descriptors

var myObject = {
 a: 2
};

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

As you can see, the property descriptor (called a data descriptor since it’s only for holding a data value) for our normal object property ‘a’ is much more than just its value of 2. It includes 3 other characteristics: writableenumerable, and configurable.

While we can see what the default values for the property descriptor characteristics are when we create a normal property, we can use Object.defineProperty(..) to add a new property, or modify an existing one (if it’s configurable !), with the desired characteristics.

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

Using defineProperty(..), we added the plain, normal `a` property to `myObject` in a manually explicit way. However, you generally wouldn’t use this manual approach unless you wanted to modify one of the descriptor characteristics from its normal behavior.


Writable

The ability for you to change the value of a property is controlled by `writable`.

var myObject = {};
Object.defineProperty( myObject, "a", {
 value: 2,
writable: false,         // not writable!
 configurable: true,
 enumerable: true
} );
myObject.a = 3;
console.log(myObject.a);  // 2

As you can see, our modification of the `value` silently failed. If we try in `strict mode`, we get an error

"use strict";
var myObject = {};
Object.defineProperty( myObject, "a", {
 value: 2,
 writable: false, // not writable!
 configurable: true,
 enumerable: true
});
myObject.a = 3; // TypeError

The `TypeError` tells us we cannot change a non-writable property.

**Note:** We will discuss getters/setters shortly, but briefly, you can observe that writable: false means a value cannot be changed, which is somewhat equivalent to if you defined a no-op setter. Actually, your no-op setter would need to throw a `TypeError` when called, to be truly conformant to `writable:false`.


Configurable 

As long as a property is currently configurable, we can modify its descriptor definition, using the same `defineProperty(..)` utility.

var myObject = {
 a: 2
};
myObject.a = 3;
myObject.a; // 3

Object.defineProperty( myObject, "a", {
 value: 4,
 writable: true,
configurable: false, // not configurable!
 enumerable: true
} );

myObject.a; // 4
myObject.a = 5;
myObject.a; // 5

Object.defineProperty( myObject, "a", {
 value: 6,
 writable: true,
 configurable: true,
 enumerable: true
} ); // TypeError

Another thing `configurable:false` prevents is the ability to use the `delete` operator to remove an existing property.

var myObject = {
 a: 2
};

myObject.a; // 2
delete myObject.a;
myObject.a; // undefined

Object.defineProperty( myObject, "a", {
 value: 2,
 writable: true,
 configurable: false,
 enumerable: true
} );

myObject.a; // 2
delete myObject.a;
myObject.a; // 2

As you can see, the last `delete` call failed (silently) because we made the `a` property non-configurable.


Enumerable

The final descriptor characteristic we will mention here (there are two others, which we deal with shortly when we discuss getter/setters) is `enumerable`.

The name probably makes it obvious, but this characteristic controls if a property will show up in certain object-property enumerations, such as the `for..in` loop. Set to `false` to keep it from showing up in such enumerations, even though it’s still completely accessible. Set to `true` to keep it present.


Object Constant

By combining `writable:false` and `configurable:false`, you can essentially create a *constant* (cannot be changed, redefined or deleted) as an object property, like:

var myObject = {};

Object.defineProperty( myObject, "FAVORITE_NUMBER", {
 value: 42,
 writable: false,
 configurable: false
} );

Prevent Extensions

If you want to prevent an object from having new properties added to it, but otherwise leave the rest of the object’s properties alone, call `Object.preventExtensions(..)`:

var myObject = {
 a: 2
};
Object.preventExtensions( myObject );

myObject.b = 3;
myObject.b; // undefined

In non-strict mode, the creation of `b` fails silently. In `strict mode`, it throws a TypeError.


Seal

‘Object.seal(..)` creates a “sealed” object, which means it takes an existing object and essentially calls `Object.preventExtensions(..)` on it, but also marks all its existing properties as `configurable:false`. So, not only can you not add any more properties, but you also cannot reconfigure or delete any existing properties (though you *can* still modify their values)


Freeze
`Object.freeze(..)` creates a frozen object, which means it takes an existing object and essentially calls `Object.seal(..)` on it, but it also marks all “data accessor” properties as `writable:false`, so that their values cannot be changed.


Existence

var myObject = {
 a: 2
};

("a" in myObject); // true
("b" in myObject); // false

myObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "b" ); // false

 

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s