Bagaimana cara memeriksa apakah suatu objek adalah array?


2753

Saya mencoba menulis fungsi yang dapat menerima daftar string, atau string tunggal. Jika ini sebuah string, maka saya ingin mengonversinya menjadi sebuah array hanya dengan satu item, jadi saya dapat mengulanginya tanpa takut ada kesalahan.

Jadi bagaimana cara saya memeriksa apakah variabel adalah array?


Saya telah mengumpulkan berbagai solusi di bawah ini dan membuat tes jsperf . Mereka semua cepat, jadi gunakan saja Array.isArray- sekarang didukung dengan baik dan bekerja di seluruh bingkai .


6
Saya pikir Anda bermaksud untuk 'memeriksa apakah objek adalah array', tetapi Anda ingin memeriksa apakah 'objek adalah array dari string atau string tunggal' secara khusus. Tidak yakin jika Anda melihatnya? Atau hanya aku? Saya sedang memikirkan sesuatu yang lebih seperti ini ... apakah saya yang kehilangan sesuatu di sini?
rr1g0

149
TL; DR - arr.constructor === Arraytercepat.
Neta

3
jsben.ch/#/QgYAV - patokan untuk cara paling umum
EscapeNetscape

39
TL; DR - Array. isArray (arr) sejak ES5; dan $. isArray (arr) di jQuery.
Ondra Žižka

6
Hanya perlu diingat bahwa jika Anda dengan alasan apa pun menimpa konstruktor Anda melalui prototipe arr.constructor === Arraytes itu akan kembali salah. Array.isArray(arr)masih mengembalikan true.
ghaschel

Jawaban:


979

Di browser modern yang dapat Anda lakukan

Array.isArray(obj)

( Didukung oleh Chrome 5, Firefox 4.0, IE 9, Opera 10.5 dan Safari 5)

Untuk kompatibilitas mundur Anda dapat menambahkan yang berikut ini

# only implement if no native implementation is available
if (typeof Array.isArray === 'undefined') {
  Array.isArray = function(obj) {
    return Object.prototype.toString.call(obj) === '[object Array]';
  }
};

Jika Anda menggunakan jQuery, Anda dapat menggunakan jQuery.isArray(obj)atau $.isArray(obj). Jika Anda menggunakan garis bawah, Anda dapat menggunakan_.isArray(obj)

Jika Anda tidak perlu mendeteksi array yang dibuat dalam bingkai yang berbeda, Anda juga bisa menggunakannya instanceof

obj instanceof Array

9
Berikut adalah daftar browser yang lebih lengkap yang mendukungArray.isArray
lightswitch05

