// 1 var x = 10; var foo = function() { var y = 20; var bar = function() { var z = 30; return x+y+z; } return bar; } var baz = foo(); console.log(baz()); // 60
x
, foo
and baz
are global, and therefore are available everywhere, regardless of the stack depth. At the moment of calling foo
, a frame with local variables y
and bar
appears at the top of the stack, then, when exiting foo
, this frame is lost, and at the time of calling baz
, only z
activated in the activation frame. Where does the interpreter get y
? In the C language (and all its descendants), this problem is solved very severely - the declaration of nested functions is prohibited. In Pascal, on the contrary, there are nested functions, but there is no possibility to return a function at the output. When people say that functions in imperative languages are not first-class objects, this is what they mean. Functional languages, on the other hand, allow you to do whatever you want with functions (the example above is real in JavaScript). How do they do it?bar
from the external function foo
, the stack frame created when foo
called is stored in the baz
variable as part of the execution context of the function bar
. Thus, a chain of scopes is formed that is completely independent of the call stack. The specific implementation of the mechanism for forming this chain may, of course, be very different from this simplified description, but the main thing is that the variable y
exists while the baz
exists, despite the fact that when exiting foo
reference to it disappears from the stack. // 2 var x = 'I am global!'; var foo = function (y) { var z = 'unchanged'; var getXYZ = function () { return 'x: '+x+' y: '+y+' z: '+z; } var setZ = function(newZ) { z = newZ; } return [getXYZ, setZ]; // , , ? } var a = foo('Alice'); // *1* var b = foo('Bob'); // *2* console.log(a[0]()); // x: I am global! y: Alice z: unchanged a[1]('changed'); // *3* console.log(a[0]()); // x: I am global! y: Alice z: changed *4* console.log(b[0]()); // x: I am global! y: Bob z: unchanged x = 'Everybody can see me!'; // *5* console.log(a[0]()); // x: Everybody can see me! y: Alice z: changed console.log(b[0]()); // x: Everybody can see me! y: Bob z: unchanged
foo
, since we returned two nested functions. They can communicate with each other through the variables y
and z
, which can be seen at the point * 3 *. At the point * 2 * we enter the function foo
a second time, and the tree grows up - copies of all local variables foo
. The “gray” getXYZ
and setZ
are also reported via y
and z
in the “gray” node foo
, but they know nothing about y
and z
from the “black” node foo
, which is clearly seen at * 4 *. At the same time, the variable x
level above is visible to all the leaves of the tree of visibility (* 5 *).foo('Alice')
or foo('Bob')
there is no way change the value of the variable y
outside) and, as long as the tree branches exist, we can control its state only insofar as the leaves allow it. // 3 var obj = { x: 10, y: 20, foo: function () {return x + y;} }; console.log(obj['foo']()); // !
x
and y
in the activation object (so called stack stack in JavaScript) of our foo()
function. There are no them and up the chain, there is only the variable obj
. To get to them, we will have to refer to them as obj['x']
and obj['y']
: // 4 var obj = { x: 10, y: 20, foo: function () {return obj['x'] + obj['y'];} } console.log(obj['foo']()); // 30
// 5 function createObj(x, y) { var obj = {}; obj['x'] = x, obj['y'] = y, obj['foo'] = function () {return obj['x'] + obj['y'];} return obj; } var obj1 = createObj(1, 2); var obj2 = createObj(3, 4); console.log(obj1['foo']()); // 3 console.log(obj2['foo']()); // 7
createObj()
function returns a nested function as part of the obj
object, each time it is called, a closure is created that contains copies of x
, y
and foo
that are independent of each other (the tree of scopes grows up). What we have done is already very similar on the full object. To note this matter, in the subsequent listings we will move to a more concise dot notation. But OOP is not OOP without inheritance. How to organize it? We could write a function that would copy all the properties of the parent object into a descendant object. Such inheritance is called cascading, but, strictly speaking, it is cloning rather than inheritance. Changes in the implementation of the parent will not affect the descendant; in addition, if each descendant contains copies of the parent's methods, this will lead to unnecessary memory consumption. Perhaps, it is better to simply store a reference to the parent in one of the properties of the child. We also need a function to search for properties up the inheritance chain: // 6 function createObj(x, y) { var obj = {}; obj.x = x, obj.y = y, obj.foo = function () {return obj.x + obj.y;} return obj; } function createChild (parent) { var child = {}; child.__parent__ = parent; return child; } function lookupProperty (obj, prop) { if (prop in obj) return obj[prop]; else if (obj.__parent__) return lookupProperty (obj.__parent__, prop); } var a = createObj(1, 2); var b = createChild (a); console.log(lookupProperty(b, 'y')); // 2 console.log(lookupProperty(b, 'foo')()); // 3
b
, for example like this: bx = 10
, then we will see that nothing really works. The foo()
method still refers to the properties of its object, not the descendant object. If we want to reuse methods during inheritance, we need to teach them to work with the properties of other objects. You can pass an argument to the method that points to the current object. It is also necessary to use the lookupProperty()
function inside the method, because we do not know in advance whether the x
and y
properties are defined in the current object, or whether they will have to be searched upwards through the inheritance chain. The functions createChild()
and lookupProperty()
remain unchanged: // 7 function createObj(x, y) { var obj = {}; obj.x = x, obj.y = y, obj.foo = function (currentObj) { return lookupProperty(currentObj, 'x') + lookupProperty(currentObj, 'y'); } return obj; } function createChild (parent) { var child = {}; child.__parent__ = parent; return child; } function lookupProperty (obj, prop) { if (prop in obj) return obj[prop]; else if (obj.__parent__) return lookupProperty (obj.__parent__, prop); } var a = createObj(1, 2); var b = createChild (a); bx = 10; console.log(lookupProperty(b, 'y')); // 2 console.log(lookupProperty(b, 'foo')(b)); // 12
lookupProperty()
should be made completely transparent by lookupProperty()
it inside the interpreter. We will not see her again, but we will remember that she is.createObj()
and createChild()
functions — they are quite similar. Both create a temporary object on entry and return it on exit. We include the combined function in the root Object
. It will take two arguments - the parent object and the object describing the differences between the descendant and the parent (this approach is also called differential or differential inheritance).this
, in accordance with the tradition of the PLO: // 8 var a = Object.create(null, { x: {value: 1}, y: {value: 2}, foo: {value: function() { return this.x + this.y; } } }); var b = Object.create(a, {x: {value: 10}}); console.log(b.x+', '+b.y+', '+b.foo()); // 10, 2, 12
new
. This scheme involves the use of constructors and prototypes. They say that Brendan Ike introduced it into the language, so as not to shock the programmers who are accustomed to classical inheritance with the simplicity and straightforwardness of the scheme described above. Perhaps a good smuggler would have come from Ike - he managed to push a functional language into mainstream programming, where the imperative languages had previously run the ball, disguised it with C-like syntax, and prototype inheritance, confusing it and making it look like a classical one.prototype
property that will point to the prototype of the created object in the case of a function being called as a constructor. Then, as in the transition to Listing 8, rename the temporary variables obj
, child
and currentObj
to this
, hide their declaration and return to the interpreter, remove the lookupProperty()
to the same lookupProperty()
. Since each constructor creates one specific type of objects, using a generic method like Object.create()
meaningless, so we will call constructors by the type of objects they create, but with a capital letter, so as not to be confused with ordinary functions. To let the interpreter know that we want to call a function as a constructor, we add the keyword new
in front of its name. Here's what we get: // 9 function A(x, y) { this.x = x, this.y = y, this.foo = function () { return this.x + this.y; } }; function B () {}; B.prototype = new A(1, 2); var b = new B(); console.log(b.x+', '+b.y+', '+b.foo()); // 1, 2, 3
B()+B.prototype
) plays the same role as the class in the classic OOP. Notice that in Listing 8, the a object serves solely to inherit b
from it, and in Listing 9, we have completely got rid of the variable a
, which means that we do not need the A()
constructor either. The x
and y
properties, which are different for each object, can be defined in the constructor B
, and the common for all method foo()
- in the prototype: // 10 function B (x, y) { this.x = x, this.y = y }; B.prototype.foo = function () { return this.x + this.y; } var b = new B(1, 2); console.log(b.x+', '+b.y+', '+b.foo()); // 1, 2, 3
Object.create()
: // 11 var B = { foo: function() { return this.x + this.y; } }; var b = Object.create(B, {x: {value: 10}, y: {value: 20}}); console.log(b.x+', '+b.y+', '+b.foo()); // 10, 20, 30
Source: https://habr.com/ru/post/125306/
All Articles