JavaScript closure: difference between call and apply as well as use them to create inheritance

Resume

Today, in an interview, speaking of closure, the interviewer asked me about the difference between call() and apply(). And I didn’t use them before. After searching for a while, I found that we can implement completely a class with JavaScript. We can implement a constructor by using call() and apply(). I thought that we can only implement a class without any inheritance before. This passage will describe what I have found.


call()

Syntax:
function.call(thisArg, arg1, arg2, ...)


Suppose we have a function like this,

var lang = {'languages':['Python', 'Haskell', 'C++', 'Java']};
var favoriteLanguages = function(){
    for(var i = 0; i < this.languages.length; i++){
        console.log('My favorite languages is', this.languages[i]);
    }
}
favoriteLanguages.call(lang);

and it will print

My favorite languages is Python
My favorite languages is Haskell
My favorite languages is C++
My favorite languages is Java

on console.


So, consequently we can say that function.call() will apply arguments to function as local variable.


apply()

Syntax:
fun.apply(thisArg, [argsArray])



Actually it’s quite simular to call(). The only difference is we pass arguments call() while we pass an argument and a list of argument to apply().
Moreover, usually we pass this as the first argument to apply.
It’s all the same when we do:

var lang = {'languages':['Python', 'Haskell', 'C++', 'Java']};
var favoriteLanguages = function(){
    for(var i = 0; i < this.languages.length; i++){
        console.log('My favorite languages is', this.languages[i]);
    }
}
favoriteLanguages.applu(lang);//We changed here.



But when we try to do something different, there will be some differences.

var languages= {'prog_languages':['Python', 'Haskell', 'C++', 'Java'],'languages':['English', 'Français', '中文']};

var prog_l = function(){
    for(var i = 0; i < this.prog_languages.length; i++){
        console.log('I like', this.prog_languages[i], 'as a programming language.');
    }
}

var lang = function(){
    for(var i = 0; i < this.languages.length; i++){
        console.log('I like', this.languages[i], 'as a language.');
    }
}

var dispatch = function(method0, args0, method1, args1){
    method0.apply(args0);
    method1.apply(args1);
};

dispatch(lang,languages,prog_l,languages);

When we run codes above, it will show

I like English as a language.
I like Français as a language.
I like 中文 as a language.
I like Python as a programming language.
I like Haskell as a programming language.
I like C++ as a programming language.
I like Java as a programming language

in console.


It means that when we are not certain how many parameters we will pass, it’s better to use apply() than call(). Because apply() put the first argument into this and take the send argument which is a list as arguments applying to function.


Inheritance

Actually it’s a little bit like the object principle in most programming languages such as Java and C++.
Let’s see an example on mozilla.org.

function Product(name, price) {
    this.name = name;
    this.price = price;
    if (price < 0) {
        throw RangeError('Cannot create product ' + this.name + ' with a negative price');
    }
}

function Food(name, price) {
    Product.call(this, name, price);
    this.category = 'food';
}

function Toy(name, price) {
    Product.call(this, name, price);
    this.category = 'toy';
}

var cheese = new Food('feta', 5);
var fun = new Toy('robot', 40);

console.log(cheese.price);

When we run these codes, it will return 5 which is exactly the price of cheese.
But why?
When we instantiate Food, then we jump into the function Food(name, price), then we invoke the function Product.call(this, name, price) and pass this to Product(name, price). In Product(name, price), actually we are modifying Food, because we passed this in Product.call(this, name, price). So that this points to object Food. And we assign name and price to this(which is the reference of Food).
So, it’s the object Food which is being modified and finally we have name, price and category in Food.


Pretty like in Java, isn’t it?
In Java, we may write

class Product{
    protected String name;
    protected int price;
    public Product(String name, int price){
        this.name = name;
        this.price = price;
        if (price < 0) {
            throw new RangeError('Cannot create product ' + this.name + ' with a negative price'); //RangeError is not defined, because it's not important in this case.
        }
    }
}

and

class Food extends Product{
    protected String category = "food";
    public Food(String name, int price){
        super(name, price);
    }
}

References

發佈了72 篇原創文章 · 獲贊 14 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章