d3js и функция первого класса
Передача функции в качестве аргумента другим функциям - трудная для понимания концепция для новичков, особенно для тех, кто использует другие языки. D3 еще больше усугубляет эту трудность, делая модель функций-аргументов неотъемлемой частью основного шаблона, но не заставляет проектировщика понимать, что, черт возьми, происходит, когда они вводят функцию в середине их код.
Вместо того, чтобы изучать, что делает основной код, некоторые просто слепо набирают…
// figure 4.1 // ... .append('rect') .attr('fill', function(d,i){ return 'brown' }) .attr('stroke', function(d,i){ return 'none' })
… Даже когда в этом явно нет необходимости. Клянусь, код, который выглядит так, как будто он написан каждый день начинающими и опытными разработчиками d3, потому что, хотя он явно не взрывает их код, он все равно работает. Это работает.
В этой статье мы постараемся убедиться, что это больше не повторится. Мы рассмотрим, что делает d3, когда вы передаете функцию вместо значения. К концу вы лучше поймете чудесно полезную часть javascript.
примечание: эту статью лучше всего читать в исходной уценке на моем репозитории Github
функция (г, я) {вернуть г; }
Почти весь код D3 принимает такую форму:
// figure 4.2 var p = [ 3, 1, 4, 1, 5] var svg = d3.select('body').append('svg') var elements = svg.selectAll('rect#foo') .data(p) .enter() .append('rect') .attr('id', 'zomg') .attr('x', function(d){ return (d*10.0) // what the heck is this? }) .attr('y',0).attr('width',4).attr('height',200)
В приведенном выше коде мы создаем элемент svg. Затем добавьте к svg несколько элементов rect.
При создании этого элемента rect мы устанавливаем для атрибута id строку zomg. ‘zomg’ - это значение, например 0 или true. Это значение передается в качестве второго аргумента в вызове attr ().
В следующей строке кода, где мы вызываем attr и устанавливаем атрибут x, вместо передачи значения, такого как число 108.1, я передаю функция как аргумент.
attr (имя, значение)
attr (имя, функция () {возвращаемое значение})
как правило
// figure 4.3 // this is normal-ish looking code function add(a,b){ return (a+b) } var a = add(2,2) // a = 4 (2 + 2) var b = add(8,a) // b = 12 (4 + 8)
Пишу функцию добавить. Затем я использую его, чтобы складывать вещи вместе.
// figure 4.4 // this is a little different function add(a,b){ return a() + b() } // first argument second argument var a = add(function(){ return 3 }, function(){ return 1 }) // a = 4
Приведенный выше код странный. Зачем тебе вообще это нужно?
На самом деле нет смысла передавать эти функции в качестве аргументов, я просто делаю это, чтобы показать, что вы можете. Функции, которые я передаю, всегда возвращают одно и то же значение. Все, кроме 3 в этом первом аргументе, излишне.
Возможность передать функцию в качестве аргумента становится важной, когда вы делаете больше, чем просто return 3 в передаваемой функции. Что, если «функция аргумента» имеет сложное поведение, например, вызывает сервер, чтобы проверить, аутентифицирован ли пользователь?
Возможность передавать функцию в качестве аргумента другим функциям упрощает разделение задач. Это должно помочь вам создавать ваши программы более удобными для сопровождения.
Вы также, вероятно, уже делаете это, не осознавая этого.
// figure 4.5 // common jquery event code $('#foo').click(function(){ $('#bar').hide() })
Приведенный выше код присоединяет обработчик событий к элементам DOM. Функция передается в качестве аргумента функции click, эта «функция аргумента» будет запускаться всякий раз, когда событие click происходит в элементе DOM с идентификатором foo.
// figure 4.6 // this is a lot different function adder_maker(a){ var c = a return function d(b){ return c() + b } } var random_adder = adder_maker( function(){ return Math.random() } // a, in adder_maker ) random_adder(1) // 1.1 random_adder(1) // 1.93 random_adder(1) // 1.03 var static_adder = adder_maker ( function(){ return 4 } ) static_adder(1) // 5 static_adder(3) // 7
Приведенный выше код еще более странный. Функция, которую вы передаете в качестве аргумента a, получает переменную c. Затем возвращается еще одна функция d, которая оценивает c и добавляет то, что возвращается из c к b, и верните все это наконец.
Здесь мы назначаем возвращенную функцию d переменной random_adder.
Теперь, когда вы вызываете random_adder много раз, вы должны каждый раз получать новый номер. Это связано с тем, что Math.random () заново вычисляется при каждом вызове random_adder, поскольку ссылка на функцию a сохраняется как c и возвращается в функции d, которую мы назначаем переменной random_adder.
назад к d3
В первом примере…
// ... .attr('x', function(d,i){ return (d*10.0) // what the heck is this? }) // ...
Почему мы передаем функцию второму аргументу attr? Эта функция, которую вы передаете в качестве аргумента, сохраняется и прикрепляется к элементу rect. (см. соответствующий исходный код d3).
Почему функция сохраняется и прикрепляется к элементу rect? Функция запускается для каждого элемента в массиве, который вы передали в data ().
// figure 4.2 (again) var p = [ 3, 1, 4, 1, 5] var svg = d3.select('body').append('svg') var elements = svg.selectAll('rect#foo') .data(p) .enter() .append('rect') .attr('id', 'zomg') .attr('x', function(d){ return (d*10.0) // what the heck is this? }) .attr('y',0).attr('width',4).attr('height',200)
Поскольку у вас есть 5 элементов в массиве p, вы добавите 5 элементов rect с идентификатором foo. По одному для каждого элемента в массиве p.
Для каждого элемента rect, добавляемого к svg, анонимная встроенная функция обратного вызова, переданная в качестве второго аргумента функции attr, запускается, когда тег Атрибут x устанавливается для каждого прямоугольника. Каждый раз, когда эта функция запускается, элементы массива p передаются в качестве аргументов анонимной функции, и все, что возвращает эта функция, определяет значение атрибута x.
// figure 4.7 // example // first rect is created function(3){ // run the function, pass in p[0] return (3*10) // 30 } // second rect is created function(1){ // run the function, pass in p[1] return (1*10) // 10 } // third rect is created function(4){ // run the function, pass in p[2] return (4*10) // 40 } // fourth rect is created function(1){ // run the function, pass in p[3] return (1*10) // 10 } // fifth rect is created function(5){ // run the function, pass in p[4] return (5*10) // 50 }
Ниже приведены результаты выполнения кода из Рисунка 4.2.
// figure 4.8 <svg> <rect id='zomg' x=30 y=0 width=4 height=200></rect> <rect id='zomg' x=10 y=0 width=4 height=200></rect> <rect id='zomg' x=40 y=0 width=4 height=200></rect> <rect id='zomg' x=10 y=0 width=4 height=200></rect> <rect id='zomg' x=50 y=0 width=4 height=200></rect> </svg>
зачем мучиться с этим?
более легкие переходы
Если вы следуете шаблону во время цикла обновление ›переход, вы можете просто снова вызвать data, передать новый массив, вызвать переход, а затем список атрибутов.
// figure 4.9 // update the bound data elements.data([ 1, 2, 3, 6, 8]) elements.transition() .delay(1000) .duration(3000) .attr('x', function(d) { return (d*10.0) })
Теперь все элементы будут иметь обновленные атрибуты x и перейдут на новые позиции на экране. Код function (d) {return (d * 10.0)} будет снова вычислен для каждого элемента в массиве data.
// figure 4.10 // new x attributes from new data array [1,2,3,6,8] <svg> // \/ here <rect id='zomg' x=10 y=0 width=4 height=200></rect> <rect id='zomg' x=20 y=0 width=4 height=200></rect> <rect id='zomg' x=30 y=0 width=4 height=200></rect> <rect id='zomg' x=60 y=0 width=4 height=200></rect> <rect id='zomg' x=80 y=0 width=4 height=200></rect> </svg>
лучшее понимание javascript в целом
Если вы уже понимали функции как аргументы, присвоение функций значениям и общую идею функции как первоклассного гражданина, то мне очень жаль, если я разговаривал с вами свысока во время этой статьи.
Большинство программистов d3 начинают в мире, где это не является обычным шаблоном программирования. Обратные вызовы обычно являются областью сетевых операций и других операций ввода-вывода. Если вы полностью понимали шаблон обратного вызова javascript до того, как начали с D3, то у вас было на одно препятствие меньше на кажущейся крутой кривой обучения D3.
копировальная паста
window.onload = function () {… легко написать, и он работает, даже если вы не знаете, что он делает. Вам не нужно знать, что вы назначаете анонимную функцию обратного вызова обработчику событий окна. Ваш код, заключенный в квадратные скобки {}, будет работать нормально.
С другой стороны, вы не сможете эффективно использовать D3, если не знаете, когда передать обратный вызов, а когда просто передать значение. D3 также станет минным полем разочарования, если вы используете привязку данных, но не понимаете, что данные, которые вы связываете, позже используются в качестве аргумента функции обратного вызова, которую вы также пишете.