if (typeof Array.isArray === 'undefined') {dapat diubah menjadiif(!Array.isArray) {
iMatoria

Untuk apa nilainya Object.prototype.string.call(obj)dapat dipalsukan jika benda Symbol.toStringTagitu ada di atasnya. Yang mengatakan saya tidak mengetahui adanya lingkungan yang mengirim Symbol.toStringTagtetapi tidak Array.isArrayjadi ini tampaknya aman.
Benjamin Gruenbaum

5
Mengapa instanceof Arraygagal jika array berasal dari frame yang berbeda?
NobleUplift

16
@NobleUplift: instanceof Arraygagal jika array berasal dari frame yang berbeda karena setiap array dari frame yang berbeda memiliki Arraykonstruktor dan prototipe yang berbeda . Untuk alasan kompatibilitas / keamanan, setiap frame memiliki lingkungan globalnya sendiri, dan ini termasuk objek global. The Objectglobal dari satu frame berbeda dari Objectglobal dari yang lain. Begitu juga untuk Arrayglobal. Axel Rauschmayer berbicara lebih banyak tentang ini .
jschoi

1943

Metode yang diberikan dalam standar ECMAScript untuk menemukan kelas Objek adalah dengan menggunakan toStringmetode dari Object.prototype.

if( Object.prototype.toString.call( someVar ) === '[object Array]' ) {
    alert( 'Array!' );
}

Atau Anda dapat menggunakan typeofuntuk menguji apakah itu sebuah String:

if( typeof someVar === 'string' ) {
    someVar = [ someVar ];
}

Atau jika Anda tidak peduli dengan kinerja, Anda bisa melakukan concatke Array kosong yang baru.

someVar = [].concat( someVar );

Ada juga konstruktor yang dapat Anda query langsung:

if (somevar.constructor.name == "Array") {
    // do something
}

Lihat pengobatan menyeluruh dari blog @TJ Crowder , seperti yang diposting di komentarnya di bawah ini.

Lihatlah patokan ini untuk mendapatkan ide metode mana yang berkinerja lebih baik: http://jsben.ch/#/QgYAV

Dari @Bharath convert string to array menggunakan Es6 untuk pertanyaan yang diajukan:

const convertStringToArray = (object) => {
   return (typeof object === 'string') ? Array(object) : object 
}

seharusnya:

let m = 'bla'
let n = ['bla','Meow']
let y = convertStringToArray(m)
let z = convertStringToArray(n)
console.log('check y: '+JSON.stringify(y)) . // check y: ['bla']
console.log('check y: '+JSON.stringify(z)) . // check y: ['bla','Meow']

65
+1 Yup, toStringadalah salah satu cara untuk melakukannya. Saya melakukan sedikit pengumpulan di
TJ Crowder

3
typeof new String('beans') > 'object'
Ben

16
Jika Anda tidak ingin mengetikkan "[objek Array]" gunakan Object.prototype.toString.call (someVar) === Object.prototype.toString.call ([]) atau buat fungsi praktis untuk mendapatkan jenis jika Anda tidak ingin mengetik Object.prototype.toString.call
Pramod

13
Saya menggunakan vanilla Array.isArray yang bekerja di 'browser modern' (yaitu, IE9 + dan yang lainnya). Dan untuk dukungan browser lama, gunakan shim dari MDN developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
David Gilbertson

21
Hidup di dunia modern -Array.isArray(obj)
mcfedr

1274

Pertama-tama saya akan memeriksa apakah implementasi Anda mendukung isArray:

if (Array.isArray)
    return Array.isArray(v);

Anda juga dapat mencoba menggunakan instanceofoperator

v instanceof Array

127
v instanceof Arrayakan mengembalikan false jika vdibuat di bingkai lain ( vadalah instance dari thatFrame.contentWindow.Arraykelas).
pepkin88

47
Untuk lebih spesifik: Array.isArraydidefinisikan sebagai bagian dari ECMAScript 5 / Javascript 1.8.5.
jevon

8
Solusi yang benar-benar sederhana dan rapi TAPI isArray tidak kompatibel dengan beberapa browser yang lebih lama (ex IE7 dan IE8). Sumber: kangax.github.io/es5-compat-table/#
Wookie88

2
Bagaimana dengan: if (Array.isArray) mengembalikan Array.isArray (v); lain mengembalikan v instance dari Array;
lewdev

1
atau hanya:return (Array.isArray && Array.isArray(v)) || (v instanceof Array);
Stijn de Witt

298

jQuery juga menawarkan $.isArray()metode:

var a = ["A", "AA", "AAA"];

if($.isArray(a)) {
  alert("a is an array!");
} else {
  alert("a is not an array!");
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>


27
Sekedar catatan, jQuery menggunakan metode toString secara internal: Sumber GitHub
Jacob Squires

5
@ Jacobs Minta itu tergantung. Saya baru saja menguji di sini, jQuery terbaru di Chrome - $.isArray === Array.isArraykembali benar.
Renan

3
@Renan: Ini adalah hal yang baik tentang menggunakan jQuery. Biasanya menggunakan metode paling modern dan terbaik untuk melakukannya, dan Anda tidak harus melakukan semua fitur memeriksa diri Anda untuk mengetahui apa yang harus digunakan.
awe

jQuery menggunakan di Array.isArraybalik layar: github.com/jquery/jquery/blob/master/src/core.js#L211
Sergiu

1
Tapi tidak ada yang akan menggunakan jQuery JUST FOR fungsi ini, kan? Saya tidak akan hanya mengunduh jquery karena saya ingin memeriksa apakah ada array: p
Automagisch

106

Ini adalah yang tercepat di antara semua metode (semua browser didukung):

function isArray(obj){
    return !!obj && obj.constructor === Array;
}

2
Anda benar, itu yang tercepat menurut tes yang saya masukkan dalam pertanyaan.
mpen

5
Apakah ada kelemahan menggunakan metode ini? Tampaknya jauh lebih sederhana dan efektif daripada jawaban teratas yang diterima.
David Meza

@shinobi - hanya ingin tahu (dan saya sudah sering melihat ini) - mengapa Anda mengutarakan kondisinya if (obj && Array === obj.constructor)sebagai lawan if (obj && obj.constructor === Array)? Apakah itu hilang dalam terjemahan ke bahasa Inggris, lalu ke kode hal? misal, penutur bahasa Inggris umumnya cenderung bertanya "apakah objek itu ada, dan apakah konstruktornya berasal dari kelas array?", jadi kodenya mengalir ketika membacanya lebih logis. atau adakah alasan teknis?
tidak sinkron

function object_type(o){var t = typeof(o);return ((t==="object") && (o.constructor===Array)) ? "array" : t;} /*allows you to */ switch(object_type(o)){ case 'array': break; case 'object' : o.dosomething();}
tidak sinkron

3
@ Shinobi semuanya baik-baik saja. saya kira itu mungkin hangover dari kebiasaan c aman - jika Anda secara tidak sengaja menggunakan = bukan ==, itu tidak akan dikompilasi karena itu bukan variabel yang ditugaskan.
tidak sinkron

47

Bayangkan Anda memiliki array ini di bawah ini :

var arr = [1,2,3,4,5];

Javascript (browser baru dan lama):

function isArray(arr) {
  return arr.constructor.toString().indexOf("Array") > -1;
}

atau

function isArray(arr) {
  return arr instanceof Array;
}

atau

function isArray(arr) {
  return Object.prototype.toString.call(arr) === '[object Array]';
}

kemudian panggil seperti ini:

isArray(arr);

Javascript (IE9 +, Ch5 +, FF4 +, Saf5 +, Opera10.5 +)

Array.isArray(arr);

jQuery:

$.isArray(arr);

Sudut:

angular.isArray(arr);

Garis Bawah dan Lodash:

_.isArray(arr);

34

Array.isArray bekerja dengan cepat, tetapi tidak didukung oleh semua versi browser. Jadi Anda bisa membuat pengecualian untuk orang lain dan menggunakan metode universal:

    Utils = {};    
    Utils.isArray = ('isArray' in Array) ? 
        Array.isArray : 
        function (value) {
            return Object.prototype.toString.call(value) === '[object Array]';
        }

3
Anda harus mendapatkan .toString()metode dari Object.prototype. Saat ini Anda menggunakan window.toString(), yang tidak sama.
sistem

Kamu benar. window.toStringlakukan hal yang sama seperti Object.prototype.toStringdi Chrome.
CruorVult

isArray tidak cepat sama sekali. Ini adalah metode paling lambat.
jemiloii

Mengapa tidak menambahkannya ke Array daripada Utils? (Saya tahu Anda tidak ingin properti tambahan pada objek array baru tapi saya pikir itu hanya terjadi jika Anda menambahkan isArray ke Array.prototype.)
David Winiecki

27

Fungsi sederhana untuk memeriksa ini:

function isArray(object)
{
    return object.constructor === Array;
}

16
Saya akan mengurangi itu menjadi satu baris return object.constructor === Array- tetapi apakah Anda yakin ini hanya akan mengembalikan true untuk array?
mpen

12
Dapat melakukannya dengan semua ekspresi boolean. Membuat saya gila ketika saya melihat if(x) return true; else return false:-) Bahkan jika mundur, Anda harus meniadakan ekspresi.
mpen

3
Alasan ini tidak mengembalikan true untuk getElementsByTagName, adalah karena hasil dari fungsi itu sebenarnya HTMLCollection dan bukan array.
Yuval A.

6
Ini gagal parah jika objek tidak terdefinisi atau nol.
John Henckel

1
@JohnHenckel Lihat jawaban saya stackoverflow.com/a/29400289/34806 memperhitungkan baik perhatian Anda maupun komentar pertama, semuanya dalam satu baris
Dexygen

17

Seperti yang dikatakan MDN di sini :

gunakan Array.isArray atau Object.prototype.toString.call untuk membedakan objek biasa dari array

Seperti ini:

  • Object.prototype.toString.call(arr) === '[object Array]', atau

  • Array.isArray(arr)


17

Hanya ada satu solusi garis untuk pertanyaan ini

x instanceof Array

di mana x adalah variabel itu akan mengembalikan true jika x adalah array dan false jika bukan.


Jauh lebih bersih dan aman di masa depan! Ini atau typeofperbandingan.
ChristoKiwi

Apakah ini sesuatu yang menerima banyak dukungan browser? Saya suka itu.
Dan Zuzevich

1
Sayangnya, ini sebenarnya tidak berguna tanpa try / catch karena jika "x" adalah objek seperti {}array, maka Anda mendapatkan kesalahan sintaksis.
Abalter

15

Anda bisa memeriksa tipe variabel Anda apakah array dengan;

var myArray=[];

if(myArray instanceof Array)
{
....
}

1
Beberapa orang telah menyebutkan instanceof.. Saya pikir itu gagal di bawah beberapa skenario aneh.
mpen

15

Saya akan membuat fungsi untuk menguji jenis objek yang Anda hadapi ...

function whatAmI(me){ return Object.prototype.toString.call(me).split(/\W/)[2]; }

// tests
console.log(
  whatAmI(["aiming","@"]),
  whatAmI({living:4,breathing:4}),
  whatAmI(function(ing){ return ing+" to the global window" }),
  whatAmI("going to do with you?")
);

// output: Array Object Function String

maka Anda dapat menulis pernyataan sederhana jika ...

if(whatAmI(myVar) === "Array"){
    // do array stuff
} else { // could also check `if(whatAmI(myVar) === "String")` here to be sure
    // do string stuff
}

12

Saya melakukan ini dengan cara yang sangat sederhana. Bekerja untukku. Adakah kekurangannya?

Array.prototype.isArray = true;

a=[]; b={};
a.isArray  // true
b.isArray  // (undefined -> false)

7
tertipu oleh{isArray:true}
Bergi

JSON.parse(someDataFromElsewhere).items.isArraydapat mengembalikan true (tergantung pada data) dan memecahkan kode Anda.
Roy Tinker

12

Ini adalah upaya saya untuk memperbaiki jawaban ini dengan mempertimbangkan komentar:

var isArray = myArray && myArray.constructor === Array;

Ini menghilangkan if / else, dan menjelaskan kemungkinan array menjadi null atau tidak terdefinisi


konstruktor tidak tersedia di ES5
TechTurtle


11

Saya telah memperbarui biola jsperf dengan dua metode alternatif serta pengecekan kesalahan.

Ternyata metode yang mendefinisikan nilai konstan dalam prototipe 'Objek' dan 'Array' lebih cepat daripada metode lainnya. Ini adalah hasil yang agak mengejutkan.

/* Initialisation */
Object.prototype.isArray = function() {
  return false;
};
Array.prototype.isArray = function() {
  return true;
};
Object.prototype._isArray = false;
Array.prototype._isArray = true;

var arr = ["1", "2"];
var noarr = "1";

/* Method 1 (function) */
if (arr.isArray()) document.write("arr is an array according to function<br/>");
if (!noarr.isArray()) document.write("noarr is not an array according to function<br/>");
/* Method 2 (value) - **** FASTEST ***** */
if (arr._isArray) document.write("arr is an array according to member value<br/>");
if (!noarr._isArray) document.write("noarr is not an array according to member value<br/>");

Kedua metode ini tidak berfungsi jika variabel mengambil nilai yang tidak ditentukan, tetapi mereka berfungsi jika Anda yakin bahwa mereka memiliki nilai. Berkenaan dengan memeriksa dengan kinerja dalam pikiran jika suatu nilai adalah array atau nilai tunggal, metode kedua terlihat seperti metode cepat yang valid. Itu sedikit lebih cepat daripada 'instanceof' di Chrome, dua kali lebih cepat dari metode terbaik kedua di Internet Explorer, Opera dan Safari (di komputer saya).


9

Saya tahu, bahwa orang mencari semacam pendekatan javascript mentah. Tetapi jika Anda tidak ingin memikirkannya, silakan lihat di sini: http://underscorejs.org/#isArray

_.isArray(object) 

Mengembalikan nilai true jika objek adalah Array.

(function(){ return _.isArray(arguments); })();
=> false
_.isArray([1,2,3]);
=> true

7
"Kecuali jika tag lain untuk kerangka / pustaka juga disertakan, jawaban JavaScript murni diharapkan."
Michał Perłakowski

5

Solusi terbaik yang saya lihat adalah penggantian lintas-browser untuk typeof. Lihat solusi Angus Croll di sini .

Versi TL; DR ada di bawah ini, tetapi artikel ini adalah diskusi yang bagus tentang masalah ini sehingga Anda harus membacanya jika Anda punya waktu.

Object.toType = function(obj) {
    return ({}).toString.call(obj).match(/\s([a-z|A-Z]+)/)[1].toLowerCase();
}
// ... and usage:
Object.toType([1,2,3]); //"array" (all browsers)

// or to test...
var shouldBeAnArray = [1,2,3];
if(Object.toType(shouldBeAnArray) === 'array'){/* do stuff */};

5

Inilah pendekatan malas saya:

if (Array.prototype.array_ === undefined) {
  Array.prototype.array_ = true;
}

// ...

var test = [],
    wat = {};

console.log(test.array_ === true); // true
console.log(wat.array_ === true);  // false

Saya tahu itu penghinaan untuk "mengacaukan" prototipe, tetapi tampaknya berperforma lebih baik daripada toStringmetode yang direkomendasikan .

Catatan: Jebakan dari pendekatan ini adalah bahwa ia tidak akan bekerja melintasi iframebatas , tetapi untuk kasus penggunaan saya ini bukan masalah.


itu tidak lebih baik dalam hal kinerja lagi, setidaknya pada FF30 di Ubuntu 64-bit
test30

2
tertipu oleh wat = {array_: true}benda.
Bergi

@Bergi: Ya, itu harus jelas. Jika Anda mengatur obj.array_ = true, maka Anda hanya membodohi diri sendiri .
namuol

@namuol: Saya sendiri tidak perlu membodohi diri sendiri. Seringkali benda yang cukup digunakan sebagai kamus. Pikirkan sebuah cacheobjek untuk memoize hasil pencarian yang menggunakan string pencarian sebagai kunci properti. Bagaimana jika pengguna mencari array_? Apakah objek Anda menjadi array karena itu? Itu hanya bug.
Bergi

@namuol: Juga, pendekatan ini mengharuskan semua pihak yang terlibat (termasuk perpustakaan yang digunakan) dapat menyetujui yang .array_digunakan untuk menandai array. Itu benar-benar tidak terjadi di sini, .arraybisa berarti apa saja. Anda setidaknya harus menggunakan string deskriptif, dan menandakan ketidaksesuaian penggunaan sewenang-wenang, misalnya dengan .__isArray = true.
Bergi

5

Ada contoh yang bagus dalam buku Pola Pola Stoyan Stefanov JavaScript yang seharusnya menangani semua masalah yang mungkin terjadi serta memanfaatkan metode ECMAScript 5 Array.isArray () .

Jadi begini:

if (typeof Array.isArray === "undefined") {
    Array.isArray = function (arg) {
        return Object.prototype.toString.call(arg) === "[object Array]";
    };
}

Omong-omong, jika Anda menggunakan jQuery, Anda dapat menggunakan metode itu $ .isArray ()


2
+1: mengapa tidak sekadar sederhana if(!Array.isArray) {...?
Marco Demaio

5

cara termudah dan tercepat untuk memeriksa apakah Obyek adalah Array atau tidak.

 var arr = [];
  arr.constructor.name ==='Array'  //return true;

atau

arr.constructor ===Array //return true;

atau Anda dapat membuat fungsi utilitas:

function isArray(obj){ return obj && obj.constructor ===Array}

pemakaian:

isArray(arr); //return true

5

Berikut ini dapat digunakan jika Anda tahu bahwa objek Anda tidak memiliki metode concat.

var arr = [];
if (typeof arr.concat === 'function') {
    console.log("It's an array");
}


Ini adalah trik yang bagus, tetapi bisa diganti ... tetapi sebagian besar waktu akan mendapatkan hasilnya
Alireza

5

Anda bisa adalah metode isArray tapi saya lebih suka memeriksanya

Object.getPrototypeOf(yourvariable) === Array.prototype


Mengapa Anda lebih suka ini?
buka

@mpen Object.getPrototypeOf(yourvariable)itu mengembalikan prototipe objek Array. Dan kodenya tercepat dan aman untuk diandalkan.
STEEL

1
Mudah digagalkan : pastebin.com/MP8d5bCE Juga, apakah Anda memiliki tes kinerja untuk mendukung klaim "tercepat" Anda?
mpen

4

Jika hanya dua jenis nilai yang bisa diteruskan ke fungsi ini adalah string atau array string, tetap sederhana dan gunakan tanda typeofcentang untuk kemungkinan string:

function someFunc(arg) {
    var arr = (typeof arg == "string") ? [arg] : arg;
}

Ya ... itu akan berhasil untuk skenario ini, tetapi tidak secara umum. Akhirnya tetap menggunakan varargs. :)
mpen

4
A = [1,2,3]
console.log(A.map==[].map)

Mencari versi terpendek di sini adalah apa yang saya dapatkan sejauh ini.

Catatan, tidak ada fungsi sempurna yang akan selalu mendeteksi semua kemungkinan kombinasi. Lebih baik mengetahui semua kemampuan dan keterbatasan alat Anda daripada mengharapkan alat ajaib.


1
sedikit derivasi milikku A.map !== undefinedtapi ya, itu bisa jadi jalan licin di dunia patchers monyet;)
dmi3y

FYI: Ini tidak berfungsi di iFrames ( stackoverflow.com/questions/460256/… )
WiredPrairie

4
function isArray(value) {
    if (value) {
        if (typeof value === 'object') {
            return (Object.prototype.toString.call(value) == '[object Array]')
        }
    }
    return false;
}

var ar = ["ff","tt"]
alert(isArray(ar))

4

Fungsi sederhana untuk menguji apakah nilai input adalah array adalah sebagai berikut:

function isArray(value)
{
  return Object.prototype.toString.call(value) === '[object Array]';
}

Ini berfungsi lintas browser, dan dengan browser yang lebih lama. Ini ditarik dari posting blog TJ Crowders


4

Anda dapat mencoba ini:

var arr = []; (or) arr = new Array();
var obj = {}; (or) arr = new Object();

arr.constructor.prototype.hasOwnProperty('push') //true

obj.constructor.prototype.hasOwnProperty('push') // false

4

Fungsi ini akan mengubah hampir semua hal menjadi array:

function arr(x) {
    if(x === null || x === undefined) {
        return [];
    }
    if(Array.isArray(x)) {
        return x;
    }
    if(isString(x) || isNumber(x)) {
        return [x];
    }
    if(x[Symbol.iterator] !== undefined || x.length !== undefined) {
        return Array.from(x);
    }
    return [x];
}

function isString(x) {
    return Object.prototype.toString.call(x) === "[object String]"
}

function isNumber(x) {
    return Object.prototype.toString.call(x) === "[object Number]"
}

Ini menggunakan beberapa fitur browser yang lebih baru sehingga Anda mungkin ingin mengisi ini untuk dukungan maksimal.

Contoh:

> arr(null);
[]
> arr(undefined)
[]
> arr(3.14)
[ 3.14 ]
> arr(1/0)
[ Infinity ]
> gen = function*() { yield 1; yield 2; yield 3; }
[Function: gen]
> arr(gen())
[ 1, 2, 3 ]
> arr([4,5,6])
[ 4, 5, 6 ]
> arr("foo")
[ 'foo' ]

String NB akan dikonversi menjadi array dengan elemen tunggal, bukan array karakter. HapusisString centang jika Anda lebih suka sebaliknya.

Saya sudah menggunakan di Array.isArraysini karena ini yang paling kuat dan juga paling sederhana.


4

Dalam kasus Anda, Anda dapat menggunakan concatmetode Array yang dapat menerima objek tunggal serta array (dan bahkan digabungkan):

function myFunc(stringOrArray)
{
  var arr = [].concat(stringOrArray);

  console.log(arr);

  arr.forEach(function(item, i)
  {
    console.log(i, "=", item);
  })
}

myFunc("one string");

myFunc(["one string", "second", "third"]);

concat tampaknya menjadi salah satu metode tertua Array (bahkan IE 5.5 tahu dengan baik).

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.