Browse Source

Implemented template-backed objects and used them for most of the built-ins. Closes #524, closes #459.

Dmitry Panov 2 years ago
parent
commit
9410bcaa81
40 changed files with 2218 additions and 1309 deletions
  1. 13 1
      array.go
  2. 13 1
      array_sparse.go
  3. 120 81
      builtin_array.go
  4. 16 0
      builtin_arrray_test.go
  5. 22 7
      builtin_boolean.go
  6. 118 75
      builtin_date.go
  7. 96 43
      builtin_error.go
  8. 90 24
      builtin_function.go
  9. 79 22
      builtin_global.go
  10. 14 10
      builtin_json.go
  11. 30 19
      builtin_map.go
  12. 74 59
      builtin_math.go
  13. 114 31
      builtin_number.go
  14. 114 42
      builtin_object.go
  15. 46 33
      builtin_promise.go
  16. 11 6
      builtin_proxy.go
  17. 21 15
      builtin_reflect.go
  18. 75 60
      builtin_regexp.go
  19. 28 15
      builtin_set.go
  20. 124 59
      builtin_string.go
  21. 26 14
      builtin_symbol.go
  22. 337 156
      builtin_typedarrays.go
  23. 23 21
      builtin_weakmap.go
  24. 21 8
      builtin_weakset.go
  25. 10 6
      func.go
  26. 3 3
      object.go
  27. 1 1
      object_dynamic.go
  28. 1 1
      object_goarray_reflect.go
  29. 5 5
      object_goreflect.go
  30. 1 1
      object_goslice.go
  31. 0 314
      object_lazy.go
  32. 469 0
      object_template.go
  33. 66 148
      runtime.go
  34. 7 0
      runtime_test.go
  35. 2 2
      string_ascii.go
  36. 1 1
      string_imported.go
  37. 2 2
      string_unicode.go
  38. 3 1
      typedarrays.go
  39. 7 7
      value.go
  40. 15 15
      vm.go

+ 13 - 1
array.go

@@ -332,6 +332,18 @@ func (a *arrayObject) hasOwnPropertyIdx(idx valueInt) bool {
 	return a.baseObject.hasOwnPropertyStr(idx.string())
 }
 
+func (a *arrayObject) hasPropertyIdx(idx valueInt) bool {
+	if a.hasOwnPropertyIdx(idx) {
+		return true
+	}
+
+	if a.prototype != nil {
+		return a.prototype.self.hasPropertyIdx(idx)
+	}
+
+	return false
+}
+
 func (a *arrayObject) expand(idx uint32) bool {
 	targetLen := idx + 1
 	if targetLen > uint32(len(a.values)) {
@@ -509,7 +521,7 @@ func (a *arrayObject) exportType() reflect.Type {
 
 func (a *arrayObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
 	r := a.val.runtime
-	if iter := a.getSym(SymIterator, nil); iter == r.global.arrayValues || iter == nil {
+	if iter := a.getSym(SymIterator, nil); iter == r.getArrayValues() || iter == nil {
 		l := toIntStrict(int64(a.length))
 		if typ.Kind() == reflect.Array {
 			if dst.Len() != l {

+ 13 - 1
array_sparse.go

@@ -302,6 +302,18 @@ func (a *sparseArrayObject) hasOwnPropertyIdx(idx valueInt) bool {
 	return a.baseObject.hasOwnPropertyStr(idx.string())
 }
 
+func (a *sparseArrayObject) hasPropertyIdx(idx valueInt) bool {
+	if a.hasOwnPropertyIdx(idx) {
+		return true
+	}
+
+	if a.prototype != nil {
+		return a.prototype.self.hasPropertyIdx(idx)
+	}
+
+	return false
+}
+
 func (a *sparseArrayObject) expand(idx uint32) bool {
 	if l := len(a.items); l >= 1024 {
 		if ii := a.items[l-1].idx; ii > idx {
@@ -458,7 +470,7 @@ func (a *sparseArrayObject) exportType() reflect.Type {
 
 func (a *sparseArrayObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
 	r := a.val.runtime
-	if iter := a.getSym(SymIterator, nil); iter == r.global.arrayValues || iter == nil {
+	if iter := a.getSym(SymIterator, nil); iter == r.getArrayValues() || iter == nil {
 		l := toIntStrict(int64(a.length))
 		if typ.Kind() == reflect.Array {
 			if dst.Len() != l {

+ 120 - 81
builtin_array.go

@@ -3,6 +3,7 @@ package goja
 import (
 	"math"
 	"sort"
+	"sync"
 )
 
 func (r *Runtime) newArray(prototype *Object) (a *arrayObject) {
@@ -19,7 +20,7 @@ func (r *Runtime) newArray(prototype *Object) (a *arrayObject) {
 }
 
 func (r *Runtime) newArrayObject() *arrayObject {
-	return r.newArray(r.global.ArrayPrototype)
+	return r.newArray(r.getArrayPrototype())
 }
 
 func setArrayValues(a *arrayObject, values []Value) *arrayObject {
@@ -96,7 +97,7 @@ func (r *Runtime) builtin_newArray(args []Value, proto *Object) *Object {
 			if float64(al) == float64(f) {
 				return r.newArrayLength(al)
 			} else {
-				panic(r.newError(r.global.RangeError, "Invalid array length"))
+				panic(r.newError(r.getRangeError(), "Invalid array length"))
 			}
 		}
 		return setArrayValues(r.newArray(proto), []Value{args[0]}).val
@@ -1259,7 +1260,7 @@ func (r *Runtime) checkStdArray(v Value) *arrayObject {
 
 func (r *Runtime) checkStdArrayIter(v Value) *arrayObject {
 	if arr := r.checkStdArray(v); arr != nil &&
-		arr.getSym(SymIterator, nil) == r.global.arrayValues {
+		arr.getSym(SymIterator, nil) == r.getArrayValues() {
 
 		return arr
 	}
@@ -1398,80 +1399,110 @@ func (r *Runtime) arrayIterProto_next(call FunctionCall) Value {
 	panic(r.NewTypeError("Method Array Iterator.prototype.next called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 }
 
-func (r *Runtime) createArrayProto(val *Object) objectImpl {
-	o := &arrayObject{
-		baseObject: baseObject{
-			class:      classArray,
-			val:        val,
-			extensible: true,
-			prototype:  r.global.ObjectPrototype,
-		},
-	}
-	o.init()
-
-	o._putProp("at", r.newNativeFunc(r.arrayproto_at, nil, "at", nil, 1), true, false, true)
-	o._putProp("constructor", r.global.Array, true, false, true)
-	o._putProp("concat", r.newNativeFunc(r.arrayproto_concat, nil, "concat", nil, 1), true, false, true)
-	o._putProp("copyWithin", r.newNativeFunc(r.arrayproto_copyWithin, nil, "copyWithin", nil, 2), true, false, true)
-	o._putProp("entries", r.newNativeFunc(r.arrayproto_entries, nil, "entries", nil, 0), true, false, true)
-	o._putProp("every", r.newNativeFunc(r.arrayproto_every, nil, "every", nil, 1), true, false, true)
-	o._putProp("fill", r.newNativeFunc(r.arrayproto_fill, nil, "fill", nil, 1), true, false, true)
-	o._putProp("filter", r.newNativeFunc(r.arrayproto_filter, nil, "filter", nil, 1), true, false, true)
-	o._putProp("find", r.newNativeFunc(r.arrayproto_find, nil, "find", nil, 1), true, false, true)
-	o._putProp("findIndex", r.newNativeFunc(r.arrayproto_findIndex, nil, "findIndex", nil, 1), true, false, true)
-	o._putProp("findLast", r.newNativeFunc(r.arrayproto_findLast, nil, "findLast", nil, 1), true, false, true)
-	o._putProp("findLastIndex", r.newNativeFunc(r.arrayproto_findLastIndex, nil, "findLastIndex", nil, 1), true, false, true)
-	o._putProp("flat", r.newNativeFunc(r.arrayproto_flat, nil, "flat", nil, 0), true, false, true)
-	o._putProp("flatMap", r.newNativeFunc(r.arrayproto_flatMap, nil, "flatMap", nil, 1), true, false, true)
-	o._putProp("forEach", r.newNativeFunc(r.arrayproto_forEach, nil, "forEach", nil, 1), true, false, true)
-	o._putProp("includes", r.newNativeFunc(r.arrayproto_includes, nil, "includes", nil, 1), true, false, true)
-	o._putProp("indexOf", r.newNativeFunc(r.arrayproto_indexOf, nil, "indexOf", nil, 1), true, false, true)
-	o._putProp("join", r.newNativeFunc(r.arrayproto_join, nil, "join", nil, 1), true, false, true)
-	o._putProp("keys", r.newNativeFunc(r.arrayproto_keys, nil, "keys", nil, 0), true, false, true)
-	o._putProp("lastIndexOf", r.newNativeFunc(r.arrayproto_lastIndexOf, nil, "lastIndexOf", nil, 1), true, false, true)
-	o._putProp("map", r.newNativeFunc(r.arrayproto_map, nil, "map", nil, 1), true, false, true)
-	o._putProp("pop", r.newNativeFunc(r.arrayproto_pop, nil, "pop", nil, 0), true, false, true)
-	o._putProp("push", r.newNativeFunc(r.arrayproto_push, nil, "push", nil, 1), true, false, true)
-	o._putProp("reduce", r.newNativeFunc(r.arrayproto_reduce, nil, "reduce", nil, 1), true, false, true)
-	o._putProp("reduceRight", r.newNativeFunc(r.arrayproto_reduceRight, nil, "reduceRight", nil, 1), true, false, true)
-	o._putProp("reverse", r.newNativeFunc(r.arrayproto_reverse, nil, "reverse", nil, 0), true, false, true)
-	o._putProp("shift", r.newNativeFunc(r.arrayproto_shift, nil, "shift", nil, 0), true, false, true)
-	o._putProp("slice", r.newNativeFunc(r.arrayproto_slice, nil, "slice", nil, 2), true, false, true)
-	o._putProp("some", r.newNativeFunc(r.arrayproto_some, nil, "some", nil, 1), true, false, true)
-	o._putProp("sort", r.newNativeFunc(r.arrayproto_sort, nil, "sort", nil, 1), true, false, true)
-	o._putProp("splice", r.newNativeFunc(r.arrayproto_splice, nil, "splice", nil, 2), true, false, true)
-	o._putProp("toLocaleString", r.newNativeFunc(r.arrayproto_toLocaleString, nil, "toLocaleString", nil, 0), true, false, true)
-	o._putProp("toString", r.global.arrayToString, true, false, true)
-	o._putProp("unshift", r.newNativeFunc(r.arrayproto_unshift, nil, "unshift", nil, 1), true, false, true)
-	o._putProp("values", r.global.arrayValues, true, false, true)
-
-	o._putSym(SymIterator, valueProp(r.global.arrayValues, true, false, true))
-
-	bl := r.newBaseObject(nil, classObject)
-	bl.setOwnStr("copyWithin", valueTrue, true)
-	bl.setOwnStr("entries", valueTrue, true)
-	bl.setOwnStr("fill", valueTrue, true)
-	bl.setOwnStr("find", valueTrue, true)
-	bl.setOwnStr("findIndex", valueTrue, true)
-	bl.setOwnStr("findLast", valueTrue, true)
-	bl.setOwnStr("findLastIndex", valueTrue, true)
-	bl.setOwnStr("flat", valueTrue, true)
-	bl.setOwnStr("flatMap", valueTrue, true)
-	bl.setOwnStr("includes", valueTrue, true)
-	bl.setOwnStr("keys", valueTrue, true)
-	bl.setOwnStr("values", valueTrue, true)
-	bl.setOwnStr("groupBy", valueTrue, true)
-	bl.setOwnStr("groupByToMap", valueTrue, true)
-	o._putSym(SymUnscopables, valueProp(bl.val, false, false, true))
+func createArrayProtoTemplate() *objectTemplate {
+	t := newObjectTemplate()
+	t.protoFactory = func(r *Runtime) *Object {
+		return r.global.ObjectPrototype
+	}
+
+	t.putStr("length", func(r *Runtime) Value { return valueProp(_positiveZero, true, false, false) })
+
+	t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getArray(), true, false, true) })
+
+	t.putStr("at", func(r *Runtime) Value { return r.methodProp(r.arrayproto_at, "at", 1) })
+	t.putStr("concat", func(r *Runtime) Value { return r.methodProp(r.arrayproto_concat, "concat", 1) })
+	t.putStr("copyWithin", func(r *Runtime) Value { return r.methodProp(r.arrayproto_copyWithin, "copyWithin", 2) })
+	t.putStr("entries", func(r *Runtime) Value { return r.methodProp(r.arrayproto_entries, "entries", 0) })
+	t.putStr("every", func(r *Runtime) Value { return r.methodProp(r.arrayproto_every, "every", 1) })
+	t.putStr("fill", func(r *Runtime) Value { return r.methodProp(r.arrayproto_fill, "fill", 1) })
+	t.putStr("filter", func(r *Runtime) Value { return r.methodProp(r.arrayproto_filter, "filter", 1) })
+	t.putStr("find", func(r *Runtime) Value { return r.methodProp(r.arrayproto_find, "find", 1) })
+	t.putStr("findIndex", func(r *Runtime) Value { return r.methodProp(r.arrayproto_findIndex, "findIndex", 1) })
+	t.putStr("findLast", func(r *Runtime) Value { return r.methodProp(r.arrayproto_findLast, "findLast", 1) })
+	t.putStr("findLastIndex", func(r *Runtime) Value { return r.methodProp(r.arrayproto_findLastIndex, "findLastIndex", 1) })
+	t.putStr("flat", func(r *Runtime) Value { return r.methodProp(r.arrayproto_flat, "flat", 0) })
+	t.putStr("flatMap", func(r *Runtime) Value { return r.methodProp(r.arrayproto_flatMap, "flatMap", 1) })
+	t.putStr("forEach", func(r *Runtime) Value { return r.methodProp(r.arrayproto_forEach, "forEach", 1) })
+	t.putStr("includes", func(r *Runtime) Value { return r.methodProp(r.arrayproto_includes, "includes", 1) })
+	t.putStr("indexOf", func(r *Runtime) Value { return r.methodProp(r.arrayproto_indexOf, "indexOf", 1) })
+	t.putStr("join", func(r *Runtime) Value { return r.methodProp(r.arrayproto_join, "join", 1) })
+	t.putStr("keys", func(r *Runtime) Value { return r.methodProp(r.arrayproto_keys, "keys", 0) })
+	t.putStr("lastIndexOf", func(r *Runtime) Value { return r.methodProp(r.arrayproto_lastIndexOf, "lastIndexOf", 1) })
+	t.putStr("map", func(r *Runtime) Value { return r.methodProp(r.arrayproto_map, "map", 1) })
+	t.putStr("pop", func(r *Runtime) Value { return r.methodProp(r.arrayproto_pop, "pop", 0) })
+	t.putStr("push", func(r *Runtime) Value { return r.methodProp(r.arrayproto_push, "push", 1) })
+	t.putStr("reduce", func(r *Runtime) Value { return r.methodProp(r.arrayproto_reduce, "reduce", 1) })
+	t.putStr("reduceRight", func(r *Runtime) Value { return r.methodProp(r.arrayproto_reduceRight, "reduceRight", 1) })
+	t.putStr("reverse", func(r *Runtime) Value { return r.methodProp(r.arrayproto_reverse, "reverse", 0) })
+	t.putStr("shift", func(r *Runtime) Value { return r.methodProp(r.arrayproto_shift, "shift", 0) })
+	t.putStr("slice", func(r *Runtime) Value { return r.methodProp(r.arrayproto_slice, "slice", 2) })
+	t.putStr("some", func(r *Runtime) Value { return r.methodProp(r.arrayproto_some, "some", 1) })
+	t.putStr("sort", func(r *Runtime) Value { return r.methodProp(r.arrayproto_sort, "sort", 1) })
+	t.putStr("splice", func(r *Runtime) Value { return r.methodProp(r.arrayproto_splice, "splice", 2) })
+	t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.arrayproto_toLocaleString, "toLocaleString", 0) })
+	t.putStr("toString", func(r *Runtime) Value { return valueProp(r.getArrayToString(), true, false, true) })
+	t.putStr("unshift", func(r *Runtime) Value { return r.methodProp(r.arrayproto_unshift, "unshift", 1) })
+	t.putStr("values", func(r *Runtime) Value { return valueProp(r.getArrayValues(), true, false, true) })
+
+	t.putSym(SymIterator, func(r *Runtime) Value { return valueProp(r.getArrayValues(), true, false, true) })
+	t.putSym(SymUnscopables, func(r *Runtime) Value {
+		bl := r.newBaseObject(nil, classObject)
+		bl.setOwnStr("copyWithin", valueTrue, true)
+		bl.setOwnStr("entries", valueTrue, true)
+		bl.setOwnStr("fill", valueTrue, true)
+		bl.setOwnStr("find", valueTrue, true)
+		bl.setOwnStr("findIndex", valueTrue, true)
+		bl.setOwnStr("findLast", valueTrue, true)
+		bl.setOwnStr("findLastIndex", valueTrue, true)
+		bl.setOwnStr("flat", valueTrue, true)
+		bl.setOwnStr("flatMap", valueTrue, true)
+		bl.setOwnStr("includes", valueTrue, true)
+		bl.setOwnStr("keys", valueTrue, true)
+		bl.setOwnStr("values", valueTrue, true)
+		bl.setOwnStr("groupBy", valueTrue, true)
+		bl.setOwnStr("groupByToMap", valueTrue, true)
+
+		return valueProp(bl.val, false, false, true)
+	})
 
-	return o
+	return t
+}
+
+var arrayProtoTemplate *objectTemplate
+var arrayProtoTemplateOnce sync.Once
+
+func getArrayProtoTemplate() *objectTemplate {
+	arrayProtoTemplateOnce.Do(func() {
+		arrayProtoTemplate = createArrayProtoTemplate()
+	})
+	return arrayProtoTemplate
+}
+
+func (r *Runtime) getArrayPrototype() *Object {
+	ret := r.global.ArrayPrototype
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.ArrayPrototype = ret
+		r.newTemplatedArrayObject(getArrayProtoTemplate(), ret)
+	}
+	return ret
+}
+
+func (r *Runtime) getArray() *Object {
+	ret := r.global.Array
+	if ret == nil {
+		ret = &Object{runtime: r}
+		ret.self = r.createArray(ret)
+		r.global.Array = ret
+	}
+	return ret
 }
 
 func (r *Runtime) createArray(val *Object) objectImpl {
-	o := r.newNativeFuncConstructObj(val, r.builtin_newArray, "Array", r.global.ArrayPrototype, 1)
-	o._putProp("from", r.newNativeFunc(r.array_from, nil, "from", nil, 1), true, false, true)
-	o._putProp("isArray", r.newNativeFunc(r.array_isArray, nil, "isArray", nil, 1), true, false, true)
-	o._putProp("of", r.newNativeFunc(r.array_of, nil, "of", nil, 0), true, false, true)
+	o := r.newNativeFuncConstructObj(val, r.builtin_newArray, "Array", r.getArrayPrototype(), 1)
+	o._putProp("from", r.newNativeFunc(r.array_from, "from", 1), true, false, true)
+	o._putProp("isArray", r.newNativeFunc(r.array_isArray, "isArray", 1), true, false, true)
+	o._putProp("of", r.newNativeFunc(r.array_of, "of", 0), true, false, true)
 	r.putSpeciesReturnThis(o)
 
 	return o
@@ -1480,20 +1511,28 @@ func (r *Runtime) createArray(val *Object) objectImpl {
 func (r *Runtime) createArrayIterProto(val *Object) objectImpl {
 	o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject)
 
-	o._putProp("next", r.newNativeFunc(r.arrayIterProto_next, nil, "next", nil, 0), true, false, true)
+	o._putProp("next", r.newNativeFunc(r.arrayIterProto_next, "next", 0), true, false, true)
 	o._putSym(SymToStringTag, valueProp(asciiString(classArrayIterator), false, false, true))
 
 	return o
 }
 
-func (r *Runtime) initArray() {
-	r.global.arrayValues = r.newNativeFunc(r.arrayproto_values, nil, "values", nil, 0)
-	r.global.arrayToString = r.newNativeFunc(r.arrayproto_toString, nil, "toString", nil, 0)
-
-	r.global.ArrayPrototype = r.newLazyObject(r.createArrayProto)
+func (r *Runtime) getArrayValues() *Object {
+	ret := r.global.arrayValues
+	if ret == nil {
+		ret = r.newNativeFunc(r.arrayproto_values, "values", 0)
+		r.global.arrayValues = ret
+	}
+	return ret
+}
 
-	r.global.Array = r.newLazyObject(r.createArray)
-	r.addToGlobal("Array", r.global.Array)
+func (r *Runtime) getArrayToString() *Object {
+	ret := r.global.arrayToString
+	if ret == nil {
+		ret = r.newNativeFunc(r.arrayproto_toString, "toString", 0)
+		r.global.arrayToString = ret
+	}
+	return ret
 }
 
 func (r *Runtime) getArrayIteratorPrototype() *Object {

+ 16 - 0
builtin_arrray_test.go

@@ -323,3 +323,19 @@ func TestArrayFlatMap(t *testing.T) {
 	`
 	testScriptWithTestLibX(SCRIPT, _undefined, t)
 }
+
+func TestArrayProto(t *testing.T) {
+	const SCRIPT = `
+	const a = Array.prototype;
+	a.push(1, 2, 3, 4, 5);
+	assert.sameValue(a.length, 5);
+	assert.sameValue(a[0], 1);
+	a.length = 3;
+	assert.sameValue(a.length, 3);
+	assert(compareArray(a, [1, 2, 3]));
+	a.shift();
+	assert.sameValue(a.length, 2);
+	assert(compareArray(a, [2, 3]));
+	`
+	testScriptWithTestLib(SCRIPT, _undefined, t)
+}

+ 22 - 7
builtin_boolean.go

@@ -49,12 +49,27 @@ func (r *Runtime) booleanproto_valueOf(call FunctionCall) Value {
 	return nil
 }
 
-func (r *Runtime) initBoolean() {
-	r.global.BooleanPrototype = r.newPrimitiveObject(valueFalse, r.global.ObjectPrototype, classBoolean)
-	o := r.global.BooleanPrototype.self
-	o._putProp("toString", r.newNativeFunc(r.booleanproto_toString, nil, "toString", nil, 0), true, false, true)
-	o._putProp("valueOf", r.newNativeFunc(r.booleanproto_valueOf, nil, "valueOf", nil, 0), true, false, true)
+func (r *Runtime) getBooleanPrototype() *Object {
+	ret := r.global.BooleanPrototype
+	if ret == nil {
+		ret = r.newPrimitiveObject(valueFalse, r.global.ObjectPrototype, classBoolean)
+		r.global.BooleanPrototype = ret
+		o := ret.self
+		o._putProp("toString", r.newNativeFunc(r.booleanproto_toString, "toString", 0), true, false, true)
+		o._putProp("valueOf", r.newNativeFunc(r.booleanproto_valueOf, "valueOf", 0), true, false, true)
+		o._putProp("constructor", r.getBoolean(), true, false, true)
+	}
+	return ret
+}
 
-	r.global.Boolean = r.newNativeFunc(r.builtin_Boolean, r.builtin_newBoolean, "Boolean", r.global.BooleanPrototype, 1)
-	r.addToGlobal("Boolean", r.global.Boolean)
+func (r *Runtime) getBoolean() *Object {
+	ret := r.global.Boolean
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Boolean = ret
+		proto := r.getBooleanPrototype()
+		r.newNativeFuncAndConstruct(ret, r.builtin_Boolean,
+			r.wrapNativeConstruct(r.builtin_newBoolean, ret, proto), proto, "Boolean", intToValue(1))
+	}
+	return ret
 }

+ 118 - 75
builtin_date.go

@@ -3,6 +3,7 @@ package goja
 import (
 	"fmt"
 	"math"
+	"sync"
 	"time"
 )
 
@@ -133,7 +134,7 @@ func (r *Runtime) dateproto_toISOString(call FunctionCall) Value {
 			// extended year
 			return asciiString(fmt.Sprintf("%+06d-", year) + utc.Format(isoDateTimeLayout[5:]))
 		} else {
-			panic(r.newError(r.global.RangeError, "Invalid time value"))
+			panic(r.newError(r.getRangeError(), "Invalid time value"))
 		}
 	}
 	panic(r.NewTypeError("Method Date.prototype.toISOString is called on incompatible receiver"))
@@ -938,78 +939,120 @@ func (r *Runtime) dateproto_setUTCFullYear(call FunctionCall) Value {
 	panic(r.NewTypeError("Method Date.prototype.setUTCFullYear is called on incompatible receiver"))
 }
 
-func (r *Runtime) createDateProto(val *Object) objectImpl {
-	o := &baseObject{
-		class:      classObject,
-		val:        val,
-		extensible: true,
-		prototype:  r.global.ObjectPrototype,
-	}
-	o.init()
-
-	o._putProp("constructor", r.global.Date, true, false, true)
-	o._putProp("toString", r.newNativeFunc(r.dateproto_toString, nil, "toString", nil, 0), true, false, true)
-	o._putProp("toDateString", r.newNativeFunc(r.dateproto_toDateString, nil, "toDateString", nil, 0), true, false, true)
-	o._putProp("toTimeString", r.newNativeFunc(r.dateproto_toTimeString, nil, "toTimeString", nil, 0), true, false, true)
-	o._putProp("toLocaleString", r.newNativeFunc(r.dateproto_toLocaleString, nil, "toLocaleString", nil, 0), true, false, true)
-	o._putProp("toLocaleDateString", r.newNativeFunc(r.dateproto_toLocaleDateString, nil, "toLocaleDateString", nil, 0), true, false, true)
-	o._putProp("toLocaleTimeString", r.newNativeFunc(r.dateproto_toLocaleTimeString, nil, "toLocaleTimeString", nil, 0), true, false, true)
-	o._putProp("valueOf", r.newNativeFunc(r.dateproto_valueOf, nil, "valueOf", nil, 0), true, false, true)
-	o._putProp("getTime", r.newNativeFunc(r.dateproto_getTime, nil, "getTime", nil, 0), true, false, true)
-	o._putProp("getFullYear", r.newNativeFunc(r.dateproto_getFullYear, nil, "getFullYear", nil, 0), true, false, true)
-	o._putProp("getUTCFullYear", r.newNativeFunc(r.dateproto_getUTCFullYear, nil, "getUTCFullYear", nil, 0), true, false, true)
-	o._putProp("getMonth", r.newNativeFunc(r.dateproto_getMonth, nil, "getMonth", nil, 0), true, false, true)
-	o._putProp("getUTCMonth", r.newNativeFunc(r.dateproto_getUTCMonth, nil, "getUTCMonth", nil, 0), true, false, true)
-	o._putProp("getDate", r.newNativeFunc(r.dateproto_getDate, nil, "getDate", nil, 0), true, false, true)
-	o._putProp("getUTCDate", r.newNativeFunc(r.dateproto_getUTCDate, nil, "getUTCDate", nil, 0), true, false, true)
-	o._putProp("getDay", r.newNativeFunc(r.dateproto_getDay, nil, "getDay", nil, 0), true, false, true)
-	o._putProp("getUTCDay", r.newNativeFunc(r.dateproto_getUTCDay, nil, "getUTCDay", nil, 0), true, false, true)
-	o._putProp("getHours", r.newNativeFunc(r.dateproto_getHours, nil, "getHours", nil, 0), true, false, true)
-	o._putProp("getUTCHours", r.newNativeFunc(r.dateproto_getUTCHours, nil, "getUTCHours", nil, 0), true, false, true)
-	o._putProp("getMinutes", r.newNativeFunc(r.dateproto_getMinutes, nil, "getMinutes", nil, 0), true, false, true)
-	o._putProp("getUTCMinutes", r.newNativeFunc(r.dateproto_getUTCMinutes, nil, "getUTCMinutes", nil, 0), true, false, true)
-	o._putProp("getSeconds", r.newNativeFunc(r.dateproto_getSeconds, nil, "getSeconds", nil, 0), true, false, true)
-	o._putProp("getUTCSeconds", r.newNativeFunc(r.dateproto_getUTCSeconds, nil, "getUTCSeconds", nil, 0), true, false, true)
-	o._putProp("getMilliseconds", r.newNativeFunc(r.dateproto_getMilliseconds, nil, "getMilliseconds", nil, 0), true, false, true)
-	o._putProp("getUTCMilliseconds", r.newNativeFunc(r.dateproto_getUTCMilliseconds, nil, "getUTCMilliseconds", nil, 0), true, false, true)
-	o._putProp("getTimezoneOffset", r.newNativeFunc(r.dateproto_getTimezoneOffset, nil, "getTimezoneOffset", nil, 0), true, false, true)
-	o._putProp("setTime", r.newNativeFunc(r.dateproto_setTime, nil, "setTime", nil, 1), true, false, true)
-	o._putProp("setMilliseconds", r.newNativeFunc(r.dateproto_setMilliseconds, nil, "setMilliseconds", nil, 1), true, false, true)
-	o._putProp("setUTCMilliseconds", r.newNativeFunc(r.dateproto_setUTCMilliseconds, nil, "setUTCMilliseconds", nil, 1), true, false, true)
-	o._putProp("setSeconds", r.newNativeFunc(r.dateproto_setSeconds, nil, "setSeconds", nil, 2), true, false, true)
-	o._putProp("setUTCSeconds", r.newNativeFunc(r.dateproto_setUTCSeconds, nil, "setUTCSeconds", nil, 2), true, false, true)
-	o._putProp("setMinutes", r.newNativeFunc(r.dateproto_setMinutes, nil, "setMinutes", nil, 3), true, false, true)
-	o._putProp("setUTCMinutes", r.newNativeFunc(r.dateproto_setUTCMinutes, nil, "setUTCMinutes", nil, 3), true, false, true)
-	o._putProp("setHours", r.newNativeFunc(r.dateproto_setHours, nil, "setHours", nil, 4), true, false, true)
-	o._putProp("setUTCHours", r.newNativeFunc(r.dateproto_setUTCHours, nil, "setUTCHours", nil, 4), true, false, true)
-	o._putProp("setDate", r.newNativeFunc(r.dateproto_setDate, nil, "setDate", nil, 1), true, false, true)
-	o._putProp("setUTCDate", r.newNativeFunc(r.dateproto_setUTCDate, nil, "setUTCDate", nil, 1), true, false, true)
-	o._putProp("setMonth", r.newNativeFunc(r.dateproto_setMonth, nil, "setMonth", nil, 2), true, false, true)
-	o._putProp("setUTCMonth", r.newNativeFunc(r.dateproto_setUTCMonth, nil, "setUTCMonth", nil, 2), true, false, true)
-	o._putProp("setFullYear", r.newNativeFunc(r.dateproto_setFullYear, nil, "setFullYear", nil, 3), true, false, true)
-	o._putProp("setUTCFullYear", r.newNativeFunc(r.dateproto_setUTCFullYear, nil, "setUTCFullYear", nil, 3), true, false, true)
-	o._putProp("toUTCString", r.newNativeFunc(r.dateproto_toUTCString, nil, "toUTCString", nil, 0), true, false, true)
-	o._putProp("toISOString", r.newNativeFunc(r.dateproto_toISOString, nil, "toISOString", nil, 0), true, false, true)
-	o._putProp("toJSON", r.newNativeFunc(r.dateproto_toJSON, nil, "toJSON", nil, 1), true, false, true)
-
-	o._putSym(SymToPrimitive, valueProp(r.newNativeFunc(r.dateproto_toPrimitive, nil, "[Symbol.toPrimitive]", nil, 1), false, false, true))
-
-	return o
-}
-
-func (r *Runtime) createDate(val *Object) objectImpl {
-	o := r.newNativeFuncObj(val, r.builtin_date, r.builtin_newDate, "Date", r.global.DatePrototype, intToValue(7))
-
-	o._putProp("parse", r.newNativeFunc(r.date_parse, nil, "parse", nil, 1), true, false, true)
-	o._putProp("UTC", r.newNativeFunc(r.date_UTC, nil, "UTC", nil, 7), true, false, true)
-	o._putProp("now", r.newNativeFunc(r.date_now, nil, "now", nil, 0), true, false, true)
-
-	return o
-}
-
-func (r *Runtime) initDate() {
-	r.global.DatePrototype = r.newLazyObject(r.createDateProto)
-
-	r.global.Date = r.newLazyObject(r.createDate)
-	r.addToGlobal("Date", r.global.Date)
+var dateTemplate *objectTemplate
+var dateTemplateOnce sync.Once
+
+func getDateTemplate() *objectTemplate {
+	dateTemplateOnce.Do(func() {
+		dateTemplate = createDateTemplate()
+	})
+	return dateTemplate
+}
+
+func createDateTemplate() *objectTemplate {
+	t := newObjectTemplate()
+	t.protoFactory = func(r *Runtime) *Object {
+		return r.getFunctionPrototype()
+	}
+
+	t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("Date"), false, false, true) })
+	t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(7), false, false, true) })
+
+	t.putStr("prototype", func(r *Runtime) Value { return valueProp(r.getDatePrototype(), false, false, false) })
+
+	t.putStr("parse", func(r *Runtime) Value { return r.methodProp(r.date_parse, "parse", 1) })
+	t.putStr("UTC", func(r *Runtime) Value { return r.methodProp(r.date_UTC, "UTC", 7) })
+	t.putStr("now", func(r *Runtime) Value { return r.methodProp(r.date_now, "now", 0) })
+
+	return t
+}
+
+func (r *Runtime) getDate() *Object {
+	ret := r.global.Date
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Date = ret
+		r.newTemplatedFuncObject(getDateTemplate(), ret, r.builtin_date,
+			r.wrapNativeConstruct(r.builtin_newDate, ret, r.getDatePrototype()))
+	}
+	return ret
+}
+
+func createDateProtoTemplate() *objectTemplate {
+	t := newObjectTemplate()
+	t.protoFactory = func(r *Runtime) *Object {
+		return r.global.ObjectPrototype
+	}
+
+	t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getDate(), true, false, true) })
+
+	t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toString, "toString", 0) })
+	t.putStr("toDateString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toDateString, "toDateString", 0) })
+	t.putStr("toTimeString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toTimeString, "toTimeString", 0) })
+	t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toLocaleString, "toLocaleString", 0) })
+	t.putStr("toLocaleDateString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toLocaleDateString, "toLocaleDateString", 0) })
+	t.putStr("toLocaleTimeString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toLocaleTimeString, "toLocaleTimeString", 0) })
+	t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.dateproto_valueOf, "valueOf", 0) })
+	t.putStr("getTime", func(r *Runtime) Value { return r.methodProp(r.dateproto_getTime, "getTime", 0) })
+	t.putStr("getFullYear", func(r *Runtime) Value { return r.methodProp(r.dateproto_getFullYear, "getFullYear", 0) })
+	t.putStr("getUTCFullYear", func(r *Runtime) Value { return r.methodProp(r.dateproto_getUTCFullYear, "getUTCFullYear", 0) })
+	t.putStr("getMonth", func(r *Runtime) Value { return r.methodProp(r.dateproto_getMonth, "getMonth", 0) })
+	t.putStr("getUTCMonth", func(r *Runtime) Value { return r.methodProp(r.dateproto_getUTCMonth, "getUTCMonth", 0) })
+	t.putStr("getDate", func(r *Runtime) Value { return r.methodProp(r.dateproto_getDate, "getDate", 0) })
+	t.putStr("getUTCDate", func(r *Runtime) Value { return r.methodProp(r.dateproto_getUTCDate, "getUTCDate", 0) })
+	t.putStr("getDay", func(r *Runtime) Value { return r.methodProp(r.dateproto_getDay, "getDay", 0) })
+	t.putStr("getUTCDay", func(r *Runtime) Value { return r.methodProp(r.dateproto_getUTCDay, "getUTCDay", 0) })
+	t.putStr("getHours", func(r *Runtime) Value { return r.methodProp(r.dateproto_getHours, "getHours", 0) })
+	t.putStr("getUTCHours", func(r *Runtime) Value { return r.methodProp(r.dateproto_getUTCHours, "getUTCHours", 0) })
+	t.putStr("getMinutes", func(r *Runtime) Value { return r.methodProp(r.dateproto_getMinutes, "getMinutes", 0) })
+	t.putStr("getUTCMinutes", func(r *Runtime) Value { return r.methodProp(r.dateproto_getUTCMinutes, "getUTCMinutes", 0) })
+	t.putStr("getSeconds", func(r *Runtime) Value { return r.methodProp(r.dateproto_getSeconds, "getSeconds", 0) })
+	t.putStr("getUTCSeconds", func(r *Runtime) Value { return r.methodProp(r.dateproto_getUTCSeconds, "getUTCSeconds", 0) })
+	t.putStr("getMilliseconds", func(r *Runtime) Value { return r.methodProp(r.dateproto_getMilliseconds, "getMilliseconds", 0) })
+	t.putStr("getUTCMilliseconds", func(r *Runtime) Value { return r.methodProp(r.dateproto_getUTCMilliseconds, "getUTCMilliseconds", 0) })
+	t.putStr("getTimezoneOffset", func(r *Runtime) Value { return r.methodProp(r.dateproto_getTimezoneOffset, "getTimezoneOffset", 0) })
+	t.putStr("setTime", func(r *Runtime) Value { return r.methodProp(r.dateproto_setTime, "setTime", 1) })
+	t.putStr("setMilliseconds", func(r *Runtime) Value { return r.methodProp(r.dateproto_setMilliseconds, "setMilliseconds", 1) })
+	t.putStr("setUTCMilliseconds", func(r *Runtime) Value { return r.methodProp(r.dateproto_setUTCMilliseconds, "setUTCMilliseconds", 1) })
+	t.putStr("setSeconds", func(r *Runtime) Value { return r.methodProp(r.dateproto_setSeconds, "setSeconds", 2) })
+	t.putStr("setUTCSeconds", func(r *Runtime) Value { return r.methodProp(r.dateproto_setUTCSeconds, "setUTCSeconds", 2) })
+	t.putStr("setMinutes", func(r *Runtime) Value { return r.methodProp(r.dateproto_setMinutes, "setMinutes", 3) })
+	t.putStr("setUTCMinutes", func(r *Runtime) Value { return r.methodProp(r.dateproto_setUTCMinutes, "setUTCMinutes", 3) })
+	t.putStr("setHours", func(r *Runtime) Value { return r.methodProp(r.dateproto_setHours, "setHours", 4) })
+	t.putStr("setUTCHours", func(r *Runtime) Value { return r.methodProp(r.dateproto_setUTCHours, "setUTCHours", 4) })
+	t.putStr("setDate", func(r *Runtime) Value { return r.methodProp(r.dateproto_setDate, "setDate", 1) })
+	t.putStr("setUTCDate", func(r *Runtime) Value { return r.methodProp(r.dateproto_setUTCDate, "setUTCDate", 1) })
+	t.putStr("setMonth", func(r *Runtime) Value { return r.methodProp(r.dateproto_setMonth, "setMonth", 2) })
+	t.putStr("setUTCMonth", func(r *Runtime) Value { return r.methodProp(r.dateproto_setUTCMonth, "setUTCMonth", 2) })
+	t.putStr("setFullYear", func(r *Runtime) Value { return r.methodProp(r.dateproto_setFullYear, "setFullYear", 3) })
+	t.putStr("setUTCFullYear", func(r *Runtime) Value { return r.methodProp(r.dateproto_setUTCFullYear, "setUTCFullYear", 3) })
+	t.putStr("toUTCString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toUTCString, "toUTCString", 0) })
+	t.putStr("toISOString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toISOString, "toISOString", 0) })
+	t.putStr("toJSON", func(r *Runtime) Value { return r.methodProp(r.dateproto_toJSON, "toJSON", 1) })
+
+	t.putSym(SymToPrimitive, func(r *Runtime) Value {
+		return valueProp(r.newNativeFunc(r.dateproto_toPrimitive, "[Symbol.toPrimitive]", 1), false, false, true)
+	})
+
+	return t
+}
+
+var dateProtoTemplate *objectTemplate
+var dateProtoTemplateOnce sync.Once
+
+func getDateProtoTemplate() *objectTemplate {
+	dateProtoTemplateOnce.Do(func() {
+		dateProtoTemplate = createDateProtoTemplate()
+	})
+	return dateProtoTemplate
+}
+
+func (r *Runtime) getDatePrototype() *Object {
+	ret := r.global.DatePrototype
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.DatePrototype = ret
+		r.newTemplatedObject(getDateProtoTemplate(), ret)
+	}
+	return ret
 }

+ 96 - 43
builtin_error.go

@@ -176,61 +176,114 @@ func (r *Runtime) error_toString(call FunctionCall) Value {
 	return sb.String()
 }
 
-func (r *Runtime) createErrorPrototype(name String) *Object {
-	o := r.newBaseObject(r.global.ErrorPrototype, classObject)
+func (r *Runtime) createErrorPrototype(name String, ctor *Object) *Object {
+	o := r.newBaseObject(r.getErrorPrototype(), classObject)
 	o._putProp("message", stringEmpty, true, false, true)
 	o._putProp("name", name, true, false, true)
+	o._putProp("constructor", ctor, true, false, true)
 	return o.val
 }
 
-func (r *Runtime) initErrors() {
-	r.global.ErrorPrototype = r.NewObject()
-	o := r.global.ErrorPrototype.self
-	o._putProp("message", stringEmpty, true, false, true)
-	o._putProp("name", stringError, true, false, true)
-	o._putProp("toString", r.newNativeFunc(r.error_toString, nil, "toString", nil, 0), true, false, true)
-
-	r.global.Error = r.newNativeFuncConstruct(r.builtin_Error, "Error", r.global.ErrorPrototype, 1)
-	r.addToGlobal("Error", r.global.Error)
-
-	r.global.AggregateErrorPrototype = r.createErrorPrototype(stringAggregateError)
-	r.global.AggregateError = r.newNativeFuncConstructProto(r.builtin_AggregateError, "AggregateError", r.global.AggregateErrorPrototype, r.global.Error, 2)
-	r.addToGlobal("AggregateError", r.global.AggregateError)
-
-	r.global.TypeErrorPrototype = r.createErrorPrototype(stringTypeError)
-
-	r.global.TypeError = r.newNativeFuncConstructProto(r.builtin_Error, "TypeError", r.global.TypeErrorPrototype, r.global.Error, 1)
-	r.addToGlobal("TypeError", r.global.TypeError)
-
-	r.global.ReferenceErrorPrototype = r.createErrorPrototype(stringReferenceError)
-
-	r.global.ReferenceError = r.newNativeFuncConstructProto(r.builtin_Error, "ReferenceError", r.global.ReferenceErrorPrototype, r.global.Error, 1)
-	r.addToGlobal("ReferenceError", r.global.ReferenceError)
-
-	r.global.SyntaxErrorPrototype = r.createErrorPrototype(stringSyntaxError)
+func (r *Runtime) getErrorPrototype() *Object {
+	ret := r.global.ErrorPrototype
+	if ret == nil {
+		ret = r.NewObject()
+		r.global.ErrorPrototype = ret
+		o := ret.self
+		o._putProp("message", stringEmpty, true, false, true)
+		o._putProp("name", stringError, true, false, true)
+		o._putProp("toString", r.newNativeFunc(r.error_toString, "toString", 0), true, false, true)
+		o._putProp("constructor", r.getError(), true, false, true)
+	}
+	return ret
+}
 
-	r.global.SyntaxError = r.newNativeFuncConstructProto(r.builtin_Error, "SyntaxError", r.global.SyntaxErrorPrototype, r.global.Error, 1)
-	r.addToGlobal("SyntaxError", r.global.SyntaxError)
+func (r *Runtime) getError() *Object {
+	ret := r.global.Error
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Error = ret
+		r.newNativeFuncConstruct(ret, r.builtin_Error, "Error", r.getErrorPrototype(), 1)
+	}
+	return ret
+}
 
-	r.global.RangeErrorPrototype = r.createErrorPrototype(stringRangeError)
+func (r *Runtime) getAggregateError() *Object {
+	ret := r.global.AggregateError
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.AggregateError = ret
+		r.newNativeFuncConstructProto(ret, r.builtin_AggregateError, "AggregateError", r.createErrorPrototype(stringAggregateError, ret), r.getError(), 2)
+	}
+	return ret
+}
 
-	r.global.RangeError = r.newNativeFuncConstructProto(r.builtin_Error, "RangeError", r.global.RangeErrorPrototype, r.global.Error, 1)
-	r.addToGlobal("RangeError", r.global.RangeError)
+func (r *Runtime) getTypeError() *Object {
+	ret := r.global.TypeError
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.TypeError = ret
+		r.newNativeFuncConstructProto(ret, r.builtin_Error, "TypeError", r.createErrorPrototype(stringTypeError, ret), r.getError(), 1)
+	}
+	return ret
+}
 
-	r.global.EvalErrorPrototype = r.createErrorPrototype(stringEvalError)
-	o = r.global.EvalErrorPrototype.self
-	o._putProp("name", stringEvalError, true, false, true)
+func (r *Runtime) getReferenceError() *Object {
+	ret := r.global.ReferenceError
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.ReferenceError = ret
+		r.newNativeFuncConstructProto(ret, r.builtin_Error, "ReferenceError", r.createErrorPrototype(stringReferenceError, ret), r.getError(), 1)
+	}
+	return ret
+}
 
-	r.global.EvalError = r.newNativeFuncConstructProto(r.builtin_Error, "EvalError", r.global.EvalErrorPrototype, r.global.Error, 1)
-	r.addToGlobal("EvalError", r.global.EvalError)
+func (r *Runtime) getSyntaxError() *Object {
+	ret := r.global.SyntaxError
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.SyntaxError = ret
+		r.newNativeFuncConstructProto(ret, r.builtin_Error, "SyntaxError", r.createErrorPrototype(stringSyntaxError, ret), r.getError(), 1)
+	}
+	return ret
+}
 
-	r.global.URIErrorPrototype = r.createErrorPrototype(stringURIError)
+func (r *Runtime) getRangeError() *Object {
+	ret := r.global.RangeError
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.RangeError = ret
+		r.newNativeFuncConstructProto(ret, r.builtin_Error, "RangeError", r.createErrorPrototype(stringRangeError, ret), r.getError(), 1)
+	}
+	return ret
+}
 
-	r.global.URIError = r.newNativeFuncConstructProto(r.builtin_Error, "URIError", r.global.URIErrorPrototype, r.global.Error, 1)
-	r.addToGlobal("URIError", r.global.URIError)
+func (r *Runtime) getEvalError() *Object {
+	ret := r.global.EvalError
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.EvalError = ret
+		r.newNativeFuncConstructProto(ret, r.builtin_Error, "EvalError", r.createErrorPrototype(stringEvalError, ret), r.getError(), 1)
+	}
+	return ret
+}
 
-	r.global.GoErrorPrototype = r.createErrorPrototype(stringGoError)
+func (r *Runtime) getURIError() *Object {
+	ret := r.global.URIError
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.URIError = ret
+		r.newNativeFuncConstructProto(ret, r.builtin_Error, "URIError", r.createErrorPrototype(stringURIError, ret), r.getError(), 1)
+	}
+	return ret
+}
 
-	r.global.GoError = r.newNativeFuncConstructProto(r.builtin_Error, "GoError", r.global.GoErrorPrototype, r.global.Error, 1)
-	r.addToGlobal("GoError", r.global.GoError)
+func (r *Runtime) getGoError() *Object {
+	ret := r.global.GoError
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.GoError = ret
+		r.newNativeFuncConstructProto(ret, r.builtin_Error, "GoError", r.createErrorPrototype(stringGoError, ret), r.getError(), 1)
+	}
+	return ret
 }

+ 90 - 24
builtin_function.go

@@ -2,6 +2,7 @@ package goja
 
 import (
 	"math"
+	"sync"
 )
 
 func (r *Runtime) functionCtor(args []Value, proto *Object, async, generator bool) *Object {
@@ -53,16 +54,10 @@ func (r *Runtime) builtin_generatorFunction(args []Value, proto *Object) *Object
 
 func (r *Runtime) functionproto_toString(call FunctionCall) Value {
 	obj := r.toObject(call.This)
-	if lazy, ok := obj.self.(*lazyObject); ok {
-		obj.self = lazy.create(obj)
-	}
 	switch f := obj.self.(type) {
 	case funcObjectImpl:
 		return f.source()
 	case *proxyObject:
-		if lazy, ok := f.target.self.(*lazyObject); ok {
-			f.target.self = lazy.create(f.target)
-		}
 		if _, ok := f.target.self.(funcObjectImpl); ok {
 			return asciiString("function () { [native code] }")
 		}
@@ -210,18 +205,76 @@ lenNotInt:
 	return v
 }
 
-func (r *Runtime) initFunction() {
-	o := r.global.FunctionPrototype.self.(*nativeFuncObject)
-	o.prototype = r.global.ObjectPrototype
-	o._putProp("name", stringEmpty, false, false, true)
-	o._putProp("apply", r.newNativeFunc(r.functionproto_apply, nil, "apply", nil, 2), true, false, true)
-	o._putProp("bind", r.newNativeFunc(r.functionproto_bind, nil, "bind", nil, 1), true, false, true)
-	o._putProp("call", r.newNativeFunc(r.functionproto_call, nil, "call", nil, 1), true, false, true)
-	o._putProp("toString", r.newNativeFunc(r.functionproto_toString, nil, "toString", nil, 0), true, false, true)
-	o._putSym(SymHasInstance, valueProp(r.newNativeFunc(r.functionproto_hasInstance, nil, "[Symbol.hasInstance]", nil, 1), false, false, false))
+func (r *Runtime) getThrower() *Object {
+	ret := r.global.thrower
+	if ret == nil {
+		ret = r.newNativeFunc(r.builtin_thrower, "", 0)
+		r.global.thrower = ret
+		r.object_freeze(FunctionCall{Arguments: []Value{ret}})
+	}
+	return ret
+}
 
-	r.global.Function = r.newNativeFuncConstruct(r.builtin_Function, "Function", r.global.FunctionPrototype, 1)
-	r.addToGlobal("Function", r.global.Function)
+func (r *Runtime) newThrowerProperty(configurable bool) Value {
+	thrower := r.getThrower()
+	return &valueProperty{
+		getterFunc:   thrower,
+		setterFunc:   thrower,
+		accessor:     true,
+		configurable: configurable,
+	}
+}
+
+func createFunctionProtoTemplate() *objectTemplate {
+	t := newObjectTemplate()
+	t.protoFactory = func(r *Runtime) *Object {
+		return r.global.ObjectPrototype
+	}
+
+	t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getFunction(), true, false, true) })
+
+	t.putStr("length", func(r *Runtime) Value { return valueProp(_positiveZero, false, false, true) })
+	t.putStr("name", func(r *Runtime) Value { return valueProp(stringEmpty, false, false, true) })
+
+	t.putStr("apply", func(r *Runtime) Value { return r.methodProp(r.functionproto_apply, "apply", 2) })
+	t.putStr("bind", func(r *Runtime) Value { return r.methodProp(r.functionproto_bind, "bind", 1) })
+	t.putStr("call", func(r *Runtime) Value { return r.methodProp(r.functionproto_call, "call", 1) })
+	t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.functionproto_toString, "toString", 0) })
+
+	t.putStr("caller", func(r *Runtime) Value { return r.newThrowerProperty(true) })
+	t.putStr("arguments", func(r *Runtime) Value { return r.newThrowerProperty(true) })
+
+	t.putSym(SymHasInstance, func(r *Runtime) Value {
+		return valueProp(r.newNativeFunc(r.functionproto_hasInstance, "[Symbol.hasInstance]", 1), false, false, false)
+	})
+
+	return t
+}
+
+var functionProtoTemplate *objectTemplate
+var functionProtoTemplateOnce sync.Once
+
+func getFunctionProtoTemplate() *objectTemplate {
+	functionProtoTemplateOnce.Do(func() {
+		functionProtoTemplate = createFunctionProtoTemplate()
+	})
+	return functionProtoTemplate
+}
+
+func (r *Runtime) getFunctionPrototype() *Object {
+	ret := r.global.FunctionPrototype
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.FunctionPrototype = ret
+		r.newTemplatedFuncObject(getFunctionProtoTemplate(), ret, func(FunctionCall) Value {
+			return _undefined
+		}, nil)
+	}
+	return ret
+}
+
+func (r *Runtime) createFunction(v *Object) objectImpl {
+	return r.newNativeFuncConstructObj(v, r.builtin_Function, "Function", r.getFunctionPrototype(), 1)
 }
 
 func (r *Runtime) createAsyncFunctionProto(val *Object) objectImpl {
@@ -229,7 +282,7 @@ func (r *Runtime) createAsyncFunctionProto(val *Object) objectImpl {
 		class:      classObject,
 		val:        val,
 		extensible: true,
-		prototype:  r.global.FunctionPrototype,
+		prototype:  r.getFunctionPrototype(),
 	}
 	o.init()
 
@@ -243,8 +296,9 @@ func (r *Runtime) createAsyncFunctionProto(val *Object) objectImpl {
 func (r *Runtime) getAsyncFunctionPrototype() *Object {
 	var o *Object
 	if o = r.global.AsyncFunctionPrototype; o == nil {
-		o = r.newLazyObject(r.createAsyncFunctionProto)
+		o = &Object{runtime: r}
 		r.global.AsyncFunctionPrototype = o
+		o.self = r.createAsyncFunctionProto(o)
 	}
 	return o
 }
@@ -293,7 +347,7 @@ func (r *Runtime) builtin_genproto_throw(call FunctionCall) Value {
 }
 
 func (r *Runtime) createGeneratorFunctionProto(val *Object) objectImpl {
-	o := newBaseObjectObj(val, r.global.FunctionPrototype, classObject)
+	o := newBaseObjectObj(val, r.getFunctionPrototype(), classObject)
 
 	o._putProp("constructor", r.getGeneratorFunction(), false, false, true)
 	o._putProp("prototype", r.getGeneratorPrototype(), false, false, true)
@@ -305,8 +359,9 @@ func (r *Runtime) createGeneratorFunctionProto(val *Object) objectImpl {
 func (r *Runtime) getGeneratorFunctionPrototype() *Object {
 	var o *Object
 	if o = r.global.GeneratorFunctionPrototype; o == nil {
-		o = r.newLazyObject(r.createGeneratorFunctionProto)
+		o = &Object{runtime: r}
 		r.global.GeneratorFunctionPrototype = o
+		o.self = r.createGeneratorFunctionProto(o)
 	}
 	return o
 }
@@ -330,9 +385,9 @@ func (r *Runtime) createGeneratorProto(val *Object) objectImpl {
 	o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject)
 
 	o._putProp("constructor", r.getGeneratorFunctionPrototype(), false, false, true)
-	o._putProp("next", r.newNativeFunc(r.builtin_genproto_next, nil, "next", nil, 1), true, false, true)
-	o._putProp("return", r.newNativeFunc(r.builtin_genproto_return, nil, "return", nil, 1), true, false, true)
-	o._putProp("throw", r.newNativeFunc(r.builtin_genproto_throw, nil, "throw", nil, 1), true, false, true)
+	o._putProp("next", r.newNativeFunc(r.builtin_genproto_next, "next", 1), true, false, true)
+	o._putProp("return", r.newNativeFunc(r.builtin_genproto_return, "return", 1), true, false, true)
+	o._putProp("throw", r.newNativeFunc(r.builtin_genproto_throw, "throw", 1), true, false, true)
 
 	o._putSym(SymToStringTag, valueProp(asciiString(classGenerator), false, false, true))
 
@@ -348,3 +403,14 @@ func (r *Runtime) getGeneratorPrototype() *Object {
 	}
 	return o
 }
+
+func (r *Runtime) getFunction() *Object {
+	ret := r.global.Function
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Function = ret
+		ret.self = r.createFunction(ret)
+	}
+
+	return ret
+}

+ 79 - 22
builtin_global.go

@@ -8,6 +8,7 @@ import (
 	"regexp"
 	"strconv"
 	"strings"
+	"sync"
 	"unicode/utf8"
 )
 
@@ -70,7 +71,7 @@ func (r *Runtime) _encode(uriString String, unescaped *[256]bool) String {
 		rn, _, err := reader.ReadRune()
 		if err != nil {
 			if err != io.EOF {
-				panic(r.newError(r.global.URIError, "Malformed URI"))
+				panic(r.newError(r.getURIError(), "Malformed URI"))
 			}
 			break
 		}
@@ -127,7 +128,7 @@ func (r *Runtime) _decode(sv String, reservedSet *[256]bool) String {
 		switch s[i] {
 		case '%':
 			if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
-				panic(r.newError(r.global.URIError, "Malformed URI"))
+				panic(r.newError(r.getURIError(), "Malformed URI"))
 			}
 			c := unhex(s[i+1])<<4 | unhex(s[i+2])
 			if !reservedSet[c] {
@@ -183,7 +184,7 @@ func (r *Runtime) _decode(sv String, reservedSet *[256]bool) String {
 		rn, size := utf8.DecodeRune(t)
 		if rn == utf8.RuneError {
 			if size != 3 || t[0] != 0xef || t[1] != 0xbf || t[2] != 0xbd {
-				panic(r.newError(r.global.URIError, "Malformed URI"))
+				panic(r.newError(r.getURIError(), "Malformed URI"))
 			}
 		}
 		us = append(us, rn)
@@ -327,28 +328,84 @@ func (r *Runtime) builtin_unescape(call FunctionCall) Value {
 	return asciiString(asciiBuf)
 }
 
-func (r *Runtime) initGlobalObject() {
-	o := r.globalObject.self
-	o._putProp("globalThis", r.globalObject, true, false, true)
-	o._putProp("NaN", _NaN, false, false, false)
-	o._putProp("undefined", _undefined, false, false, false)
-	o._putProp("Infinity", _positiveInf, false, false, false)
-
-	o._putProp("isNaN", r.newNativeFunc(r.builtin_isNaN, nil, "isNaN", nil, 1), true, false, true)
-	o._putProp("parseInt", r.newNativeFunc(r.builtin_parseInt, nil, "parseInt", nil, 2), true, false, true)
-	o._putProp("parseFloat", r.newNativeFunc(r.builtin_parseFloat, nil, "parseFloat", nil, 1), true, false, true)
-	o._putProp("isFinite", r.newNativeFunc(r.builtin_isFinite, nil, "isFinite", nil, 1), true, false, true)
-	o._putProp("decodeURI", r.newNativeFunc(r.builtin_decodeURI, nil, "decodeURI", nil, 1), true, false, true)
-	o._putProp("decodeURIComponent", r.newNativeFunc(r.builtin_decodeURIComponent, nil, "decodeURIComponent", nil, 1), true, false, true)
-	o._putProp("encodeURI", r.newNativeFunc(r.builtin_encodeURI, nil, "encodeURI", nil, 1), true, false, true)
-	o._putProp("encodeURIComponent", r.newNativeFunc(r.builtin_encodeURIComponent, nil, "encodeURIComponent", nil, 1), true, false, true)
-	o._putProp("escape", r.newNativeFunc(r.builtin_escape, nil, "escape", nil, 1), true, false, true)
-	o._putProp("unescape", r.newNativeFunc(r.builtin_unescape, nil, "unescape", nil, 1), true, false, true)
-
-	o._putSym(SymToStringTag, valueProp(asciiString(classGlobal), false, false, true))
+func createGlobalObjectTemplate() *objectTemplate {
+	t := newObjectTemplate()
+	t.protoFactory = func(r *Runtime) *Object {
+		return r.global.ObjectPrototype
+	}
+
+	t.putStr("Object", func(r *Runtime) Value { return valueProp(r.getObject(), true, false, true) })
+	t.putStr("Function", func(r *Runtime) Value { return valueProp(r.getFunction(), true, false, true) })
+	t.putStr("Array", func(r *Runtime) Value { return valueProp(r.getArray(), true, false, true) })
+	t.putStr("String", func(r *Runtime) Value { return valueProp(r.getString(), true, false, true) })
+	t.putStr("Number", func(r *Runtime) Value { return valueProp(r.getNumber(), true, false, true) })
+	t.putStr("RegExp", func(r *Runtime) Value { return valueProp(r.getRegExp(), true, false, true) })
+	t.putStr("Date", func(r *Runtime) Value { return valueProp(r.getDate(), true, false, true) })
+	t.putStr("Boolean", func(r *Runtime) Value { return valueProp(r.getBoolean(), true, false, true) })
+	t.putStr("Proxy", func(r *Runtime) Value { return valueProp(r.getProxy(), true, false, true) })
+	t.putStr("Reflect", func(r *Runtime) Value { return valueProp(r.getReflect(), true, false, true) })
+	t.putStr("Error", func(r *Runtime) Value { return valueProp(r.getError(), true, false, true) })
+	t.putStr("AggregateError", func(r *Runtime) Value { return valueProp(r.getAggregateError(), true, false, true) })
+	t.putStr("TypeError", func(r *Runtime) Value { return valueProp(r.getTypeError(), true, false, true) })
+	t.putStr("ReferenceError", func(r *Runtime) Value { return valueProp(r.getReferenceError(), true, false, true) })
+	t.putStr("SyntaxError", func(r *Runtime) Value { return valueProp(r.getSyntaxError(), true, false, true) })
+	t.putStr("RangeError", func(r *Runtime) Value { return valueProp(r.getRangeError(), true, false, true) })
+	t.putStr("EvalError", func(r *Runtime) Value { return valueProp(r.getEvalError(), true, false, true) })
+	t.putStr("URIError", func(r *Runtime) Value { return valueProp(r.getURIError(), true, false, true) })
+	t.putStr("GoError", func(r *Runtime) Value { return valueProp(r.getGoError(), true, false, true) })
+
+	t.putStr("eval", func(r *Runtime) Value { return valueProp(r.getEval(), true, false, true) })
+
+	t.putStr("Math", func(r *Runtime) Value { return valueProp(r.getMath(), true, false, true) })
+	t.putStr("JSON", func(r *Runtime) Value { return valueProp(r.getJSON(), true, false, true) })
+	addTypedArrays(t)
+	t.putStr("Symbol", func(r *Runtime) Value { return valueProp(r.getSymbol(), true, false, true) })
+	t.putStr("WeakSet", func(r *Runtime) Value { return valueProp(r.getWeakSet(), true, false, true) })
+	t.putStr("WeakMap", func(r *Runtime) Value { return valueProp(r.getWeakMap(), true, false, true) })
+	t.putStr("Map", func(r *Runtime) Value { return valueProp(r.getMap(), true, false, true) })
+	t.putStr("Set", func(r *Runtime) Value { return valueProp(r.getSet(), true, false, true) })
+	t.putStr("Promise", func(r *Runtime) Value { return valueProp(r.getPromise(), true, false, true) })
+
+	t.putStr("globalThis", func(r *Runtime) Value { return valueProp(r.globalObject, true, false, true) })
+	t.putStr("NaN", func(r *Runtime) Value { return valueProp(_NaN, false, false, false) })
+	t.putStr("undefined", func(r *Runtime) Value { return valueProp(_undefined, false, false, false) })
+	t.putStr("Infinity", func(r *Runtime) Value { return valueProp(_positiveInf, false, false, false) })
+
+	t.putStr("isNaN", func(r *Runtime) Value { return r.methodProp(r.builtin_isNaN, "isNaN", 1) })
+	t.putStr("parseInt", func(r *Runtime) Value { return valueProp(r.getParseInt(), true, false, true) })
+	t.putStr("parseFloat", func(r *Runtime) Value { return valueProp(r.getParseFloat(), true, false, true) })
+	t.putStr("isFinite", func(r *Runtime) Value { return r.methodProp(r.builtin_isFinite, "isFinite", 1) })
+	t.putStr("decodeURI", func(r *Runtime) Value { return r.methodProp(r.builtin_decodeURI, "decodeURI", 1) })
+	t.putStr("decodeURIComponent", func(r *Runtime) Value { return r.methodProp(r.builtin_decodeURIComponent, "decodeURIComponent", 1) })
+	t.putStr("encodeURI", func(r *Runtime) Value { return r.methodProp(r.builtin_encodeURI, "encodeURI", 1) })
+	t.putStr("encodeURIComponent", func(r *Runtime) Value { return r.methodProp(r.builtin_encodeURIComponent, "encodeURIComponent", 1) })
+	t.putStr("escape", func(r *Runtime) Value { return r.methodProp(r.builtin_escape, "escape", 1) })
+	t.putStr("unescape", func(r *Runtime) Value { return r.methodProp(r.builtin_unescape, "unescape", 1) })
 
 	// TODO: Annex B
 
+	t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString(classGlobal), false, false, true) })
+
+	return t
+}
+
+var globalObjectTemplate *objectTemplate
+var globalObjectTemplateOnce sync.Once
+
+func getGlobalObjectTemplate() *objectTemplate {
+	globalObjectTemplateOnce.Do(func() {
+		globalObjectTemplate = createGlobalObjectTemplate()
+	})
+	return globalObjectTemplate
+}
+
+func (r *Runtime) getEval() *Object {
+	ret := r.global.Eval
+	if ret == nil {
+		ret = r.newNativeFunc(r.builtin_eval, "eval", 1)
+		r.global.Eval = ret
+	}
+	return ret
 }
 
 func digitVal(d byte) int {

+ 14 - 10
builtin_json.go

@@ -22,14 +22,14 @@ func (r *Runtime) builtinJSON_parse(call FunctionCall) Value {
 
 	value, err := r.builtinJSON_decodeValue(d)
 	if errors.Is(err, io.EOF) {
-		panic(r.newError(r.global.SyntaxError, "Unexpected end of JSON input (%v)", err.Error()))
+		panic(r.newError(r.getSyntaxError(), "Unexpected end of JSON input (%v)", err.Error()))
 	}
 	if err != nil {
-		panic(r.newError(r.global.SyntaxError, err.Error()))
+		panic(r.newError(r.getSyntaxError(), err.Error()))
 	}
 
 	if tok, err := d.Token(); err != io.EOF {
-		panic(r.newError(r.global.SyntaxError, "Unexpected token at the end: %v", tok))
+		panic(r.newError(r.getSyntaxError(), "Unexpected token at the end: %v", tok))
 	}
 
 	var reviver func(FunctionCall) Value
@@ -522,11 +522,15 @@ func (ctx *_builtinJSON_stringifyContext) quote(str String) {
 	ctx.buf.WriteByte('"')
 }
 
-func (r *Runtime) initJSON() {
-	JSON := r.newBaseObject(r.global.ObjectPrototype, classObject)
-	JSON._putProp("parse", r.newNativeFunc(r.builtinJSON_parse, nil, "parse", nil, 2), true, false, true)
-	JSON._putProp("stringify", r.newNativeFunc(r.builtinJSON_stringify, nil, "stringify", nil, 3), true, false, true)
-	JSON._putSym(SymToStringTag, valueProp(asciiString(classJSON), false, false, true))
-
-	r.addToGlobal("JSON", JSON.val)
+func (r *Runtime) getJSON() *Object {
+	ret := r.global.JSON
+	if ret == nil {
+		JSON := r.newBaseObject(r.global.ObjectPrototype, classObject)
+		ret = JSON.val
+		r.global.JSON = ret
+		JSON._putProp("parse", r.newNativeFunc(r.builtinJSON_parse, "parse", 2), true, false, true)
+		JSON._putProp("stringify", r.newNativeFunc(r.builtinJSON_stringify, "stringify", 3), true, false, true)
+		JSON._putSym(SymToStringTag, valueProp(asciiString(classJSON), false, false, true))
+	}
+	return ret
 }

+ 30 - 19
builtin_map.go

@@ -270,24 +270,24 @@ func (r *Runtime) mapIterProto_next(call FunctionCall) Value {
 func (r *Runtime) createMapProto(val *Object) objectImpl {
 	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
 
-	o._putProp("constructor", r.global.Map, true, false, true)
-	o._putProp("clear", r.newNativeFunc(r.mapProto_clear, nil, "clear", nil, 0), true, false, true)
-	r.global.mapAdder = r.newNativeFunc(r.mapProto_set, nil, "set", nil, 2)
+	o._putProp("constructor", r.getMap(), true, false, true)
+	o._putProp("clear", r.newNativeFunc(r.mapProto_clear, "clear", 0), true, false, true)
+	r.global.mapAdder = r.newNativeFunc(r.mapProto_set, "set", 2)
 	o._putProp("set", r.global.mapAdder, true, false, true)
-	o._putProp("delete", r.newNativeFunc(r.mapProto_delete, nil, "delete", nil, 1), true, false, true)
-	o._putProp("forEach", r.newNativeFunc(r.mapProto_forEach, nil, "forEach", nil, 1), true, false, true)
-	o._putProp("has", r.newNativeFunc(r.mapProto_has, nil, "has", nil, 1), true, false, true)
-	o._putProp("get", r.newNativeFunc(r.mapProto_get, nil, "get", nil, 1), true, false, true)
+	o._putProp("delete", r.newNativeFunc(r.mapProto_delete, "delete", 1), true, false, true)
+	o._putProp("forEach", r.newNativeFunc(r.mapProto_forEach, "forEach", 1), true, false, true)
+	o._putProp("has", r.newNativeFunc(r.mapProto_has, "has", 1), true, false, true)
+	o._putProp("get", r.newNativeFunc(r.mapProto_get, "get", 1), true, false, true)
 	o.setOwnStr("size", &valueProperty{
-		getterFunc:   r.newNativeFunc(r.mapProto_getSize, nil, "get size", nil, 0),
+		getterFunc:   r.newNativeFunc(r.mapProto_getSize, "get size", 0),
 		accessor:     true,
 		writable:     true,
 		configurable: true,
 	}, true)
-	o._putProp("keys", r.newNativeFunc(r.mapProto_keys, nil, "keys", nil, 0), true, false, true)
-	o._putProp("values", r.newNativeFunc(r.mapProto_values, nil, "values", nil, 0), true, false, true)
+	o._putProp("keys", r.newNativeFunc(r.mapProto_keys, "keys", 0), true, false, true)
+	o._putProp("values", r.newNativeFunc(r.mapProto_values, "values", 0), true, false, true)
 
-	entriesFunc := r.newNativeFunc(r.mapProto_entries, nil, "entries", nil, 0)
+	entriesFunc := r.newNativeFunc(r.mapProto_entries, "entries", 0)
 	o._putProp("entries", entriesFunc, true, false, true)
 	o._putSym(SymIterator, valueProp(entriesFunc, true, false, true))
 	o._putSym(SymToStringTag, valueProp(asciiString(classMap), false, false, true))
@@ -296,7 +296,7 @@ func (r *Runtime) createMapProto(val *Object) objectImpl {
 }
 
 func (r *Runtime) createMap(val *Object) objectImpl {
-	o := r.newNativeConstructOnly(val, r.builtin_newMap, r.global.MapPrototype, "Map", 0)
+	o := r.newNativeConstructOnly(val, r.builtin_newMap, r.getMapPrototype(), "Map", 0)
 	r.putSpeciesReturnThis(o)
 
 	return o
@@ -305,7 +305,7 @@ func (r *Runtime) createMap(val *Object) objectImpl {
 func (r *Runtime) createMapIterProto(val *Object) objectImpl {
 	o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject)
 
-	o._putProp("next", r.newNativeFunc(r.mapIterProto_next, nil, "next", nil, 0), true, false, true)
+	o._putProp("next", r.newNativeFunc(r.mapIterProto_next, "next", 0), true, false, true)
 	o._putSym(SymToStringTag, valueProp(asciiString(classMapIterator), false, false, true))
 
 	return o
@@ -321,11 +321,22 @@ func (r *Runtime) getMapIteratorPrototype() *Object {
 	return o
 }
 
-func (r *Runtime) initMap() {
-	r.global.MapIteratorPrototype = r.newLazyObject(r.createMapIterProto)
-
-	r.global.MapPrototype = r.newLazyObject(r.createMapProto)
-	r.global.Map = r.newLazyObject(r.createMap)
+func (r *Runtime) getMapPrototype() *Object {
+	ret := r.global.MapPrototype
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.MapPrototype = ret
+		ret.self = r.createMapProto(ret)
+	}
+	return ret
+}
 
-	r.addToGlobal("Map", r.global.Map)
+func (r *Runtime) getMap() *Object {
+	ret := r.global.Map
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Map = ret
+		ret.self = r.createMap(ret)
+	}
+	return ret
 }

+ 74 - 59
builtin_math.go

@@ -3,6 +3,7 @@ package goja
 import (
 	"math"
 	"math/bits"
+	"sync"
 )
 
 func (r *Runtime) math_abs(call FunctionCall) Value {
@@ -280,64 +281,78 @@ func (r *Runtime) math_trunc(call FunctionCall) Value {
 	return floatToValue(math.Trunc(arg.ToFloat()))
 }
 
-func (r *Runtime) createMath(val *Object) objectImpl {
-	m := &baseObject{
-		class:      classObject,
-		val:        val,
-		extensible: true,
-		prototype:  r.global.ObjectPrototype,
+func createMathTemplate() *objectTemplate {
+	t := newObjectTemplate()
+	t.protoFactory = func(r *Runtime) *Object {
+		return r.global.ObjectPrototype
 	}
-	m.init()
-
-	m._putProp("E", valueFloat(math.E), false, false, false)
-	m._putProp("LN10", valueFloat(math.Ln10), false, false, false)
-	m._putProp("LN2", valueFloat(math.Ln2), false, false, false)
-	m._putProp("LOG10E", valueFloat(math.Log10E), false, false, false)
-	m._putProp("LOG2E", valueFloat(math.Log2E), false, false, false)
-	m._putProp("PI", valueFloat(math.Pi), false, false, false)
-	m._putProp("SQRT1_2", valueFloat(sqrt1_2), false, false, false)
-	m._putProp("SQRT2", valueFloat(math.Sqrt2), false, false, false)
-	m._putSym(SymToStringTag, valueProp(asciiString(classMath), false, false, true))
-
-	m._putProp("abs", r.newNativeFunc(r.math_abs, nil, "abs", nil, 1), true, false, true)
-	m._putProp("acos", r.newNativeFunc(r.math_acos, nil, "acos", nil, 1), true, false, true)
-	m._putProp("acosh", r.newNativeFunc(r.math_acosh, nil, "acosh", nil, 1), true, false, true)
-	m._putProp("asin", r.newNativeFunc(r.math_asin, nil, "asin", nil, 1), true, false, true)
-	m._putProp("asinh", r.newNativeFunc(r.math_asinh, nil, "asinh", nil, 1), true, false, true)
-	m._putProp("atan", r.newNativeFunc(r.math_atan, nil, "atan", nil, 1), true, false, true)
-	m._putProp("atanh", r.newNativeFunc(r.math_atanh, nil, "atanh", nil, 1), true, false, true)
-	m._putProp("atan2", r.newNativeFunc(r.math_atan2, nil, "atan2", nil, 2), true, false, true)
-	m._putProp("cbrt", r.newNativeFunc(r.math_cbrt, nil, "cbrt", nil, 1), true, false, true)
-	m._putProp("ceil", r.newNativeFunc(r.math_ceil, nil, "ceil", nil, 1), true, false, true)
-	m._putProp("clz32", r.newNativeFunc(r.math_clz32, nil, "clz32", nil, 1), true, false, true)
-	m._putProp("cos", r.newNativeFunc(r.math_cos, nil, "cos", nil, 1), true, false, true)
-	m._putProp("cosh", r.newNativeFunc(r.math_cosh, nil, "cosh", nil, 1), true, false, true)
-	m._putProp("exp", r.newNativeFunc(r.math_exp, nil, "exp", nil, 1), true, false, true)
-	m._putProp("expm1", r.newNativeFunc(r.math_expm1, nil, "expm1", nil, 1), true, false, true)
-	m._putProp("floor", r.newNativeFunc(r.math_floor, nil, "floor", nil, 1), true, false, true)
-	m._putProp("fround", r.newNativeFunc(r.math_fround, nil, "fround", nil, 1), true, false, true)
-	m._putProp("hypot", r.newNativeFunc(r.math_hypot, nil, "hypot", nil, 2), true, false, true)
-	m._putProp("imul", r.newNativeFunc(r.math_imul, nil, "imul", nil, 2), true, false, true)
-	m._putProp("log", r.newNativeFunc(r.math_log, nil, "log", nil, 1), true, false, true)
-	m._putProp("log1p", r.newNativeFunc(r.math_log1p, nil, "log1p", nil, 1), true, false, true)
-	m._putProp("log10", r.newNativeFunc(r.math_log10, nil, "log10", nil, 1), true, false, true)
-	m._putProp("log2", r.newNativeFunc(r.math_log2, nil, "log2", nil, 1), true, false, true)
-	m._putProp("max", r.newNativeFunc(r.math_max, nil, "max", nil, 2), true, false, true)
-	m._putProp("min", r.newNativeFunc(r.math_min, nil, "min", nil, 2), true, false, true)
-	m._putProp("pow", r.newNativeFunc(r.math_pow, nil, "pow", nil, 2), true, false, true)
-	m._putProp("random", r.newNativeFunc(r.math_random, nil, "random", nil, 0), true, false, true)
-	m._putProp("round", r.newNativeFunc(r.math_round, nil, "round", nil, 1), true, false, true)
-	m._putProp("sign", r.newNativeFunc(r.math_sign, nil, "sign", nil, 1), true, false, true)
-	m._putProp("sin", r.newNativeFunc(r.math_sin, nil, "sin", nil, 1), true, false, true)
-	m._putProp("sinh", r.newNativeFunc(r.math_sinh, nil, "sinh", nil, 1), true, false, true)
-	m._putProp("sqrt", r.newNativeFunc(r.math_sqrt, nil, "sqrt", nil, 1), true, false, true)
-	m._putProp("tan", r.newNativeFunc(r.math_tan, nil, "tan", nil, 1), true, false, true)
-	m._putProp("tanh", r.newNativeFunc(r.math_tanh, nil, "tanh", nil, 1), true, false, true)
-	m._putProp("trunc", r.newNativeFunc(r.math_trunc, nil, "trunc", nil, 1), true, false, true)
-
-	return m
-}
-
-func (r *Runtime) initMath() {
-	r.addToGlobal("Math", r.newLazyObject(r.createMath))
+
+	t.putStr("E", func(r *Runtime) Value { return valueProp(valueFloat(math.E), false, false, false) })
+	t.putStr("LN10", func(r *Runtime) Value { return valueProp(valueFloat(math.Ln10), false, false, false) })
+	t.putStr("LN2", func(r *Runtime) Value { return valueProp(valueFloat(math.Ln2), false, false, false) })
+	t.putStr("LOG10E", func(r *Runtime) Value { return valueProp(valueFloat(math.Log10E), false, false, false) })
+	t.putStr("LOG2E", func(r *Runtime) Value { return valueProp(valueFloat(math.Log2E), false, false, false) })
+	t.putStr("PI", func(r *Runtime) Value { return valueProp(valueFloat(math.Pi), false, false, false) })
+	t.putStr("SQRT1_2", func(r *Runtime) Value { return valueProp(valueFloat(sqrt1_2), false, false, false) })
+	t.putStr("SQRT2", func(r *Runtime) Value { return valueProp(valueFloat(math.Sqrt2), false, false, false) })
+
+	t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString(classMath), false, false, true) })
+
+	t.putStr("abs", func(r *Runtime) Value { return r.methodProp(r.math_abs, "abs", 1) })
+	t.putStr("acos", func(r *Runtime) Value { return r.methodProp(r.math_acos, "acos", 1) })
+	t.putStr("acosh", func(r *Runtime) Value { return r.methodProp(r.math_acosh, "acosh", 1) })
+	t.putStr("asin", func(r *Runtime) Value { return r.methodProp(r.math_asin, "asin", 1) })
+	t.putStr("asinh", func(r *Runtime) Value { return r.methodProp(r.math_asinh, "asinh", 1) })
+	t.putStr("atan", func(r *Runtime) Value { return r.methodProp(r.math_atan, "atan", 1) })
+	t.putStr("atanh", func(r *Runtime) Value { return r.methodProp(r.math_atanh, "atanh", 1) })
+	t.putStr("atan2", func(r *Runtime) Value { return r.methodProp(r.math_atan2, "atan2", 2) })
+	t.putStr("cbrt", func(r *Runtime) Value { return r.methodProp(r.math_cbrt, "cbrt", 1) })
+	t.putStr("ceil", func(r *Runtime) Value { return r.methodProp(r.math_ceil, "ceil", 1) })
+	t.putStr("clz32", func(r *Runtime) Value { return r.methodProp(r.math_clz32, "clz32", 1) })
+	t.putStr("cos", func(r *Runtime) Value { return r.methodProp(r.math_cos, "cos", 1) })
+	t.putStr("cosh", func(r *Runtime) Value { return r.methodProp(r.math_cosh, "cosh", 1) })
+	t.putStr("exp", func(r *Runtime) Value { return r.methodProp(r.math_exp, "exp", 1) })
+	t.putStr("expm1", func(r *Runtime) Value { return r.methodProp(r.math_expm1, "expm1", 1) })
+	t.putStr("floor", func(r *Runtime) Value { return r.methodProp(r.math_floor, "floor", 1) })
+	t.putStr("fround", func(r *Runtime) Value { return r.methodProp(r.math_fround, "fround", 1) })
+	t.putStr("hypot", func(r *Runtime) Value { return r.methodProp(r.math_hypot, "hypot", 2) })
+	t.putStr("imul", func(r *Runtime) Value { return r.methodProp(r.math_imul, "imul", 2) })
+	t.putStr("log", func(r *Runtime) Value { return r.methodProp(r.math_log, "log", 1) })
+	t.putStr("log1p", func(r *Runtime) Value { return r.methodProp(r.math_log1p, "log1p", 1) })
+	t.putStr("log10", func(r *Runtime) Value { return r.methodProp(r.math_log10, "log10", 1) })
+	t.putStr("log2", func(r *Runtime) Value { return r.methodProp(r.math_log2, "log2", 1) })
+	t.putStr("max", func(r *Runtime) Value { return r.methodProp(r.math_max, "max", 2) })
+	t.putStr("min", func(r *Runtime) Value { return r.methodProp(r.math_min, "min", 2) })
+	t.putStr("pow", func(r *Runtime) Value { return r.methodProp(r.math_pow, "pow", 2) })
+	t.putStr("random", func(r *Runtime) Value { return r.methodProp(r.math_random, "random", 0) })
+	t.putStr("round", func(r *Runtime) Value { return r.methodProp(r.math_round, "round", 1) })
+	t.putStr("sign", func(r *Runtime) Value { return r.methodProp(r.math_sign, "sign", 1) })
+	t.putStr("sin", func(r *Runtime) Value { return r.methodProp(r.math_sin, "sin", 1) })
+	t.putStr("sinh", func(r *Runtime) Value { return r.methodProp(r.math_sinh, "sinh", 1) })
+	t.putStr("sqrt", func(r *Runtime) Value { return r.methodProp(r.math_sqrt, "sqrt", 1) })
+	t.putStr("tan", func(r *Runtime) Value { return r.methodProp(r.math_tan, "tan", 1) })
+	t.putStr("tanh", func(r *Runtime) Value { return r.methodProp(r.math_tanh, "tanh", 1) })
+	t.putStr("trunc", func(r *Runtime) Value { return r.methodProp(r.math_trunc, "trunc", 1) })
+
+	return t
+}
+
+var mathTemplate *objectTemplate
+var mathTemplateOnce sync.Once
+
+func getMathTemplate() *objectTemplate {
+	mathTemplateOnce.Do(func() {
+		mathTemplate = createMathTemplate()
+	})
+	return mathTemplate
+}
+
+func (r *Runtime) getMath() *Object {
+	ret := r.global.Math
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Math = ret
+		r.newTemplatedObject(getMathTemplate(), ret)
+	}
+	return ret
 }

+ 114 - 31
builtin_number.go

@@ -2,6 +2,7 @@ package goja
 
 import (
 	"math"
+	"sync"
 
 	"github.com/dop251/goja/ftoa"
 )
@@ -19,6 +20,9 @@ func (r *Runtime) toNumber(v Value) Value {
 				return t.valueOf()
 			}
 		}
+		if t == r.global.NumberPrototype {
+			return _positiveZero
+		}
 	}
 	panic(r.NewTypeError("Value is not a number: %s", v))
 }
@@ -46,6 +50,9 @@ func (r *Runtime) numberproto_toString(call FunctionCall) Value {
 				}
 			}
 		}
+		if t == r.global.NumberPrototype {
+			return asciiString("0")
+		}
 	}
 	if numVal == nil {
 		panic(r.NewTypeError("Value is not a number"))
@@ -58,7 +65,7 @@ func (r *Runtime) numberproto_toString(call FunctionCall) Value {
 	}
 
 	if radix < 2 || radix > 36 {
-		panic(r.newError(r.global.RangeError, "toString() radix argument must be between 2 and 36"))
+		panic(r.newError(r.getRangeError(), "toString() radix argument must be between 2 and 36"))
 	}
 
 	num := numVal.ToFloat()
@@ -87,7 +94,7 @@ func (r *Runtime) numberproto_toFixed(call FunctionCall) Value {
 	prec := call.Argument(0).ToInteger()
 
 	if prec < 0 || prec > 100 {
-		panic(r.newError(r.global.RangeError, "toFixed() precision must be between 0 and 100"))
+		panic(r.newError(r.getRangeError(), "toFixed() precision must be between 0 and 100"))
 	}
 	if math.IsNaN(num) {
 		return stringNaN
@@ -116,7 +123,7 @@ func (r *Runtime) numberproto_toExponential(call FunctionCall) Value {
 	}
 
 	if prec < 0 || prec > 100 {
-		panic(r.newError(r.global.RangeError, "toExponential() precision must be between 0 and 100"))
+		panic(r.newError(r.getRangeError(), "toExponential() precision must be between 0 and 100"))
 	}
 
 	return asciiString(fToStr(num, ftoa.ModeExponential, int(prec+1)))
@@ -141,7 +148,7 @@ func (r *Runtime) numberproto_toPrecision(call FunctionCall) Value {
 		return stringNegInfinity
 	}
 	if prec < 1 || prec > 100 {
-		panic(r.newError(r.global.RangeError, "toPrecision() precision must be between 1 and 100"))
+		panic(r.newError(r.getRangeError(), "toPrecision() precision must be between 1 and 100"))
 	}
 
 	return asciiString(fToStr(num, ftoa.ModePrecision, int(prec)))
@@ -189,32 +196,108 @@ func (r *Runtime) number_isSafeInteger(call FunctionCall) Value {
 	return valueFalse
 }
 
-func (r *Runtime) initNumber() {
-	r.global.NumberPrototype = r.newPrimitiveObject(valueInt(0), r.global.ObjectPrototype, classNumber)
-	o := r.global.NumberPrototype.self
-	o._putProp("toExponential", r.newNativeFunc(r.numberproto_toExponential, nil, "toExponential", nil, 1), true, false, true)
-	o._putProp("toFixed", r.newNativeFunc(r.numberproto_toFixed, nil, "toFixed", nil, 1), true, false, true)
-	o._putProp("toLocaleString", r.newNativeFunc(r.numberproto_toString, nil, "toLocaleString", nil, 0), true, false, true)
-	o._putProp("toPrecision", r.newNativeFunc(r.numberproto_toPrecision, nil, "toPrecision", nil, 1), true, false, true)
-	o._putProp("toString", r.newNativeFunc(r.numberproto_toString, nil, "toString", nil, 1), true, false, true)
-	o._putProp("valueOf", r.newNativeFunc(r.numberproto_valueOf, nil, "valueOf", nil, 0), true, false, true)
-
-	r.global.Number = r.newNativeFunc(r.builtin_Number, r.builtin_newNumber, "Number", r.global.NumberPrototype, 1)
-	o = r.global.Number.self
-	o._putProp("EPSILON", _epsilon, false, false, false)
-	o._putProp("isFinite", r.newNativeFunc(r.number_isFinite, nil, "isFinite", nil, 1), true, false, true)
-	o._putProp("isInteger", r.newNativeFunc(r.number_isInteger, nil, "isInteger", nil, 1), true, false, true)
-	o._putProp("isNaN", r.newNativeFunc(r.number_isNaN, nil, "isNaN", nil, 1), true, false, true)
-	o._putProp("isSafeInteger", r.newNativeFunc(r.number_isSafeInteger, nil, "isSafeInteger", nil, 1), true, false, true)
-	o._putProp("MAX_SAFE_INTEGER", valueInt(maxInt-1), false, false, false)
-	o._putProp("MIN_SAFE_INTEGER", valueInt(-(maxInt - 1)), false, false, false)
-	o._putProp("MIN_VALUE", valueFloat(math.SmallestNonzeroFloat64), false, false, false)
-	o._putProp("MAX_VALUE", valueFloat(math.MaxFloat64), false, false, false)
-	o._putProp("NaN", _NaN, false, false, false)
-	o._putProp("NEGATIVE_INFINITY", _negativeInf, false, false, false)
-	o._putProp("parseFloat", r.Get("parseFloat"), true, false, true)
-	o._putProp("parseInt", r.Get("parseInt"), true, false, true)
-	o._putProp("POSITIVE_INFINITY", _positiveInf, false, false, false)
-	r.addToGlobal("Number", r.global.Number)
+func createNumberProtoTemplate() *objectTemplate {
+	t := newObjectTemplate()
+	t.protoFactory = func(r *Runtime) *Object {
+		return r.global.ObjectPrototype
+	}
+
+	t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getNumber(), true, false, true) })
+
+	t.putStr("toExponential", func(r *Runtime) Value { return r.methodProp(r.numberproto_toExponential, "toExponential", 1) })
+	t.putStr("toFixed", func(r *Runtime) Value { return r.methodProp(r.numberproto_toFixed, "toFixed", 1) })
+	t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.numberproto_toString, "toLocaleString", 0) })
+	t.putStr("toPrecision", func(r *Runtime) Value { return r.methodProp(r.numberproto_toPrecision, "toPrecision", 1) })
+	t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.numberproto_toString, "toString", 1) })
+	t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.numberproto_valueOf, "valueOf", 0) })
+
+	return t
+}
+
+var numberProtoTemplate *objectTemplate
+var numberProtoTemplateOnce sync.Once
 
+func getNumberProtoTemplate() *objectTemplate {
+	numberProtoTemplateOnce.Do(func() {
+		numberProtoTemplate = createNumberProtoTemplate()
+	})
+	return numberProtoTemplate
+}
+
+func (r *Runtime) getNumberPrototype() *Object {
+	ret := r.global.NumberPrototype
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.NumberPrototype = ret
+		o := r.newTemplatedObject(getNumberProtoTemplate(), ret)
+		o.class = classNumber
+	}
+	return ret
+}
+
+func (r *Runtime) getParseFloat() *Object {
+	ret := r.global.parseFloat
+	if ret == nil {
+		ret = r.newNativeFunc(r.builtin_parseFloat, "parseFloat", 1)
+		r.global.parseFloat = ret
+	}
+	return ret
+}
+
+func (r *Runtime) getParseInt() *Object {
+	ret := r.global.parseInt
+	if ret == nil {
+		ret = r.newNativeFunc(r.builtin_parseInt, "parseInt", 2)
+		r.global.parseInt = ret
+	}
+	return ret
+}
+
+func createNumberTemplate() *objectTemplate {
+	t := newObjectTemplate()
+	t.protoFactory = func(r *Runtime) *Object {
+		return r.getFunctionPrototype()
+	}
+	t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(1), false, false, true) })
+	t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("Number"), false, false, true) })
+
+	t.putStr("prototype", func(r *Runtime) Value { return valueProp(r.getNumberPrototype(), false, false, false) })
+
+	t.putStr("EPSILON", func(r *Runtime) Value { return valueProp(_epsilon, false, false, false) })
+	t.putStr("isFinite", func(r *Runtime) Value { return r.methodProp(r.number_isFinite, "isFinite", 1) })
+	t.putStr("isInteger", func(r *Runtime) Value { return r.methodProp(r.number_isInteger, "isInteger", 1) })
+	t.putStr("isNaN", func(r *Runtime) Value { return r.methodProp(r.number_isNaN, "isNaN", 1) })
+	t.putStr("isSafeInteger", func(r *Runtime) Value { return r.methodProp(r.number_isSafeInteger, "isSafeInteger", 1) })
+	t.putStr("MAX_SAFE_INTEGER", func(r *Runtime) Value { return valueProp(valueInt(maxInt-1), false, false, false) })
+	t.putStr("MIN_SAFE_INTEGER", func(r *Runtime) Value { return valueProp(valueInt(-(maxInt - 1)), false, false, false) })
+	t.putStr("MIN_VALUE", func(r *Runtime) Value { return valueProp(valueFloat(math.SmallestNonzeroFloat64), false, false, false) })
+	t.putStr("MAX_VALUE", func(r *Runtime) Value { return valueProp(valueFloat(math.MaxFloat64), false, false, false) })
+	t.putStr("NaN", func(r *Runtime) Value { return valueProp(_NaN, false, false, false) })
+	t.putStr("NEGATIVE_INFINITY", func(r *Runtime) Value { return valueProp(_negativeInf, false, false, false) })
+	t.putStr("parseFloat", func(r *Runtime) Value { return valueProp(r.getParseFloat(), true, false, true) })
+	t.putStr("parseInt", func(r *Runtime) Value { return valueProp(r.getParseInt(), true, false, true) })
+	t.putStr("POSITIVE_INFINITY", func(r *Runtime) Value { return valueProp(_positiveInf, false, false, false) })
+
+	return t
+}
+
+var numberTemplate *objectTemplate
+var numberTemplateOnce sync.Once
+
+func getNumberTemplate() *objectTemplate {
+	numberTemplateOnce.Do(func() {
+		numberTemplate = createNumberTemplate()
+	})
+	return numberTemplate
+}
+
+func (r *Runtime) getNumber() *Object {
+	ret := r.global.Number
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Number = ret
+		r.newTemplatedFuncObject(getNumberTemplate(), ret, r.builtin_Number,
+			r.wrapNativeConstruct(r.builtin_newNumber, ret, r.getNumberPrototype()))
+	}
+	return ret
 }

+ 114 - 42
builtin_object.go

@@ -2,10 +2,11 @@ package goja
 
 import (
 	"fmt"
+	"sync"
 )
 
 func (r *Runtime) builtin_Object(args []Value, newTarget *Object) *Object {
-	if newTarget != nil && newTarget != r.global.Object {
+	if newTarget != nil && newTarget != r.getObject() {
 		proto := r.getPrototypeFromCtor(newTarget, nil, r.global.ObjectPrototype)
 		return r.newBaseObject(proto, classObject).val
 	}
@@ -595,45 +596,116 @@ func (r *Runtime) object_hasOwn(call FunctionCall) Value {
 	}
 }
 
-func (r *Runtime) initObject() {
-	o := r.global.ObjectPrototype.self
-	o._putProp("toString", r.newNativeFunc(r.objectproto_toString, nil, "toString", nil, 0), true, false, true)
-	o._putProp("toLocaleString", r.newNativeFunc(r.objectproto_toLocaleString, nil, "toLocaleString", nil, 0), true, false, true)
-	o._putProp("valueOf", r.newNativeFunc(r.objectproto_valueOf, nil, "valueOf", nil, 0), true, false, true)
-	o._putProp("hasOwnProperty", r.newNativeFunc(r.objectproto_hasOwnProperty, nil, "hasOwnProperty", nil, 1), true, false, true)
-	o._putProp("isPrototypeOf", r.newNativeFunc(r.objectproto_isPrototypeOf, nil, "isPrototypeOf", nil, 1), true, false, true)
-	o._putProp("propertyIsEnumerable", r.newNativeFunc(r.objectproto_propertyIsEnumerable, nil, "propertyIsEnumerable", nil, 1), true, false, true)
-	o.defineOwnPropertyStr(__proto__, PropertyDescriptor{
-		Getter:       r.newNativeFunc(r.objectproto_getProto, nil, "get __proto__", nil, 0),
-		Setter:       r.newNativeFunc(r.objectproto_setProto, nil, "set __proto__", nil, 1),
-		Configurable: FLAG_TRUE,
-	}, true)
-
-	r.global.Object = r.newNativeConstructOnly(nil, r.builtin_Object, r.global.ObjectPrototype, "Object", 1).val
-	r.global.ObjectPrototype.self._putProp("constructor", r.global.Object, true, false, true)
-	o = r.global.Object.self
-	o._putProp("assign", r.newNativeFunc(r.object_assign, nil, "assign", nil, 2), true, false, true)
-	o._putProp("defineProperty", r.newNativeFunc(r.object_defineProperty, nil, "defineProperty", nil, 3), true, false, true)
-	o._putProp("defineProperties", r.newNativeFunc(r.object_defineProperties, nil, "defineProperties", nil, 2), true, false, true)
-	o._putProp("entries", r.newNativeFunc(r.object_entries, nil, "entries", nil, 1), true, false, true)
-	o._putProp("getOwnPropertyDescriptor", r.newNativeFunc(r.object_getOwnPropertyDescriptor, nil, "getOwnPropertyDescriptor", nil, 2), true, false, true)
-	o._putProp("getOwnPropertyDescriptors", r.newNativeFunc(r.object_getOwnPropertyDescriptors, nil, "getOwnPropertyDescriptors", nil, 1), true, false, true)
-	o._putProp("getPrototypeOf", r.newNativeFunc(r.object_getPrototypeOf, nil, "getPrototypeOf", nil, 1), true, false, true)
-	o._putProp("is", r.newNativeFunc(r.object_is, nil, "is", nil, 2), true, false, true)
-	o._putProp("getOwnPropertyNames", r.newNativeFunc(r.object_getOwnPropertyNames, nil, "getOwnPropertyNames", nil, 1), true, false, true)
-	o._putProp("getOwnPropertySymbols", r.newNativeFunc(r.object_getOwnPropertySymbols, nil, "getOwnPropertySymbols", nil, 1), true, false, true)
-	o._putProp("create", r.newNativeFunc(r.object_create, nil, "create", nil, 2), true, false, true)
-	o._putProp("seal", r.newNativeFunc(r.object_seal, nil, "seal", nil, 1), true, false, true)
-	o._putProp("freeze", r.newNativeFunc(r.object_freeze, nil, "freeze", nil, 1), true, false, true)
-	o._putProp("preventExtensions", r.newNativeFunc(r.object_preventExtensions, nil, "preventExtensions", nil, 1), true, false, true)
-	o._putProp("isSealed", r.newNativeFunc(r.object_isSealed, nil, "isSealed", nil, 1), true, false, true)
-	o._putProp("isFrozen", r.newNativeFunc(r.object_isFrozen, nil, "isFrozen", nil, 1), true, false, true)
-	o._putProp("isExtensible", r.newNativeFunc(r.object_isExtensible, nil, "isExtensible", nil, 1), true, false, true)
-	o._putProp("keys", r.newNativeFunc(r.object_keys, nil, "keys", nil, 1), true, false, true)
-	o._putProp("setPrototypeOf", r.newNativeFunc(r.object_setPrototypeOf, nil, "setPrototypeOf", nil, 2), true, false, true)
-	o._putProp("values", r.newNativeFunc(r.object_values, nil, "values", nil, 1), true, false, true)
-	o._putProp("fromEntries", r.newNativeFunc(r.object_fromEntries, nil, "fromEntries", nil, 1), true, false, true)
-	o._putProp("hasOwn", r.newNativeFunc(r.object_hasOwn, nil, "hasOwn", nil, 2), true, false, true)
-
-	r.addToGlobal("Object", r.global.Object)
+func createObjectTemplate() *objectTemplate {
+	t := newObjectTemplate()
+	t.protoFactory = func(r *Runtime) *Object {
+		return r.getFunctionPrototype()
+	}
+
+	t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(1), false, false, true) })
+	t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("Object"), false, false, true) })
+
+	t.putStr("prototype", func(r *Runtime) Value { return valueProp(r.global.ObjectPrototype, false, false, false) })
+
+	t.putStr("assign", func(r *Runtime) Value { return r.methodProp(r.object_assign, "assign", 2) })
+	t.putStr("defineProperty", func(r *Runtime) Value { return r.methodProp(r.object_defineProperty, "defineProperty", 3) })
+	t.putStr("defineProperties", func(r *Runtime) Value { return r.methodProp(r.object_defineProperties, "defineProperties", 2) })
+	t.putStr("entries", func(r *Runtime) Value { return r.methodProp(r.object_entries, "entries", 1) })
+	t.putStr("getOwnPropertyDescriptor", func(r *Runtime) Value {
+		return r.methodProp(r.object_getOwnPropertyDescriptor, "getOwnPropertyDescriptor", 2)
+	})
+	t.putStr("getOwnPropertyDescriptors", func(r *Runtime) Value {
+		return r.methodProp(r.object_getOwnPropertyDescriptors, "getOwnPropertyDescriptors", 1)
+	})
+	t.putStr("getPrototypeOf", func(r *Runtime) Value { return r.methodProp(r.object_getPrototypeOf, "getPrototypeOf", 1) })
+	t.putStr("is", func(r *Runtime) Value { return r.methodProp(r.object_is, "is", 2) })
+	t.putStr("getOwnPropertyNames", func(r *Runtime) Value { return r.methodProp(r.object_getOwnPropertyNames, "getOwnPropertyNames", 1) })
+	t.putStr("getOwnPropertySymbols", func(r *Runtime) Value {
+		return r.methodProp(r.object_getOwnPropertySymbols, "getOwnPropertySymbols", 1)
+	})
+	t.putStr("create", func(r *Runtime) Value { return r.methodProp(r.object_create, "create", 2) })
+	t.putStr("seal", func(r *Runtime) Value { return r.methodProp(r.object_seal, "seal", 1) })
+	t.putStr("freeze", func(r *Runtime) Value { return r.methodProp(r.object_freeze, "freeze", 1) })
+	t.putStr("preventExtensions", func(r *Runtime) Value { return r.methodProp(r.object_preventExtensions, "preventExtensions", 1) })
+	t.putStr("isSealed", func(r *Runtime) Value { return r.methodProp(r.object_isSealed, "isSealed", 1) })
+	t.putStr("isFrozen", func(r *Runtime) Value { return r.methodProp(r.object_isFrozen, "isFrozen", 1) })
+	t.putStr("isExtensible", func(r *Runtime) Value { return r.methodProp(r.object_isExtensible, "isExtensible", 1) })
+	t.putStr("keys", func(r *Runtime) Value { return r.methodProp(r.object_keys, "keys", 1) })
+	t.putStr("setPrototypeOf", func(r *Runtime) Value { return r.methodProp(r.object_setPrototypeOf, "setPrototypeOf", 2) })
+	t.putStr("values", func(r *Runtime) Value { return r.methodProp(r.object_values, "values", 1) })
+	t.putStr("fromEntries", func(r *Runtime) Value { return r.methodProp(r.object_fromEntries, "fromEntries", 1) })
+	t.putStr("hasOwn", func(r *Runtime) Value { return r.methodProp(r.object_hasOwn, "hasOwn", 2) })
+
+	return t
+}
+
+var _objectTemplate *objectTemplate
+var objectTemplateOnce sync.Once
+
+func getObjectTemplate() *objectTemplate {
+	objectTemplateOnce.Do(func() {
+		_objectTemplate = createObjectTemplate()
+	})
+	return _objectTemplate
+}
+
+func (r *Runtime) getObject() *Object {
+	ret := r.global.Object
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Object = ret
+		r.newTemplatedFuncObject(getObjectTemplate(), ret, func(call FunctionCall) Value {
+			return r.builtin_Object(call.Arguments, nil)
+		}, r.builtin_Object)
+	}
+	return ret
+}
+
+/*
+func (r *Runtime) getObjectPrototype() *Object {
+	ret := r.global.ObjectPrototype
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.ObjectPrototype = ret
+		r.newTemplatedObject(getObjectProtoTemplate(), ret)
+	}
+	return ret
+}
+*/
+
+var objectProtoTemplate *objectTemplate
+var objectProtoTemplateOnce sync.Once
+
+func getObjectProtoTemplate() *objectTemplate {
+	objectProtoTemplateOnce.Do(func() {
+		objectProtoTemplate = createObjectProtoTemplate()
+	})
+	return objectProtoTemplate
+}
+
+func createObjectProtoTemplate() *objectTemplate {
+	t := newObjectTemplate()
+
+	// null prototype
+
+	t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getObject(), true, false, true) })
+
+	t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.objectproto_toString, "toString", 0) })
+	t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.objectproto_toLocaleString, "toLocaleString", 0) })
+	t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.objectproto_valueOf, "valueOf", 0) })
+	t.putStr("hasOwnProperty", func(r *Runtime) Value { return r.methodProp(r.objectproto_hasOwnProperty, "hasOwnProperty", 1) })
+	t.putStr("isPrototypeOf", func(r *Runtime) Value { return r.methodProp(r.objectproto_isPrototypeOf, "isPrototypeOf", 1) })
+	t.putStr("propertyIsEnumerable", func(r *Runtime) Value {
+		return r.methodProp(r.objectproto_propertyIsEnumerable, "propertyIsEnumerable", 1)
+	})
+	t.putStr(__proto__, func(r *Runtime) Value {
+		return &valueProperty{
+			accessor:     true,
+			getterFunc:   r.newNativeFunc(r.objectproto_getProto, "get __proto__", 0),
+			setterFunc:   r.newNativeFunc(r.objectproto_setProto, "set __proto__", 1),
+			configurable: true,
+		}
+	})
+
+	return t
 }

+ 46 - 33
builtin_promise.go

@@ -108,7 +108,7 @@ func (p *Promise) createResolvingFunctions() (resolve, reject *Object) {
 				}
 			}
 			return p.fulfill(resolution)
-		}, nil, "", nil, 1),
+		}, "", 1),
 		p.val.runtime.newNativeFunc(func(call FunctionCall) Value {
 			if alreadyResolved {
 				return _undefined
@@ -116,7 +116,7 @@ func (p *Promise) createResolvingFunctions() (resolve, reject *Object) {
 			alreadyResolved = true
 			reason := call.Argument(0)
 			return p.reject(reason)
-		}, nil, "", nil, 1)
+		}, "", 1)
 }
 
 func (p *Promise) reject(reason Value) Value {
@@ -253,7 +253,7 @@ func (r *Runtime) builtin_newPromise(args []Value, newTarget *Object) *Object {
 	}
 	executor := r.toCallable(arg0)
 
-	proto := r.getPrototypeFromCtor(newTarget, r.global.Promise, r.global.PromisePrototype)
+	proto := r.getPrototypeFromCtor(newTarget, r.global.Promise, r.getPromisePrototype())
 	po := r.newPromise(proto)
 
 	resolve, reject := po.createResolvingFunctions()
@@ -271,7 +271,7 @@ func (r *Runtime) builtin_newPromise(args []Value, newTarget *Object) *Object {
 func (r *Runtime) promiseProto_then(call FunctionCall) Value {
 	thisObj := r.toObject(call.This)
 	if p, ok := thisObj.self.(*Promise); ok {
-		c := r.speciesConstructorObj(thisObj, r.global.Promise)
+		c := r.speciesConstructorObj(thisObj, r.getPromise())
 		resultCapability := r.newPromiseCapability(c)
 		return r.performPromiseThen(p, call.Argument(0), call.Argument(1), resultCapability)
 	}
@@ -280,8 +280,8 @@ func (r *Runtime) promiseProto_then(call FunctionCall) Value {
 
 func (r *Runtime) newPromiseCapability(c *Object) *promiseCapability {
 	pcap := new(promiseCapability)
-	if c == r.global.Promise {
-		p := r.newPromise(r.global.PromisePrototype)
+	if c == r.getPromise() {
+		p := r.newPromise(r.getPromisePrototype())
 		pcap.resolveObj, pcap.rejectObj = p.createResolvingFunctions()
 		pcap.promise = p.val
 	} else {
@@ -300,7 +300,7 @@ func (r *Runtime) newPromiseCapability(c *Object) *promiseCapability {
 				reject = arg
 			}
 			return nil
-		}, nil, "", nil, 2)
+		}, "", 2)
 		pcap.promise = r.toConstructor(c)([]Value{executor}, c)
 		pcap.resolveObj = r.toObject(resolve)
 		r.toCallable(pcap.resolveObj) // make sure it's callable
@@ -353,7 +353,7 @@ func (r *Runtime) promiseResolve(c *Object, x Value) *Object {
 
 func (r *Runtime) promiseProto_finally(call FunctionCall) Value {
 	promise := r.toObject(call.This)
-	c := r.speciesConstructorObj(promise, r.global.Promise)
+	c := r.speciesConstructorObj(promise, r.getPromise())
 	onFinally := call.Argument(0)
 	var thenFinally, catchFinally Value
 	if onFinallyFn, ok := assertCallable(onFinally); !ok {
@@ -365,9 +365,9 @@ func (r *Runtime) promiseProto_finally(call FunctionCall) Value {
 			promise := r.promiseResolve(c, result)
 			valueThunk := r.newNativeFunc(func(call FunctionCall) Value {
 				return value
-			}, nil, "", nil, 0)
+			}, "", 0)
 			return r.invoke(promise, "then", valueThunk)
-		}, nil, "", nil, 1)
+		}, "", 1)
 
 		catchFinally = r.newNativeFunc(func(call FunctionCall) Value {
 			reason := call.Argument(0)
@@ -375,9 +375,9 @@ func (r *Runtime) promiseProto_finally(call FunctionCall) Value {
 			promise := r.promiseResolve(c, result)
 			thrower := r.newNativeFunc(func(call FunctionCall) Value {
 				panic(reason)
-			}, nil, "", nil, 0)
+			}, "", 0)
 			return r.invoke(promise, "then", thrower)
-		}, nil, "", nil, 1)
+		}, "", 1)
 	}
 	return r.invoke(promise, "then", thenFinally, catchFinally)
 }
@@ -424,7 +424,7 @@ func (r *Runtime) promise_all(call FunctionCall) Value {
 					pcap.resolve(r.newArrayValues(values))
 				}
 				return _undefined
-			}, nil, "", nil, 1)
+			}, "", 1)
 			remainingElementsCount++
 			r.invoke(nextPromise, "then", onFulfilled, pcap.rejectObj)
 		})
@@ -465,7 +465,7 @@ func (r *Runtime) promise_allSettled(call FunctionCall) Value {
 						pcap.resolve(r.newArrayValues(values))
 					}
 					return _undefined
-				}, nil, "", nil, 1)
+				}, "", 1)
 			}
 			onFulfilled := reaction(asciiString("fulfilled"), "value")
 			onRejected := reaction(asciiString("rejected"), "reason")
@@ -502,19 +502,19 @@ func (r *Runtime) promise_any(call FunctionCall) Value {
 				errors[index] = call.Argument(0)
 				remainingElementsCount--
 				if remainingElementsCount == 0 {
-					_error := r.builtin_new(r.global.AggregateError, nil)
+					_error := r.builtin_new(r.getAggregateError(), nil)
 					_error.self._putProp("errors", r.newArrayValues(errors), true, false, true)
 					pcap.reject(_error)
 				}
 				return _undefined
-			}, nil, "", nil, 1)
+			}, "", 1)
 
 			remainingElementsCount++
 			r.invoke(nextPromise, "then", pcap.resolveObj, onRejected)
 		})
 		remainingElementsCount--
 		if remainingElementsCount == 0 {
-			_error := r.builtin_new(r.global.AggregateError, nil)
+			_error := r.builtin_new(r.getAggregateError(), nil)
 			_error.self._putProp("errors", r.newArrayValues(errors), true, false, true)
 			pcap.reject(_error)
 		}
@@ -549,11 +549,11 @@ func (r *Runtime) promise_resolve(call FunctionCall) Value {
 
 func (r *Runtime) createPromiseProto(val *Object) objectImpl {
 	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
-	o._putProp("constructor", r.global.Promise, true, false, true)
+	o._putProp("constructor", r.getPromise(), true, false, true)
 
-	o._putProp("catch", r.newNativeFunc(r.promiseProto_catch, nil, "catch", nil, 1), true, false, true)
-	o._putProp("finally", r.newNativeFunc(r.promiseProto_finally, nil, "finally", nil, 1), true, false, true)
-	o._putProp("then", r.newNativeFunc(r.promiseProto_then, nil, "then", nil, 2), true, false, true)
+	o._putProp("catch", r.newNativeFunc(r.promiseProto_catch, "catch", 1), true, false, true)
+	o._putProp("finally", r.newNativeFunc(r.promiseProto_finally, "finally", 1), true, false, true)
+	o._putProp("then", r.newNativeFunc(r.promiseProto_then, "then", 2), true, false, true)
 
 	o._putSym(SymToStringTag, valueProp(asciiString(classPromise), false, false, true))
 
@@ -561,25 +561,38 @@ func (r *Runtime) createPromiseProto(val *Object) objectImpl {
 }
 
 func (r *Runtime) createPromise(val *Object) objectImpl {
-	o := r.newNativeConstructOnly(val, r.builtin_newPromise, r.global.PromisePrototype, "Promise", 1)
+	o := r.newNativeConstructOnly(val, r.builtin_newPromise, r.getPromisePrototype(), "Promise", 1)
 
-	o._putProp("all", r.newNativeFunc(r.promise_all, nil, "all", nil, 1), true, false, true)
-	o._putProp("allSettled", r.newNativeFunc(r.promise_allSettled, nil, "allSettled", nil, 1), true, false, true)
-	o._putProp("any", r.newNativeFunc(r.promise_any, nil, "any", nil, 1), true, false, true)
-	o._putProp("race", r.newNativeFunc(r.promise_race, nil, "race", nil, 1), true, false, true)
-	o._putProp("reject", r.newNativeFunc(r.promise_reject, nil, "reject", nil, 1), true, false, true)
-	o._putProp("resolve", r.newNativeFunc(r.promise_resolve, nil, "resolve", nil, 1), true, false, true)
+	o._putProp("all", r.newNativeFunc(r.promise_all, "all", 1), true, false, true)
+	o._putProp("allSettled", r.newNativeFunc(r.promise_allSettled, "allSettled", 1), true, false, true)
+	o._putProp("any", r.newNativeFunc(r.promise_any, "any", 1), true, false, true)
+	o._putProp("race", r.newNativeFunc(r.promise_race, "race", 1), true, false, true)
+	o._putProp("reject", r.newNativeFunc(r.promise_reject, "reject", 1), true, false, true)
+	o._putProp("resolve", r.newNativeFunc(r.promise_resolve, "resolve", 1), true, false, true)
 
 	r.putSpeciesReturnThis(o)
 
 	return o
 }
 
-func (r *Runtime) initPromise() {
-	r.global.PromisePrototype = r.newLazyObject(r.createPromiseProto)
-	r.global.Promise = r.newLazyObject(r.createPromise)
+func (r *Runtime) getPromisePrototype() *Object {
+	ret := r.global.PromisePrototype
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.PromisePrototype = ret
+		ret.self = r.createPromiseProto(ret)
+	}
+	return ret
+}
 
-	r.addToGlobal("Promise", r.global.Promise)
+func (r *Runtime) getPromise() *Object {
+	ret := r.global.Promise
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Promise = ret
+		ret.self = r.createPromise(ret)
+	}
+	return ret
 }
 
 func (r *Runtime) wrapPromiseReaction(fObj *Object) func(interface{}) {
@@ -609,7 +622,7 @@ func (r *Runtime) wrapPromiseReaction(fObj *Object) func(interface{}) {
 //	    }()
 //	}
 func (r *Runtime) NewPromise() (promise *Promise, resolve func(result interface{}), reject func(reason interface{})) {
-	p := r.newPromise(r.global.PromisePrototype)
+	p := r.newPromise(r.getPromisePrototype())
 	resolveF, rejectF := p.createResolvingFunctions()
 	return p, r.wrapPromiseReaction(resolveF), r.wrapPromiseReaction(rejectF)
 }

+ 11 - 6
builtin_proxy.go

@@ -345,7 +345,7 @@ func (r *Runtime) builtin_newProxy(args []Value, newTarget *Object) *Object {
 	if newTarget == nil {
 		panic(r.needNew("Proxy"))
 	}
-	return r.newProxy(args, r.getPrototypeFromCtor(newTarget, r.global.Proxy, r.global.ObjectPrototype))
+	return r.newProxy(args, r.getPrototypeFromCtor(newTarget, r.getProxy(), r.global.ObjectPrototype))
 }
 
 func (r *Runtime) NewProxy(target *Object, nativeHandler *ProxyTrapConfig) Proxy {
@@ -367,7 +367,7 @@ func (r *Runtime) builtin_proxy_revocable(call FunctionCall) Value {
 				revoke := r.newNativeFunc(func(FunctionCall) Value {
 					proxy.revoke()
 					return _undefined
-				}, nil, "", nil, 0)
+				}, "", 0)
 				ret := r.NewObject()
 				ret.self._putProp("proxy", proxy.val, true, true, true)
 				ret.self._putProp("revoke", revoke, true, true, true)
@@ -381,11 +381,16 @@ func (r *Runtime) builtin_proxy_revocable(call FunctionCall) Value {
 func (r *Runtime) createProxy(val *Object) objectImpl {
 	o := r.newNativeConstructOnly(val, r.builtin_newProxy, nil, "Proxy", 2)
 
-	o._putProp("revocable", r.newNativeFunc(r.builtin_proxy_revocable, nil, "revocable", nil, 2), true, false, true)
+	o._putProp("revocable", r.newNativeFunc(r.builtin_proxy_revocable, "revocable", 2), true, false, true)
 	return o
 }
 
-func (r *Runtime) initProxy() {
-	r.global.Proxy = r.newLazyObject(r.createProxy)
-	r.addToGlobal("Proxy", r.global.Proxy)
+func (r *Runtime) getProxy() *Object {
+	ret := r.global.Proxy
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Proxy = ret
+		r.createProxy(ret)
+	}
+	return ret
 }

+ 21 - 15
builtin_reflect.go

@@ -110,25 +110,31 @@ func (r *Runtime) builtin_reflect_setPrototypeOf(call FunctionCall) Value {
 func (r *Runtime) createReflect(val *Object) objectImpl {
 	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
 
-	o._putProp("apply", r.newNativeFunc(r.builtin_reflect_apply, nil, "apply", nil, 3), true, false, true)
-	o._putProp("construct", r.newNativeFunc(r.builtin_reflect_construct, nil, "construct", nil, 2), true, false, true)
-	o._putProp("defineProperty", r.newNativeFunc(r.builtin_reflect_defineProperty, nil, "defineProperty", nil, 3), true, false, true)
-	o._putProp("deleteProperty", r.newNativeFunc(r.builtin_reflect_deleteProperty, nil, "deleteProperty", nil, 2), true, false, true)
-	o._putProp("get", r.newNativeFunc(r.builtin_reflect_get, nil, "get", nil, 2), true, false, true)
-	o._putProp("getOwnPropertyDescriptor", r.newNativeFunc(r.builtin_reflect_getOwnPropertyDescriptor, nil, "getOwnPropertyDescriptor", nil, 2), true, false, true)
-	o._putProp("getPrototypeOf", r.newNativeFunc(r.builtin_reflect_getPrototypeOf, nil, "getPrototypeOf", nil, 1), true, false, true)
-	o._putProp("has", r.newNativeFunc(r.builtin_reflect_has, nil, "has", nil, 2), true, false, true)
-	o._putProp("isExtensible", r.newNativeFunc(r.builtin_reflect_isExtensible, nil, "isExtensible", nil, 1), true, false, true)
-	o._putProp("ownKeys", r.newNativeFunc(r.builtin_reflect_ownKeys, nil, "ownKeys", nil, 1), true, false, true)
-	o._putProp("preventExtensions", r.newNativeFunc(r.builtin_reflect_preventExtensions, nil, "preventExtensions", nil, 1), true, false, true)
-	o._putProp("set", r.newNativeFunc(r.builtin_reflect_set, nil, "set", nil, 3), true, false, true)
-	o._putProp("setPrototypeOf", r.newNativeFunc(r.builtin_reflect_setPrototypeOf, nil, "setPrototypeOf", nil, 2), true, false, true)
+	o._putProp("apply", r.newNativeFunc(r.builtin_reflect_apply, "apply", 3), true, false, true)
+	o._putProp("construct", r.newNativeFunc(r.builtin_reflect_construct, "construct", 2), true, false, true)
+	o._putProp("defineProperty", r.newNativeFunc(r.builtin_reflect_defineProperty, "defineProperty", 3), true, false, true)
+	o._putProp("deleteProperty", r.newNativeFunc(r.builtin_reflect_deleteProperty, "deleteProperty", 2), true, false, true)
+	o._putProp("get", r.newNativeFunc(r.builtin_reflect_get, "get", 2), true, false, true)
+	o._putProp("getOwnPropertyDescriptor", r.newNativeFunc(r.builtin_reflect_getOwnPropertyDescriptor, "getOwnPropertyDescriptor", 2), true, false, true)
+	o._putProp("getPrototypeOf", r.newNativeFunc(r.builtin_reflect_getPrototypeOf, "getPrototypeOf", 1), true, false, true)
+	o._putProp("has", r.newNativeFunc(r.builtin_reflect_has, "has", 2), true, false, true)
+	o._putProp("isExtensible", r.newNativeFunc(r.builtin_reflect_isExtensible, "isExtensible", 1), true, false, true)
+	o._putProp("ownKeys", r.newNativeFunc(r.builtin_reflect_ownKeys, "ownKeys", 1), true, false, true)
+	o._putProp("preventExtensions", r.newNativeFunc(r.builtin_reflect_preventExtensions, "preventExtensions", 1), true, false, true)
+	o._putProp("set", r.newNativeFunc(r.builtin_reflect_set, "set", 3), true, false, true)
+	o._putProp("setPrototypeOf", r.newNativeFunc(r.builtin_reflect_setPrototypeOf, "setPrototypeOf", 2), true, false, true)
 
 	o._putSym(SymToStringTag, valueProp(asciiString("Reflect"), false, false, true))
 
 	return o
 }
 
-func (r *Runtime) initReflect() {
-	r.addToGlobal("Reflect", r.newLazyObject(r.createReflect))
+func (r *Runtime) getReflect() *Object {
+	ret := r.global.Reflect
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Reflect = ret
+		ret.self = r.createReflect(ret)
+	}
+	return ret
 }

+ 75 - 60
builtin_regexp.go

@@ -347,7 +347,7 @@ func (r *Runtime) builtin_RegExp(call FunctionCall) Value {
 			}
 		}
 	}
-	return r.newRegExp(pattern, flags, r.global.RegExpPrototype).val
+	return r.newRegExp(pattern, flags, r.getRegExpPrototype()).val
 }
 
 func (r *Runtime) regexpproto_compile(call FunctionCall) Value {
@@ -771,7 +771,7 @@ func (r *Runtime) regexpproto_stdMatcherAll(call FunctionCall) Value {
 	thisObj := r.toObject(call.This)
 	s := call.Argument(0).toString()
 	flags := nilSafe(thisObj.self.getStr("flags", nil)).toString()
-	c := r.speciesConstructorObj(call.This.(*Object), r.global.RegExp)
+	c := r.speciesConstructorObj(call.This.(*Object), r.getRegExp())
 	matcher := r.toConstructor(c)([]Value{call.This, flags}, nil)
 	matcher.self.setOwnStr("lastIndex", valueInt(toLength(thisObj.self.getStr("lastIndex", nil))), true)
 	flagsStr := flags.String()
@@ -963,7 +963,7 @@ func (r *Runtime) regexpproto_stdSplitter(call FunctionCall) Value {
 	limitValue := call.Argument(1)
 	var splitter *Object
 	search := r.checkStdRegexp(rxObj)
-	c := r.speciesConstructorObj(rxObj, r.global.RegExp)
+	c := r.speciesConstructorObj(rxObj, r.getRegExp())
 	if search == nil || c != r.global.RegExp {
 		flags := nilSafe(rxObj.self.getStr("flags", nil)).toString()
 		flagsStr := flags.String()
@@ -1214,7 +1214,7 @@ func (r *Runtime) regExpStringIteratorProto_next(call FunctionCall) Value {
 func (r *Runtime) createRegExpStringIteratorPrototype(val *Object) objectImpl {
 	o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject)
 
-	o._putProp("next", r.newNativeFunc(r.regExpStringIteratorProto_next, nil, "next", nil, 0), true, false, true)
+	o._putProp("next", r.newNativeFunc(r.regExpStringIteratorProto_next, "next", 0), true, false, true)
 	o._putSym(SymToStringTag, valueProp(asciiString(classRegExpStringIterator), false, false, true))
 
 	return o
@@ -1230,60 +1230,75 @@ func (r *Runtime) getRegExpStringIteratorPrototype() *Object {
 	return o
 }
 
-func (r *Runtime) initRegExp() {
-	o := r.newGuardedObject(r.global.ObjectPrototype, classObject)
-	r.global.RegExpPrototype = o.val
-	r.global.stdRegexpProto = o
-
-	o._putProp("compile", r.newNativeFunc(r.regexpproto_compile, nil, "compile", nil, 2), true, false, true)
-	o._putProp("exec", r.newNativeFunc(r.regexpproto_exec, nil, "exec", nil, 1), true, false, true)
-	o._putProp("test", r.newNativeFunc(r.regexpproto_test, nil, "test", nil, 1), true, false, true)
-	o._putProp("toString", r.newNativeFunc(r.regexpproto_toString, nil, "toString", nil, 0), true, false, true)
-	o.setOwnStr("source", &valueProperty{
-		configurable: true,
-		getterFunc:   r.newNativeFunc(r.regexpproto_getSource, nil, "get source", nil, 0),
-		accessor:     true,
-	}, false)
-	o.setOwnStr("global", &valueProperty{
-		configurable: true,
-		getterFunc:   r.newNativeFunc(r.regexpproto_getGlobal, nil, "get global", nil, 0),
-		accessor:     true,
-	}, false)
-	o.setOwnStr("multiline", &valueProperty{
-		configurable: true,
-		getterFunc:   r.newNativeFunc(r.regexpproto_getMultiline, nil, "get multiline", nil, 0),
-		accessor:     true,
-	}, false)
-	o.setOwnStr("ignoreCase", &valueProperty{
-		configurable: true,
-		getterFunc:   r.newNativeFunc(r.regexpproto_getIgnoreCase, nil, "get ignoreCase", nil, 0),
-		accessor:     true,
-	}, false)
-	o.setOwnStr("unicode", &valueProperty{
-		configurable: true,
-		getterFunc:   r.newNativeFunc(r.regexpproto_getUnicode, nil, "get unicode", nil, 0),
-		accessor:     true,
-	}, false)
-	o.setOwnStr("sticky", &valueProperty{
-		configurable: true,
-		getterFunc:   r.newNativeFunc(r.regexpproto_getSticky, nil, "get sticky", nil, 0),
-		accessor:     true,
-	}, false)
-	o.setOwnStr("flags", &valueProperty{
-		configurable: true,
-		getterFunc:   r.newNativeFunc(r.regexpproto_getFlags, nil, "get flags", nil, 0),
-		accessor:     true,
-	}, false)
-
-	o._putSym(SymMatch, valueProp(r.newNativeFunc(r.regexpproto_stdMatcher, nil, "[Symbol.match]", nil, 1), true, false, true))
-	o._putSym(SymMatchAll, valueProp(r.newNativeFunc(r.regexpproto_stdMatcherAll, nil, "[Symbol.matchAll]", nil, 1), true, false, true))
-	o._putSym(SymSearch, valueProp(r.newNativeFunc(r.regexpproto_stdSearch, nil, "[Symbol.search]", nil, 1), true, false, true))
-	o._putSym(SymSplit, valueProp(r.newNativeFunc(r.regexpproto_stdSplitter, nil, "[Symbol.split]", nil, 2), true, false, true))
-	o._putSym(SymReplace, valueProp(r.newNativeFunc(r.regexpproto_stdReplacer, nil, "[Symbol.replace]", nil, 2), true, false, true))
-	o.guard("exec", "global", "multiline", "ignoreCase", "unicode", "sticky")
-
-	r.global.RegExp = r.newNativeFunc(r.builtin_RegExp, r.builtin_newRegExp, "RegExp", r.global.RegExpPrototype, 2)
-	rx := r.global.RegExp.self
-	r.putSpeciesReturnThis(rx)
-	r.addToGlobal("RegExp", r.global.RegExp)
+func (r *Runtime) getRegExp() *Object {
+	ret := r.global.RegExp
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.RegExp = ret
+		proto := r.getRegExpPrototype()
+		r.newNativeFuncAndConstruct(ret, r.builtin_RegExp,
+			r.wrapNativeConstruct(r.builtin_newRegExp, ret, proto), proto, "RegExp", intToValue(2))
+		rx := ret.self
+		r.putSpeciesReturnThis(rx)
+	}
+	return ret
+}
+
+func (r *Runtime) getRegExpPrototype() *Object {
+	ret := r.global.RegExpPrototype
+	if ret == nil {
+		o := r.newGuardedObject(r.global.ObjectPrototype, classObject)
+		ret = o.val
+		r.global.RegExpPrototype = ret
+		r.global.stdRegexpProto = o
+
+		o._putProp("constructor", r.getRegExp(), true, false, true)
+		o._putProp("compile", r.newNativeFunc(r.regexpproto_compile, "compile", 2), true, false, true)
+		o._putProp("exec", r.newNativeFunc(r.regexpproto_exec, "exec", 1), true, false, true)
+		o._putProp("test", r.newNativeFunc(r.regexpproto_test, "test", 1), true, false, true)
+		o._putProp("toString", r.newNativeFunc(r.regexpproto_toString, "toString", 0), true, false, true)
+		o.setOwnStr("source", &valueProperty{
+			configurable: true,
+			getterFunc:   r.newNativeFunc(r.regexpproto_getSource, "get source", 0),
+			accessor:     true,
+		}, false)
+		o.setOwnStr("global", &valueProperty{
+			configurable: true,
+			getterFunc:   r.newNativeFunc(r.regexpproto_getGlobal, "get global", 0),
+			accessor:     true,
+		}, false)
+		o.setOwnStr("multiline", &valueProperty{
+			configurable: true,
+			getterFunc:   r.newNativeFunc(r.regexpproto_getMultiline, "get multiline", 0),
+			accessor:     true,
+		}, false)
+		o.setOwnStr("ignoreCase", &valueProperty{
+			configurable: true,
+			getterFunc:   r.newNativeFunc(r.regexpproto_getIgnoreCase, "get ignoreCase", 0),
+			accessor:     true,
+		}, false)
+		o.setOwnStr("unicode", &valueProperty{
+			configurable: true,
+			getterFunc:   r.newNativeFunc(r.regexpproto_getUnicode, "get unicode", 0),
+			accessor:     true,
+		}, false)
+		o.setOwnStr("sticky", &valueProperty{
+			configurable: true,
+			getterFunc:   r.newNativeFunc(r.regexpproto_getSticky, "get sticky", 0),
+			accessor:     true,
+		}, false)
+		o.setOwnStr("flags", &valueProperty{
+			configurable: true,
+			getterFunc:   r.newNativeFunc(r.regexpproto_getFlags, "get flags", 0),
+			accessor:     true,
+		}, false)
+
+		o._putSym(SymMatch, valueProp(r.newNativeFunc(r.regexpproto_stdMatcher, "[Symbol.match]", 1), true, false, true))
+		o._putSym(SymMatchAll, valueProp(r.newNativeFunc(r.regexpproto_stdMatcherAll, "[Symbol.matchAll]", 1), true, false, true))
+		o._putSym(SymSearch, valueProp(r.newNativeFunc(r.regexpproto_stdSearch, "[Symbol.search]", 1), true, false, true))
+		o._putSym(SymSplit, valueProp(r.newNativeFunc(r.regexpproto_stdSplitter, "[Symbol.split]", 2), true, false, true))
+		o._putSym(SymReplace, valueProp(r.newNativeFunc(r.regexpproto_stdReplacer, "[Symbol.replace]", 2), true, false, true))
+		o.guard("exec", "global", "multiline", "ignoreCase", "unicode", "sticky")
+	}
+	return ret
 }

+ 28 - 15
builtin_set.go

@@ -274,25 +274,25 @@ func (r *Runtime) setIterProto_next(call FunctionCall) Value {
 func (r *Runtime) createSetProto(val *Object) objectImpl {
 	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
 
-	o._putProp("constructor", r.global.Set, true, false, true)
-	r.global.setAdder = r.newNativeFunc(r.setProto_add, nil, "add", nil, 1)
+	o._putProp("constructor", r.getSet(), true, false, true)
+	r.global.setAdder = r.newNativeFunc(r.setProto_add, "add", 1)
 	o._putProp("add", r.global.setAdder, true, false, true)
 
-	o._putProp("clear", r.newNativeFunc(r.setProto_clear, nil, "clear", nil, 0), true, false, true)
-	o._putProp("delete", r.newNativeFunc(r.setProto_delete, nil, "delete", nil, 1), true, false, true)
-	o._putProp("forEach", r.newNativeFunc(r.setProto_forEach, nil, "forEach", nil, 1), true, false, true)
-	o._putProp("has", r.newNativeFunc(r.setProto_has, nil, "has", nil, 1), true, false, true)
+	o._putProp("clear", r.newNativeFunc(r.setProto_clear, "clear", 0), true, false, true)
+	o._putProp("delete", r.newNativeFunc(r.setProto_delete, "delete", 1), true, false, true)
+	o._putProp("forEach", r.newNativeFunc(r.setProto_forEach, "forEach", 1), true, false, true)
+	o._putProp("has", r.newNativeFunc(r.setProto_has, "has", 1), true, false, true)
 	o.setOwnStr("size", &valueProperty{
-		getterFunc:   r.newNativeFunc(r.setProto_getSize, nil, "get size", nil, 0),
+		getterFunc:   r.newNativeFunc(r.setProto_getSize, "get size", 0),
 		accessor:     true,
 		writable:     true,
 		configurable: true,
 	}, true)
 
-	valuesFunc := r.newNativeFunc(r.setProto_values, nil, "values", nil, 0)
+	valuesFunc := r.newNativeFunc(r.setProto_values, "values", 0)
 	o._putProp("values", valuesFunc, true, false, true)
 	o._putProp("keys", valuesFunc, true, false, true)
-	o._putProp("entries", r.newNativeFunc(r.setProto_entries, nil, "entries", nil, 0), true, false, true)
+	o._putProp("entries", r.newNativeFunc(r.setProto_entries, "entries", 0), true, false, true)
 	o._putSym(SymIterator, valueProp(valuesFunc, true, false, true))
 	o._putSym(SymToStringTag, valueProp(asciiString(classSet), false, false, true))
 
@@ -300,7 +300,7 @@ func (r *Runtime) createSetProto(val *Object) objectImpl {
 }
 
 func (r *Runtime) createSet(val *Object) objectImpl {
-	o := r.newNativeConstructOnly(val, r.builtin_newSet, r.global.SetPrototype, "Set", 0)
+	o := r.newNativeConstructOnly(val, r.builtin_newSet, r.getSetPrototype(), "Set", 0)
 	r.putSpeciesReturnThis(o)
 
 	return o
@@ -309,7 +309,7 @@ func (r *Runtime) createSet(val *Object) objectImpl {
 func (r *Runtime) createSetIterProto(val *Object) objectImpl {
 	o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject)
 
-	o._putProp("next", r.newNativeFunc(r.setIterProto_next, nil, "next", nil, 0), true, false, true)
+	o._putProp("next", r.newNativeFunc(r.setIterProto_next, "next", 0), true, false, true)
 	o._putSym(SymToStringTag, valueProp(asciiString(classSetIterator), false, false, true))
 
 	return o
@@ -325,9 +325,22 @@ func (r *Runtime) getSetIteratorPrototype() *Object {
 	return o
 }
 
-func (r *Runtime) initSet() {
-	r.global.SetPrototype = r.newLazyObject(r.createSetProto)
-	r.global.Set = r.newLazyObject(r.createSet)
+func (r *Runtime) getSetPrototype() *Object {
+	ret := r.global.SetPrototype
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.SetPrototype = ret
+		ret.self = r.createSetProto(ret)
+	}
+	return ret
+}
 
-	r.addToGlobal("Set", r.global.Set)
+func (r *Runtime) getSet() *Object {
+	ret := r.global.Set
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Set = ret
+		ret.self = r.createSet(ret)
+	}
+	return ret
 }

+ 124 - 59
builtin_string.go

@@ -4,6 +4,7 @@ import (
 	"github.com/dop251/goja/unistring"
 	"math"
 	"strings"
+	"sync"
 	"unicode/utf16"
 	"unicode/utf8"
 
@@ -82,6 +83,9 @@ func (r *Runtime) stringproto_toStringValueOf(this Value, funcName string) Value
 				return valueOf()
 			}
 		}
+		if obj == r.global.StringPrototype {
+			return stringEmpty
+		}
 	}
 	r.typeErrorResult(true, "String.prototype.%s is called on incompatible receiver", funcName)
 	return nil
@@ -131,11 +135,11 @@ func (r *Runtime) string_fromcodepoint(call FunctionCall) Value {
 		var c rune
 		if numInt, ok := num.(valueInt); ok {
 			if numInt < 0 || numInt > utf8.MaxRune {
-				panic(r.newError(r.global.RangeError, "Invalid code point %d", numInt))
+				panic(r.newError(r.getRangeError(), "Invalid code point %d", numInt))
 			}
 			c = rune(numInt)
 		} else {
-			panic(r.newError(r.global.RangeError, "Invalid code point %s", num))
+			panic(r.newError(r.getRangeError(), "Invalid code point %s", num))
 		}
 		sb.WriteRune(c)
 	}
@@ -391,7 +395,7 @@ func (r *Runtime) stringproto_match(call FunctionCall) Value {
 	}
 
 	if rx == nil {
-		rx = r.newRegExp(regexp, nil, r.global.RegExpPrototype)
+		rx = r.newRegExp(regexp, nil, r.getRegExpPrototype())
 	}
 
 	if matcher, ok := r.toObject(rx.getSym(SymMatch, nil)).self.assertCallable(); ok {
@@ -425,7 +429,7 @@ func (r *Runtime) stringproto_matchAll(call FunctionCall) Value {
 		}
 	}
 
-	rx := r.newRegExp(regexp, asciiString("g"), r.global.RegExpPrototype)
+	rx := r.newRegExp(regexp, asciiString("g"), r.getRegExpPrototype())
 
 	if matcher, ok := r.toObject(rx.getSym(SymMatchAll, nil)).self.assertCallable(); ok {
 		return matcher(FunctionCall{
@@ -457,7 +461,7 @@ func (r *Runtime) stringproto_normalize(call FunctionCall) Value {
 	case "NFKD":
 		f = norm.NFKD
 	default:
-		panic(r.newError(r.global.RangeError, "The normalization form should be one of NFC, NFD, NFKC, NFKD"))
+		panic(r.newError(r.getRangeError(), "The normalization form should be one of NFC, NFD, NFKC, NFKD"))
 	}
 
 	switch s := s.(type) {
@@ -551,11 +555,11 @@ func (r *Runtime) stringproto_repeat(call FunctionCall) Value {
 	s := call.This.toString()
 	n := call.Argument(0).ToNumber()
 	if n == _positiveInf {
-		panic(r.newError(r.global.RangeError, "Invalid count value"))
+		panic(r.newError(r.getRangeError(), "Invalid count value"))
 	}
 	numInt := n.ToInteger()
 	if numInt < 0 {
-		panic(r.newError(r.global.RangeError, "Invalid count value"))
+		panic(r.newError(r.getRangeError(), "Invalid count value"))
 	}
 	if numInt == 0 || s.Length() == 0 {
 		return stringEmpty
@@ -736,7 +740,7 @@ func (r *Runtime) stringproto_search(call FunctionCall) Value {
 	}
 
 	if rx == nil {
-		rx = r.newRegExp(regexp, nil, r.global.RegExpPrototype)
+		rx = r.newRegExp(regexp, nil, r.getRegExpPrototype())
 	}
 
 	if searcher, ok := r.toObject(rx.getSym(SymSearch, nil)).self.assertCallable(); ok {
@@ -977,7 +981,7 @@ func (r *Runtime) stringIterProto_next(call FunctionCall) Value {
 func (r *Runtime) createStringIterProto(val *Object) objectImpl {
 	o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject)
 
-	o._putProp("next", r.newNativeFunc(r.stringIterProto_next, nil, "next", nil, 0), true, false, true)
+	o._putProp("next", r.newNativeFunc(r.stringIterProto_next, "next", 0), true, false, true)
 	o._putSym(SymToStringTag, valueProp(asciiString(classStringIterator), false, false, true))
 
 	return o
@@ -993,59 +997,120 @@ func (r *Runtime) getStringIteratorPrototype() *Object {
 	return o
 }
 
-func (r *Runtime) initString() {
-	r.global.StringPrototype = r.builtin_newString([]Value{stringEmpty}, r.global.ObjectPrototype)
-
-	o := r.global.StringPrototype.self
-	o._putProp("at", r.newNativeFunc(r.stringproto_at, nil, "at", nil, 1), true, false, true)
-	o._putProp("charAt", r.newNativeFunc(r.stringproto_charAt, nil, "charAt", nil, 1), true, false, true)
-	o._putProp("charCodeAt", r.newNativeFunc(r.stringproto_charCodeAt, nil, "charCodeAt", nil, 1), true, false, true)
-	o._putProp("codePointAt", r.newNativeFunc(r.stringproto_codePointAt, nil, "codePointAt", nil, 1), true, false, true)
-	o._putProp("concat", r.newNativeFunc(r.stringproto_concat, nil, "concat", nil, 1), true, false, true)
-	o._putProp("endsWith", r.newNativeFunc(r.stringproto_endsWith, nil, "endsWith", nil, 1), true, false, true)
-	o._putProp("includes", r.newNativeFunc(r.stringproto_includes, nil, "includes", nil, 1), true, false, true)
-	o._putProp("indexOf", r.newNativeFunc(r.stringproto_indexOf, nil, "indexOf", nil, 1), true, false, true)
-	o._putProp("lastIndexOf", r.newNativeFunc(r.stringproto_lastIndexOf, nil, "lastIndexOf", nil, 1), true, false, true)
-	o._putProp("localeCompare", r.newNativeFunc(r.stringproto_localeCompare, nil, "localeCompare", nil, 1), true, false, true)
-	o._putProp("match", r.newNativeFunc(r.stringproto_match, nil, "match", nil, 1), true, false, true)
-	o._putProp("matchAll", r.newNativeFunc(r.stringproto_matchAll, nil, "matchAll", nil, 1), true, false, true)
-	o._putProp("normalize", r.newNativeFunc(r.stringproto_normalize, nil, "normalize", nil, 0), true, false, true)
-	o._putProp("padEnd", r.newNativeFunc(r.stringproto_padEnd, nil, "padEnd", nil, 1), true, false, true)
-	o._putProp("padStart", r.newNativeFunc(r.stringproto_padStart, nil, "padStart", nil, 1), true, false, true)
-	o._putProp("repeat", r.newNativeFunc(r.stringproto_repeat, nil, "repeat", nil, 1), true, false, true)
-	o._putProp("replace", r.newNativeFunc(r.stringproto_replace, nil, "replace", nil, 2), true, false, true)
-	o._putProp("replaceAll", r.newNativeFunc(r.stringproto_replaceAll, nil, "replaceAll", nil, 2), true, false, true)
-	o._putProp("search", r.newNativeFunc(r.stringproto_search, nil, "search", nil, 1), true, false, true)
-	o._putProp("slice", r.newNativeFunc(r.stringproto_slice, nil, "slice", nil, 2), true, false, true)
-	o._putProp("split", r.newNativeFunc(r.stringproto_split, nil, "split", nil, 2), true, false, true)
-	o._putProp("startsWith", r.newNativeFunc(r.stringproto_startsWith, nil, "startsWith", nil, 1), true, false, true)
-	o._putProp("substring", r.newNativeFunc(r.stringproto_substring, nil, "substring", nil, 2), true, false, true)
-	o._putProp("toLocaleLowerCase", r.newNativeFunc(r.stringproto_toLowerCase, nil, "toLocaleLowerCase", nil, 0), true, false, true)
-	o._putProp("toLocaleUpperCase", r.newNativeFunc(r.stringproto_toUpperCase, nil, "toLocaleUpperCase", nil, 0), true, false, true)
-	o._putProp("toLowerCase", r.newNativeFunc(r.stringproto_toLowerCase, nil, "toLowerCase", nil, 0), true, false, true)
-	o._putProp("toString", r.newNativeFunc(r.stringproto_toString, nil, "toString", nil, 0), true, false, true)
-	o._putProp("toUpperCase", r.newNativeFunc(r.stringproto_toUpperCase, nil, "toUpperCase", nil, 0), true, false, true)
-	o._putProp("trim", r.newNativeFunc(r.stringproto_trim, nil, "trim", nil, 0), true, false, true)
-	trimEnd := r.newNativeFunc(r.stringproto_trimEnd, nil, "trimEnd", nil, 0)
-	trimStart := r.newNativeFunc(r.stringproto_trimStart, nil, "trimStart", nil, 0)
-	o._putProp("trimEnd", trimEnd, true, false, true)
-	o._putProp("trimStart", trimStart, true, false, true)
-	o._putProp("trimRight", trimEnd, true, false, true)
-	o._putProp("trimLeft", trimStart, true, false, true)
-	o._putProp("valueOf", r.newNativeFunc(r.stringproto_valueOf, nil, "valueOf", nil, 0), true, false, true)
-
-	o._putSym(SymIterator, valueProp(r.newNativeFunc(r.stringproto_iterator, nil, "[Symbol.iterator]", nil, 0), true, false, true))
+func createStringProtoTemplate() *objectTemplate {
+	t := newObjectTemplate()
+	t.protoFactory = func(r *Runtime) *Object {
+		return r.global.ObjectPrototype
+	}
+
+	t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(0), false, false, false) })
+
+	t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getString(), true, false, true) })
+
+	t.putStr("at", func(r *Runtime) Value { return r.methodProp(r.stringproto_at, "at", 1) })
+	t.putStr("charAt", func(r *Runtime) Value { return r.methodProp(r.stringproto_charAt, "charAt", 1) })
+	t.putStr("charCodeAt", func(r *Runtime) Value { return r.methodProp(r.stringproto_charCodeAt, "charCodeAt", 1) })
+	t.putStr("codePointAt", func(r *Runtime) Value { return r.methodProp(r.stringproto_codePointAt, "codePointAt", 1) })
+	t.putStr("concat", func(r *Runtime) Value { return r.methodProp(r.stringproto_concat, "concat", 1) })
+	t.putStr("endsWith", func(r *Runtime) Value { return r.methodProp(r.stringproto_endsWith, "endsWith", 1) })
+	t.putStr("includes", func(r *Runtime) Value { return r.methodProp(r.stringproto_includes, "includes", 1) })
+	t.putStr("indexOf", func(r *Runtime) Value { return r.methodProp(r.stringproto_indexOf, "indexOf", 1) })
+	t.putStr("lastIndexOf", func(r *Runtime) Value { return r.methodProp(r.stringproto_lastIndexOf, "lastIndexOf", 1) })
+	t.putStr("localeCompare", func(r *Runtime) Value { return r.methodProp(r.stringproto_localeCompare, "localeCompare", 1) })
+	t.putStr("match", func(r *Runtime) Value { return r.methodProp(r.stringproto_match, "match", 1) })
+	t.putStr("matchAll", func(r *Runtime) Value { return r.methodProp(r.stringproto_matchAll, "matchAll", 1) })
+	t.putStr("normalize", func(r *Runtime) Value { return r.methodProp(r.stringproto_normalize, "normalize", 0) })
+	t.putStr("padEnd", func(r *Runtime) Value { return r.methodProp(r.stringproto_padEnd, "padEnd", 1) })
+	t.putStr("padStart", func(r *Runtime) Value { return r.methodProp(r.stringproto_padStart, "padStart", 1) })
+	t.putStr("repeat", func(r *Runtime) Value { return r.methodProp(r.stringproto_repeat, "repeat", 1) })
+	t.putStr("replace", func(r *Runtime) Value { return r.methodProp(r.stringproto_replace, "replace", 2) })
+	t.putStr("replaceAll", func(r *Runtime) Value { return r.methodProp(r.stringproto_replaceAll, "replaceAll", 2) })
+	t.putStr("search", func(r *Runtime) Value { return r.methodProp(r.stringproto_search, "search", 1) })
+	t.putStr("slice", func(r *Runtime) Value { return r.methodProp(r.stringproto_slice, "slice", 2) })
+	t.putStr("split", func(r *Runtime) Value { return r.methodProp(r.stringproto_split, "split", 2) })
+	t.putStr("startsWith", func(r *Runtime) Value { return r.methodProp(r.stringproto_startsWith, "startsWith", 1) })
+	t.putStr("substring", func(r *Runtime) Value { return r.methodProp(r.stringproto_substring, "substring", 2) })
+	t.putStr("toLocaleLowerCase", func(r *Runtime) Value { return r.methodProp(r.stringproto_toLowerCase, "toLocaleLowerCase", 0) })
+	t.putStr("toLocaleUpperCase", func(r *Runtime) Value { return r.methodProp(r.stringproto_toUpperCase, "toLocaleUpperCase", 0) })
+	t.putStr("toLowerCase", func(r *Runtime) Value { return r.methodProp(r.stringproto_toLowerCase, "toLowerCase", 0) })
+	t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.stringproto_toString, "toString", 0) })
+	t.putStr("toUpperCase", func(r *Runtime) Value { return r.methodProp(r.stringproto_toUpperCase, "toUpperCase", 0) })
+	t.putStr("trim", func(r *Runtime) Value { return r.methodProp(r.stringproto_trim, "trim", 0) })
+	t.putStr("trimEnd", func(r *Runtime) Value { return valueProp(r.getStringproto_trimEnd(), true, false, true) })
+	t.putStr("trimStart", func(r *Runtime) Value { return valueProp(r.getStringproto_trimStart(), true, false, true) })
+	t.putStr("trimRight", func(r *Runtime) Value { return valueProp(r.getStringproto_trimEnd(), true, false, true) })
+	t.putStr("trimLeft", func(r *Runtime) Value { return valueProp(r.getStringproto_trimStart(), true, false, true) })
+	t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.stringproto_valueOf, "valueOf", 0) })
 
 	// Annex B
-	o._putProp("substr", r.newNativeFunc(r.stringproto_substr, nil, "substr", nil, 2), true, false, true)
+	t.putStr("substr", func(r *Runtime) Value { return r.methodProp(r.stringproto_substr, "substr", 2) })
+
+	t.putSym(SymIterator, func(r *Runtime) Value {
+		return valueProp(r.newNativeFunc(r.stringproto_iterator, "[Symbol.iterator]", 0), true, false, true)
+	})
 
-	r.global.String = r.newNativeFunc(r.builtin_String, r.builtin_newString, "String", r.global.StringPrototype, 1)
-	o = r.global.String.self
-	o._putProp("fromCharCode", r.newNativeFunc(r.string_fromcharcode, nil, "fromCharCode", nil, 1), true, false, true)
-	o._putProp("fromCodePoint", r.newNativeFunc(r.string_fromcodepoint, nil, "fromCodePoint", nil, 1), true, false, true)
-	o._putProp("raw", r.newNativeFunc(r.string_raw, nil, "raw", nil, 1), true, false, true)
+	return t
+}
 
-	r.addToGlobal("String", r.global.String)
+func (r *Runtime) getStringproto_trimEnd() *Object {
+	ret := r.global.stringproto_trimEnd
+	if ret == nil {
+		ret = r.newNativeFunc(r.stringproto_trimEnd, "trimEnd", 0)
+		r.global.stringproto_trimEnd = ret
+	}
+	return ret
+}
 
-	r.stringSingleton = r.builtin_new(r.global.String, nil).self.(*stringObject)
+func (r *Runtime) getStringproto_trimStart() *Object {
+	ret := r.global.stringproto_trimStart
+	if ret == nil {
+		ret = r.newNativeFunc(r.stringproto_trimStart, "trimStart", 0)
+		r.global.stringproto_trimStart = ret
+	}
+	return ret
+}
+
+func (r *Runtime) getStringSingleton() *stringObject {
+	ret := r.stringSingleton
+	if ret == nil {
+		ret = r.builtin_new(r.getString(), nil).self.(*stringObject)
+		r.stringSingleton = ret
+	}
+	return ret
+}
+
+func (r *Runtime) getString() *Object {
+	ret := r.global.String
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.String = ret
+		proto := r.getStringPrototype()
+		o := r.newNativeFuncAndConstruct(ret, r.builtin_String, r.wrapNativeConstruct(r.builtin_newString, ret, proto), proto, "String", intToValue(1))
+		ret.self = o
+		o._putProp("fromCharCode", r.newNativeFunc(r.string_fromcharcode, "fromCharCode", 1), true, false, true)
+		o._putProp("fromCodePoint", r.newNativeFunc(r.string_fromcodepoint, "fromCodePoint", 1), true, false, true)
+		o._putProp("raw", r.newNativeFunc(r.string_raw, "raw", 1), true, false, true)
+	}
+	return ret
+}
+
+var stringProtoTemplate *objectTemplate
+var stringProtoTemplateOnce sync.Once
+
+func getStringProtoTemplate() *objectTemplate {
+	stringProtoTemplateOnce.Do(func() {
+		stringProtoTemplate = createStringProtoTemplate()
+	})
+	return stringProtoTemplate
+}
+
+func (r *Runtime) getStringPrototype() *Object {
+	ret := r.global.StringPrototype
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.StringPrototype = ret
+		o := r.newTemplatedObject(getStringProtoTemplate(), ret)
+		o.class = classString
+	}
+	return ret
 }

+ 26 - 14
builtin_symbol.go

@@ -110,29 +110,29 @@ func (r *Runtime) createSymbolProto(val *Object) objectImpl {
 	}
 	o.init()
 
-	o._putProp("constructor", r.global.Symbol, true, false, true)
+	o._putProp("constructor", r.getSymbol(), true, false, true)
 	o.setOwnStr("description", &valueProperty{
 		configurable: true,
 		getterFunc: r.newNativeFunc(func(call FunctionCall) Value {
 			return r.thisSymbolValue(call.This).desc
-		}, nil, "get description", nil, 0),
+		}, "get description", 0),
 		accessor: true,
 	}, false)
-	o._putProp("toString", r.newNativeFunc(r.symbolproto_tostring, nil, "toString", nil, 0), true, false, true)
-	o._putProp("valueOf", r.newNativeFunc(r.symbolproto_valueOf, nil, "valueOf", nil, 0), true, false, true)
-	o._putSym(SymToPrimitive, valueProp(r.newNativeFunc(r.symbolproto_valueOf, nil, "[Symbol.toPrimitive]", nil, 1), false, false, true))
+	o._putProp("toString", r.newNativeFunc(r.symbolproto_tostring, "toString", 0), true, false, true)
+	o._putProp("valueOf", r.newNativeFunc(r.symbolproto_valueOf, "valueOf", 0), true, false, true)
+	o._putSym(SymToPrimitive, valueProp(r.newNativeFunc(r.symbolproto_valueOf, "[Symbol.toPrimitive]", 1), false, false, true))
 	o._putSym(SymToStringTag, valueProp(newStringValue("Symbol"), false, false, true))
 
 	return o
 }
 
 func (r *Runtime) createSymbol(val *Object) objectImpl {
-	o := r.newNativeFuncObj(val, r.builtin_symbol, func(args []Value, proto *Object) *Object {
+	o := r.newNativeFuncAndConstruct(val, r.builtin_symbol, func(args []Value, newTarget *Object) *Object {
 		panic(r.NewTypeError("Symbol is not a constructor"))
-	}, "Symbol", r.global.SymbolPrototype, _positiveZero)
+	}, r.getSymbolPrototype(), "Symbol", _positiveZero)
 
-	o._putProp("for", r.newNativeFunc(r.symbol_for, nil, "for", nil, 1), true, false, true)
-	o._putProp("keyFor", r.newNativeFunc(r.symbol_keyfor, nil, "keyFor", nil, 1), true, false, true)
+	o._putProp("for", r.newNativeFunc(r.symbol_for, "for", 1), true, false, true)
+	o._putProp("keyFor", r.newNativeFunc(r.symbol_keyfor, "keyFor", 1), true, false, true)
 
 	for _, s := range []*Symbol{
 		SymHasInstance,
@@ -156,10 +156,22 @@ func (r *Runtime) createSymbol(val *Object) objectImpl {
 	return o
 }
 
-func (r *Runtime) initSymbol() {
-	r.global.SymbolPrototype = r.newLazyObject(r.createSymbolProto)
-
-	r.global.Symbol = r.newLazyObject(r.createSymbol)
-	r.addToGlobal("Symbol", r.global.Symbol)
+func (r *Runtime) getSymbolPrototype() *Object {
+	ret := r.global.SymbolPrototype
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.SymbolPrototype = ret
+		ret.self = r.createSymbolProto(ret)
+	}
+	return ret
+}
 
+func (r *Runtime) getSymbol() *Object {
+	ret := r.global.Symbol
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Symbol = ret
+		ret.self = r.createSymbol(ret)
+	}
+	return ret
 }

+ 337 - 156
builtin_typedarrays.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"math"
 	"sort"
+	"sync"
 	"unsafe"
 
 	"github.com/dop251/goja/unistring"
@@ -78,7 +79,7 @@ func (r *Runtime) builtin_newArrayBuffer(args []Value, newTarget *Object) *Objec
 	if newTarget == nil {
 		panic(r.needNew("ArrayBuffer"))
 	}
-	b := r._newArrayBuffer(r.getPrototypeFromCtor(newTarget, r.global.ArrayBuffer, r.global.ArrayBufferPrototype), nil)
+	b := r._newArrayBuffer(r.getPrototypeFromCtor(newTarget, r.getArrayBuffer(), r.getArrayBufferPrototype()), nil)
 	if len(args) > 0 {
 		b.data = allocByteSlice(r.toIndex(args[0]))
 	}
@@ -109,7 +110,7 @@ func (r *Runtime) arrayBufferProto_slice(call FunctionCall) Value {
 		}
 		stop = relToIdx(stop, l)
 		newLen := max(stop-start, 0)
-		ret := r.speciesConstructor(o, r.global.ArrayBuffer)([]Value{intToValue(newLen)}, nil)
+		ret := r.speciesConstructor(o, r.getArrayBuffer())([]Value{intToValue(newLen)}, nil)
 		if ab, ok := ret.self.(*arrayBufferObject); ok {
 			if newLen > 0 {
 				b.ensureNotDetached(true)
@@ -145,7 +146,7 @@ func (r *Runtime) newDataView(args []Value, newTarget *Object) *Object {
 	if newTarget == nil {
 		panic(r.needNew("DataView"))
 	}
-	proto := r.getPrototypeFromCtor(newTarget, r.global.DataView, r.global.DataViewPrototype)
+	proto := r.getPrototypeFromCtor(newTarget, r.getDataView(), r.getDataViewPrototype())
 	var bufArg Value
 	if len(args) > 0 {
 		bufArg = args[0]
@@ -165,13 +166,13 @@ func (r *Runtime) newDataView(args []Value, newTarget *Object) *Object {
 		byteOffset = r.toIndex(offsetArg)
 		buffer.ensureNotDetached(true)
 		if byteOffset > len(buffer.data) {
-			panic(r.newError(r.global.RangeError, "Start offset %s is outside the bounds of the buffer", offsetArg.String()))
+			panic(r.newError(r.getRangeError(), "Start offset %s is outside the bounds of the buffer", offsetArg.String()))
 		}
 	}
 	if len(args) > 2 && args[2] != nil && args[2] != _undefined {
 		byteLen = r.toIndex(args[2])
 		if byteOffset+byteLen > len(buffer.data) {
-			panic(r.newError(r.global.RangeError, "Invalid DataView length %d", byteLen))
+			panic(r.newError(r.getRangeError(), "Invalid DataView length %d", byteLen))
 		}
 	} else {
 		byteLen = len(buffer.data) - byteOffset
@@ -945,7 +946,7 @@ func (r *Runtime) typedArrayProto_set(call FunctionCall) Value {
 		srcObj := call.Argument(0).ToObject(r)
 		targetOffset := toIntStrict(call.Argument(1).ToInteger())
 		if targetOffset < 0 {
-			panic(r.newError(r.global.RangeError, "offset should be >= 0"))
+			panic(r.newError(r.getRangeError(), "offset should be >= 0"))
 		}
 		ta.viewedArrayBuf.ensureNotDetached(true)
 		targetLen := ta.length
@@ -953,7 +954,7 @@ func (r *Runtime) typedArrayProto_set(call FunctionCall) Value {
 			src.viewedArrayBuf.ensureNotDetached(true)
 			srcLen := src.length
 			if x := srcLen + targetOffset; x < 0 || x > targetLen {
-				panic(r.newError(r.global.RangeError, "Source is too large"))
+				panic(r.newError(r.getRangeError(), "Source is too large"))
 			}
 			if src.defaultCtor == ta.defaultCtor {
 				copy(ta.viewedArrayBuf.data[(ta.offset+targetOffset)*ta.elemSize:],
@@ -1002,7 +1003,7 @@ func (r *Runtime) typedArrayProto_set(call FunctionCall) Value {
 			targetLen := ta.length
 			srcLen := toIntStrict(toLength(srcObj.self.getStr("length", nil)))
 			if x := srcLen + targetOffset; x < 0 || x > targetLen {
-				panic(r.newError(r.global.RangeError, "Source is too large"))
+				panic(r.newError(r.getRangeError(), "Source is too large"))
 			}
 			for i := 0; i < srcLen; i++ {
 				val := nilSafe(srcObj.self.getIdx(valueInt(i), nil))
@@ -1297,7 +1298,7 @@ func (r *Runtime) _newTypedArrayFromArrayBuffer(ab *arrayBufferObject, args []Va
 	if len(args) > 1 && args[1] != nil && args[1] != _undefined {
 		byteOffset = r.toIndex(args[1])
 		if byteOffset%ta.elemSize != 0 {
-			panic(r.newError(r.global.RangeError, "Start offset of %s should be a multiple of %d", newTarget.self.getStr("name", nil), ta.elemSize))
+			panic(r.newError(r.getRangeError(), "Start offset of %s should be a multiple of %d", newTarget.self.getStr("name", nil), ta.elemSize))
 		}
 	}
 	var length int
@@ -1305,16 +1306,16 @@ func (r *Runtime) _newTypedArrayFromArrayBuffer(ab *arrayBufferObject, args []Va
 		length = r.toIndex(args[2])
 		ab.ensureNotDetached(true)
 		if byteOffset+length*ta.elemSize > len(ab.data) {
-			panic(r.newError(r.global.RangeError, "Invalid typed array length: %d", length))
+			panic(r.newError(r.getRangeError(), "Invalid typed array length: %d", length))
 		}
 	} else {
 		ab.ensureNotDetached(true)
 		if len(ab.data)%ta.elemSize != 0 {
-			panic(r.newError(r.global.RangeError, "Byte length of %s should be a multiple of %d", newTarget.self.getStr("name", nil), ta.elemSize))
+			panic(r.newError(r.getRangeError(), "Byte length of %s should be a multiple of %d", newTarget.self.getStr("name", nil), ta.elemSize))
 		}
 		length = (len(ab.data) - byteOffset) / ta.elemSize
 		if length < 0 {
-			panic(r.newError(r.global.RangeError, "Start offset %d is outside the bounds of the buffer", byteOffset))
+			panic(r.newError(r.getRangeError(), "Start offset %d is outside the bounds of the buffer", byteOffset))
 		}
 	}
 	ta.offset = byteOffset / ta.elemSize
@@ -1327,7 +1328,8 @@ func (r *Runtime) _newTypedArrayFromTypedArray(src *typedArrayObject, newTarget
 	src.viewedArrayBuf.ensureNotDetached(true)
 	l := src.length
 
-	dst.viewedArrayBuf.prototype = r.getPrototypeFromCtor(r.speciesConstructorObj(src.viewedArrayBuf.val, r.global.ArrayBuffer), r.global.ArrayBuffer, r.global.ArrayBufferPrototype)
+	arrayBuffer := r.getArrayBuffer()
+	dst.viewedArrayBuf.prototype = r.getPrototypeFromCtor(r.speciesConstructorObj(src.viewedArrayBuf.val, arrayBuffer), arrayBuffer, r.getArrayBufferPrototype())
 	dst.viewedArrayBuf.data = allocByteSlice(toIntStrict(int64(l) * int64(dst.elemSize)))
 	src.viewedArrayBuf.ensureNotDetached(true)
 	if src.defaultCtor == dst.defaultCtor {
@@ -1408,192 +1410,371 @@ func (r *Runtime) createArrayBufferProto(val *Object) objectImpl {
 	byteLengthProp := &valueProperty{
 		accessor:     true,
 		configurable: true,
-		getterFunc:   r.newNativeFunc(r.arrayBufferProto_getByteLength, nil, "get byteLength", nil, 0),
+		getterFunc:   r.newNativeFunc(r.arrayBufferProto_getByteLength, "get byteLength", 0),
 	}
 	b._put("byteLength", byteLengthProp)
-	b._putProp("constructor", r.global.ArrayBuffer, true, false, true)
-	b._putProp("slice", r.newNativeFunc(r.arrayBufferProto_slice, nil, "slice", nil, 2), true, false, true)
+	b._putProp("constructor", r.getArrayBuffer(), true, false, true)
+	b._putProp("slice", r.newNativeFunc(r.arrayBufferProto_slice, "slice", 2), true, false, true)
 	b._putSym(SymToStringTag, valueProp(asciiString("ArrayBuffer"), false, false, true))
 	return b
 }
 
 func (r *Runtime) createArrayBuffer(val *Object) objectImpl {
-	o := r.newNativeConstructOnly(val, r.builtin_newArrayBuffer, r.global.ArrayBufferPrototype, "ArrayBuffer", 1)
-	o._putProp("isView", r.newNativeFunc(r.arrayBuffer_isView, nil, "isView", nil, 1), true, false, true)
+	o := r.newNativeConstructOnly(val, r.builtin_newArrayBuffer, r.getArrayBufferPrototype(), "ArrayBuffer", 1)
+	o._putProp("isView", r.newNativeFunc(r.arrayBuffer_isView, "isView", 1), true, false, true)
 	r.putSpeciesReturnThis(o)
 
 	return o
 }
 
-func (r *Runtime) createDataViewProto(val *Object) objectImpl {
-	b := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
-	b._put("buffer", &valueProperty{
-		accessor:     true,
-		configurable: true,
-		getterFunc:   r.newNativeFunc(r.dataViewProto_getBuffer, nil, "get buffer", nil, 0),
-	})
-	b._put("byteLength", &valueProperty{
-		accessor:     true,
-		configurable: true,
-		getterFunc:   r.newNativeFunc(r.dataViewProto_getByteLen, nil, "get byteLength", nil, 0),
-	})
-	b._put("byteOffset", &valueProperty{
-		accessor:     true,
-		configurable: true,
-		getterFunc:   r.newNativeFunc(r.dataViewProto_getByteOffset, nil, "get byteOffset", nil, 0),
-	})
-	b._putProp("constructor", r.global.DataView, true, false, true)
-	b._putProp("getFloat32", r.newNativeFunc(r.dataViewProto_getFloat32, nil, "getFloat32", nil, 1), true, false, true)
-	b._putProp("getFloat64", r.newNativeFunc(r.dataViewProto_getFloat64, nil, "getFloat64", nil, 1), true, false, true)
-	b._putProp("getInt8", r.newNativeFunc(r.dataViewProto_getInt8, nil, "getInt8", nil, 1), true, false, true)
-	b._putProp("getInt16", r.newNativeFunc(r.dataViewProto_getInt16, nil, "getInt16", nil, 1), true, false, true)
-	b._putProp("getInt32", r.newNativeFunc(r.dataViewProto_getInt32, nil, "getInt32", nil, 1), true, false, true)
-	b._putProp("getUint8", r.newNativeFunc(r.dataViewProto_getUint8, nil, "getUint8", nil, 1), true, false, true)
-	b._putProp("getUint16", r.newNativeFunc(r.dataViewProto_getUint16, nil, "getUint16", nil, 1), true, false, true)
-	b._putProp("getUint32", r.newNativeFunc(r.dataViewProto_getUint32, nil, "getUint32", nil, 1), true, false, true)
-	b._putProp("setFloat32", r.newNativeFunc(r.dataViewProto_setFloat32, nil, "setFloat32", nil, 2), true, false, true)
-	b._putProp("setFloat64", r.newNativeFunc(r.dataViewProto_setFloat64, nil, "setFloat64", nil, 2), true, false, true)
-	b._putProp("setInt8", r.newNativeFunc(r.dataViewProto_setInt8, nil, "setInt8", nil, 2), true, false, true)
-	b._putProp("setInt16", r.newNativeFunc(r.dataViewProto_setInt16, nil, "setInt16", nil, 2), true, false, true)
-	b._putProp("setInt32", r.newNativeFunc(r.dataViewProto_setInt32, nil, "setInt32", nil, 2), true, false, true)
-	b._putProp("setUint8", r.newNativeFunc(r.dataViewProto_setUint8, nil, "setUint8", nil, 2), true, false, true)
-	b._putProp("setUint16", r.newNativeFunc(r.dataViewProto_setUint16, nil, "setUint16", nil, 2), true, false, true)
-	b._putProp("setUint32", r.newNativeFunc(r.dataViewProto_setUint32, nil, "setUint32", nil, 2), true, false, true)
-	b._putSym(SymToStringTag, valueProp(asciiString("DataView"), false, false, true))
-
-	return b
+func (r *Runtime) createDataView(val *Object) objectImpl {
+	o := r.newNativeConstructOnly(val, r.newDataView, r.getDataViewPrototype(), "DataView", 1)
+	return o
 }
 
-func (r *Runtime) createDataView(val *Object) objectImpl {
-	o := r.newNativeConstructOnly(val, r.newDataView, r.global.DataViewPrototype, "DataView", 1)
+func (r *Runtime) createTypedArray(val *Object) objectImpl {
+	o := r.newNativeConstructOnly(val, r.newTypedArray, r.getTypedArrayPrototype(), "TypedArray", 0)
+	o._putProp("from", r.newNativeFunc(r.typedArray_from, "from", 1), true, false, true)
+	o._putProp("of", r.newNativeFunc(r.typedArray_of, "of", 0), true, false, true)
+	r.putSpeciesReturnThis(o)
+
 	return o
 }
 
-func (r *Runtime) createTypedArrayProto(val *Object) objectImpl {
-	b := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
-	b._put("buffer", &valueProperty{
-		accessor:     true,
-		configurable: true,
-		getterFunc:   r.newNativeFunc(r.typedArrayProto_getBuffer, nil, "get buffer", nil, 0),
+func (r *Runtime) getTypedArray() *Object {
+	ret := r.global.TypedArray
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.TypedArray = ret
+		r.createTypedArray(ret)
+	}
+	return ret
+}
+
+func (r *Runtime) createTypedArrayCtor(val *Object, ctor func(args []Value, newTarget, proto *Object) *Object, name unistring.String, bytesPerElement int) {
+	p := r.newBaseObject(r.getTypedArrayPrototype(), classObject)
+	o := r.newNativeConstructOnly(val, func(args []Value, newTarget *Object) *Object {
+		return ctor(args, newTarget, p.val)
+	}, p.val, name, 3)
+
+	p._putProp("constructor", o.val, true, false, true)
+
+	o.prototype = r.getTypedArray()
+	bpe := intToValue(int64(bytesPerElement))
+	o._putProp("BYTES_PER_ELEMENT", bpe, false, false, false)
+	p._putProp("BYTES_PER_ELEMENT", bpe, false, false, false)
+}
+
+func addTypedArrays(t *objectTemplate) {
+	t.putStr("ArrayBuffer", func(r *Runtime) Value { return valueProp(r.getArrayBuffer(), true, false, true) })
+	t.putStr("DataView", func(r *Runtime) Value { return valueProp(r.getDataView(), true, false, true) })
+	t.putStr("Uint8Array", func(r *Runtime) Value { return valueProp(r.getUint8Array(), true, false, true) })
+	t.putStr("Uint8ClampedArray", func(r *Runtime) Value { return valueProp(r.getUint8ClampedArray(), true, false, true) })
+	t.putStr("Int8Array", func(r *Runtime) Value { return valueProp(r.getInt8Array(), true, false, true) })
+	t.putStr("Uint16Array", func(r *Runtime) Value { return valueProp(r.getUint16Array(), true, false, true) })
+	t.putStr("Int16Array", func(r *Runtime) Value { return valueProp(r.getInt16Array(), true, false, true) })
+	t.putStr("Uint32Array", func(r *Runtime) Value { return valueProp(r.getUint32Array(), true, false, true) })
+	t.putStr("Int32Array", func(r *Runtime) Value { return valueProp(r.getInt32Array(), true, false, true) })
+	t.putStr("Float32Array", func(r *Runtime) Value { return valueProp(r.getFloat32Array(), true, false, true) })
+	t.putStr("Float64Array", func(r *Runtime) Value { return valueProp(r.getFloat64Array(), true, false, true) })
+}
+
+func createTypedArrayProtoTemplate() *objectTemplate {
+	t := newObjectTemplate()
+	t.protoFactory = func(r *Runtime) *Object {
+		return r.global.ObjectPrototype
+	}
+
+	t.putStr("buffer", func(r *Runtime) Value {
+		return &valueProperty{
+			accessor:     true,
+			configurable: true,
+			getterFunc:   r.newNativeFunc(r.typedArrayProto_getBuffer, "get buffer", 0),
+		}
 	})
-	b._put("byteLength", &valueProperty{
-		accessor:     true,
-		configurable: true,
-		getterFunc:   r.newNativeFunc(r.typedArrayProto_getByteLen, nil, "get byteLength", nil, 0),
+
+	t.putStr("byteLength", func(r *Runtime) Value {
+		return &valueProperty{
+			accessor:     true,
+			configurable: true,
+			getterFunc:   r.newNativeFunc(r.typedArrayProto_getByteLen, "get byteLength", 0),
+		}
 	})
-	b._put("byteOffset", &valueProperty{
-		accessor:     true,
-		configurable: true,
-		getterFunc:   r.newNativeFunc(r.typedArrayProto_getByteOffset, nil, "get byteOffset", nil, 0),
+
+	t.putStr("byteOffset", func(r *Runtime) Value {
+		return &valueProperty{
+			accessor:     true,
+			configurable: true,
+			getterFunc:   r.newNativeFunc(r.typedArrayProto_getByteOffset, "get byteOffset", 0),
+		}
 	})
-	b._putProp("at", r.newNativeFunc(r.typedArrayProto_at, nil, "at", nil, 1), true, false, true)
-	b._putProp("constructor", r.global.TypedArray, true, false, true)
-	b._putProp("copyWithin", r.newNativeFunc(r.typedArrayProto_copyWithin, nil, "copyWithin", nil, 2), true, false, true)
-	b._putProp("entries", r.newNativeFunc(r.typedArrayProto_entries, nil, "entries", nil, 0), true, false, true)
-	b._putProp("every", r.newNativeFunc(r.typedArrayProto_every, nil, "every", nil, 1), true, false, true)
-	b._putProp("fill", r.newNativeFunc(r.typedArrayProto_fill, nil, "fill", nil, 1), true, false, true)
-	b._putProp("filter", r.newNativeFunc(r.typedArrayProto_filter, nil, "filter", nil, 1), true, false, true)
-	b._putProp("find", r.newNativeFunc(r.typedArrayProto_find, nil, "find", nil, 1), true, false, true)
-	b._putProp("findIndex", r.newNativeFunc(r.typedArrayProto_findIndex, nil, "findIndex", nil, 1), true, false, true)
-	b._putProp("findLast", r.newNativeFunc(r.typedArrayProto_findLast, nil, "findLast", nil, 1), true, false, true)
-	b._putProp("findLastIndex", r.newNativeFunc(r.typedArrayProto_findLastIndex, nil, "findLastIndex", nil, 1), true, false, true)
-	b._putProp("forEach", r.newNativeFunc(r.typedArrayProto_forEach, nil, "forEach", nil, 1), true, false, true)
-	b._putProp("includes", r.newNativeFunc(r.typedArrayProto_includes, nil, "includes", nil, 1), true, false, true)
-	b._putProp("indexOf", r.newNativeFunc(r.typedArrayProto_indexOf, nil, "indexOf", nil, 1), true, false, true)
-	b._putProp("join", r.newNativeFunc(r.typedArrayProto_join, nil, "join", nil, 1), true, false, true)
-	b._putProp("keys", r.newNativeFunc(r.typedArrayProto_keys, nil, "keys", nil, 0), true, false, true)
-	b._putProp("lastIndexOf", r.newNativeFunc(r.typedArrayProto_lastIndexOf, nil, "lastIndexOf", nil, 1), true, false, true)
-	b._put("length", &valueProperty{
-		accessor:     true,
-		configurable: true,
-		getterFunc:   r.newNativeFunc(r.typedArrayProto_getLength, nil, "get length", nil, 0),
+
+	t.putStr("at", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_at, "at", 1) })
+	t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getTypedArray(), true, false, true) })
+	t.putStr("copyWithin", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_copyWithin, "copyWithin", 2) })
+	t.putStr("entries", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_entries, "entries", 0) })
+	t.putStr("every", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_every, "every", 1) })
+	t.putStr("fill", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_fill, "fill", 1) })
+	t.putStr("filter", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_filter, "filter", 1) })
+	t.putStr("find", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_find, "find", 1) })
+	t.putStr("findIndex", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_findIndex, "findIndex", 1) })
+	t.putStr("findLast", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_findLast, "findLast", 1) })
+	t.putStr("findLastIndex", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_findLastIndex, "findLastIndex", 1) })
+	t.putStr("forEach", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_forEach, "forEach", 1) })
+	t.putStr("includes", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_includes, "includes", 1) })
+	t.putStr("indexOf", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_indexOf, "indexOf", 1) })
+	t.putStr("join", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_join, "join", 1) })
+	t.putStr("keys", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_keys, "keys", 0) })
+	t.putStr("lastIndexOf", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_lastIndexOf, "lastIndexOf", 1) })
+	t.putStr("length", func(r *Runtime) Value {
+		return &valueProperty{
+			accessor:     true,
+			configurable: true,
+			getterFunc:   r.newNativeFunc(r.typedArrayProto_getLength, "get length", 0),
+		}
 	})
-	b._putProp("map", r.newNativeFunc(r.typedArrayProto_map, nil, "map", nil, 1), true, false, true)
-	b._putProp("reduce", r.newNativeFunc(r.typedArrayProto_reduce, nil, "reduce", nil, 1), true, false, true)
-	b._putProp("reduceRight", r.newNativeFunc(r.typedArrayProto_reduceRight, nil, "reduceRight", nil, 1), true, false, true)
-	b._putProp("reverse", r.newNativeFunc(r.typedArrayProto_reverse, nil, "reverse", nil, 0), true, false, true)
-	b._putProp("set", r.newNativeFunc(r.typedArrayProto_set, nil, "set", nil, 1), true, false, true)
-	b._putProp("slice", r.newNativeFunc(r.typedArrayProto_slice, nil, "slice", nil, 2), true, false, true)
-	b._putProp("some", r.newNativeFunc(r.typedArrayProto_some, nil, "some", nil, 1), true, false, true)
-	b._putProp("sort", r.newNativeFunc(r.typedArrayProto_sort, nil, "sort", nil, 1), true, false, true)
-	b._putProp("subarray", r.newNativeFunc(r.typedArrayProto_subarray, nil, "subarray", nil, 2), true, false, true)
-	b._putProp("toLocaleString", r.newNativeFunc(r.typedArrayProto_toLocaleString, nil, "toLocaleString", nil, 0), true, false, true)
-	b._putProp("toString", r.global.arrayToString, true, false, true)
-	valuesFunc := r.newNativeFunc(r.typedArrayProto_values, nil, "values", nil, 0)
-	b._putProp("values", valuesFunc, true, false, true)
-	b._putSym(SymIterator, valueProp(valuesFunc, true, false, true))
-	b._putSym(SymToStringTag, &valueProperty{
-		getterFunc:   r.newNativeFunc(r.typedArrayProto_toStringTag, nil, "get [Symbol.toStringTag]", nil, 0),
-		accessor:     true,
-		configurable: true,
+	t.putStr("map", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_map, "map", 1) })
+	t.putStr("reduce", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_reduce, "reduce", 1) })
+	t.putStr("reduceRight", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_reduceRight, "reduceRight", 1) })
+	t.putStr("reverse", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_reverse, "reverse", 0) })
+	t.putStr("set", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_set, "set", 1) })
+	t.putStr("slice", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_slice, "slice", 2) })
+	t.putStr("some", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_some, "some", 1) })
+	t.putStr("sort", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_sort, "sort", 1) })
+	t.putStr("subarray", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_subarray, "subarray", 2) })
+	t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_toLocaleString, "toLocaleString", 0) })
+	t.putStr("toString", func(r *Runtime) Value { return valueProp(r.getArrayToString(), true, false, true) })
+	t.putStr("values", func(r *Runtime) Value { return valueProp(r.getTypedArrayValues(), true, false, true) })
+
+	t.putSym(SymIterator, func(r *Runtime) Value { return valueProp(r.getTypedArrayValues(), true, false, true) })
+	t.putSym(SymToStringTag, func(r *Runtime) Value {
+		return &valueProperty{
+			getterFunc:   r.newNativeFunc(r.typedArrayProto_toStringTag, "get [Symbol.toStringTag]", 0),
+			accessor:     true,
+			configurable: true,
+		}
 	})
 
-	return b
+	return t
 }
 
-func (r *Runtime) createTypedArray(val *Object) objectImpl {
-	o := r.newNativeConstructOnly(val, r.newTypedArray, r.global.TypedArrayPrototype, "TypedArray", 0)
-	o._putProp("from", r.newNativeFunc(r.typedArray_from, nil, "from", nil, 1), true, false, true)
-	o._putProp("of", r.newNativeFunc(r.typedArray_of, nil, "of", nil, 0), true, false, true)
-	r.putSpeciesReturnThis(o)
+func (r *Runtime) getTypedArrayValues() *Object {
+	ret := r.global.typedArrayValues
+	if ret == nil {
+		ret = r.newNativeFunc(r.typedArrayProto_values, "values", 0)
+		r.global.typedArrayValues = ret
+	}
+	return ret
+}
 
-	return o
+var typedArrayProtoTemplate *objectTemplate
+var typedArrayProtoTemplateOnce sync.Once
+
+func getTypedArrayProtoTemplate() *objectTemplate {
+	typedArrayProtoTemplateOnce.Do(func() {
+		typedArrayProtoTemplate = createTypedArrayProtoTemplate()
+	})
+	return typedArrayProtoTemplate
+}
+
+func (r *Runtime) getTypedArrayPrototype() *Object {
+	ret := r.global.TypedArrayPrototype
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.TypedArrayPrototype = ret
+		r.newTemplatedObject(getTypedArrayProtoTemplate(), ret)
+	}
+	return ret
+}
+
+func (r *Runtime) getUint8Array() *Object {
+	ret := r.global.Uint8Array
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Uint8Array = ret
+		r.createTypedArrayCtor(ret, r.newUint8Array, "Uint8Array", 1)
+	}
+	return ret
+}
+
+func (r *Runtime) getUint8ClampedArray() *Object {
+	ret := r.global.Uint8ClampedArray
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Uint8ClampedArray = ret
+		r.createTypedArrayCtor(ret, r.newUint8ClampedArray, "Uint8ClampedArray", 1)
+	}
+	return ret
+}
+
+func (r *Runtime) getInt8Array() *Object {
+	ret := r.global.Int8Array
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Int8Array = ret
+		r.createTypedArrayCtor(ret, r.newInt8Array, "Int8Array", 1)
+	}
+	return ret
+}
+
+func (r *Runtime) getUint16Array() *Object {
+	ret := r.global.Uint16Array
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Uint16Array = ret
+		r.createTypedArrayCtor(ret, r.newUint16Array, "Uint16Array", 2)
+	}
+	return ret
+}
+
+func (r *Runtime) getInt16Array() *Object {
+	ret := r.global.Int16Array
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Int16Array = ret
+		r.createTypedArrayCtor(ret, r.newInt16Array, "Int16Array", 2)
+	}
+	return ret
 }
 
-func (r *Runtime) typedArrayCreator(ctor func(args []Value, newTarget, proto *Object) *Object, name unistring.String, bytesPerElement int) func(val *Object) objectImpl {
-	return func(val *Object) objectImpl {
-		p := r.newBaseObject(r.global.TypedArrayPrototype, classObject)
-		o := r.newNativeConstructOnly(val, func(args []Value, newTarget *Object) *Object {
-			return ctor(args, newTarget, p.val)
-		}, p.val, name, 3)
+func (r *Runtime) getUint32Array() *Object {
+	ret := r.global.Uint32Array
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Uint32Array = ret
+		r.createTypedArrayCtor(ret, r.newUint32Array, "Uint32Array", 4)
+	}
+	return ret
+}
 
-		p._putProp("constructor", o.val, true, false, true)
+func (r *Runtime) getInt32Array() *Object {
+	ret := r.global.Int32Array
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Int32Array = ret
+		r.createTypedArrayCtor(ret, r.newInt32Array, "Int32Array", 4)
+	}
+	return ret
+}
 
-		o.prototype = r.global.TypedArray
-		bpe := intToValue(int64(bytesPerElement))
-		o._putProp("BYTES_PER_ELEMENT", bpe, false, false, false)
-		p._putProp("BYTES_PER_ELEMENT", bpe, false, false, false)
-		return o
+func (r *Runtime) getFloat32Array() *Object {
+	ret := r.global.Float32Array
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Float32Array = ret
+		r.createTypedArrayCtor(ret, r.newFloat32Array, "Float32Array", 4)
 	}
+	return ret
 }
 
-func (r *Runtime) initTypedArrays() {
+func (r *Runtime) getFloat64Array() *Object {
+	ret := r.global.Float64Array
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.Float64Array = ret
+		r.createTypedArrayCtor(ret, r.newFloat64Array, "Float64Array", 8)
+	}
+	return ret
+}
 
-	r.global.ArrayBufferPrototype = r.newLazyObject(r.createArrayBufferProto)
-	r.global.ArrayBuffer = r.newLazyObject(r.createArrayBuffer)
-	r.addToGlobal("ArrayBuffer", r.global.ArrayBuffer)
+func createDataViewProtoTemplate() *objectTemplate {
+	t := newObjectTemplate()
+	t.protoFactory = func(r *Runtime) *Object {
+		return r.global.ObjectPrototype
+	}
 
-	r.global.DataViewPrototype = r.newLazyObject(r.createDataViewProto)
-	r.global.DataView = r.newLazyObject(r.createDataView)
-	r.addToGlobal("DataView", r.global.DataView)
+	t.putStr("buffer", func(r *Runtime) Value {
+		return &valueProperty{
+			accessor:     true,
+			configurable: true,
+			getterFunc:   r.newNativeFunc(r.dataViewProto_getBuffer, "get buffer", 0),
+		}
+	})
+	t.putStr("byteLength", func(r *Runtime) Value {
+		return &valueProperty{
+			accessor:     true,
+			configurable: true,
+			getterFunc:   r.newNativeFunc(r.dataViewProto_getByteLen, "get byteLength", 0),
+		}
+	})
+	t.putStr("byteOffset", func(r *Runtime) Value {
+		return &valueProperty{
+			accessor:     true,
+			configurable: true,
+			getterFunc:   r.newNativeFunc(r.dataViewProto_getByteOffset, "get byteOffset", 0),
+		}
+	})
 
-	r.global.TypedArrayPrototype = r.newLazyObject(r.createTypedArrayProto)
-	r.global.TypedArray = r.newLazyObject(r.createTypedArray)
+	t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getDataView(), true, false, true) })
 
-	r.global.Uint8Array = r.newLazyObject(r.typedArrayCreator(r.newUint8Array, "Uint8Array", 1))
-	r.addToGlobal("Uint8Array", r.global.Uint8Array)
+	t.putStr("getFloat32", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getFloat32, "getFloat32", 1) })
+	t.putStr("getFloat64", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getFloat64, "getFloat64", 1) })
+	t.putStr("getInt8", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getInt8, "getInt8", 1) })
+	t.putStr("getInt16", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getInt16, "getInt16", 1) })
+	t.putStr("getInt32", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getInt32, "getInt32", 1) })
+	t.putStr("getUint8", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getUint8, "getUint8", 1) })
+	t.putStr("getUint16", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getUint16, "getUint16", 1) })
+	t.putStr("getUint32", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getUint32, "getUint32", 1) })
+	t.putStr("setFloat32", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setFloat32, "setFloat32", 2) })
+	t.putStr("setFloat64", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setFloat64, "setFloat64", 2) })
+	t.putStr("setInt8", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setInt8, "setInt8", 2) })
+	t.putStr("setInt16", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setInt16, "setInt16", 2) })
+	t.putStr("setInt32", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setInt32, "setInt32", 2) })
+	t.putStr("setUint8", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setUint8, "setUint8", 2) })
+	t.putStr("setUint16", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setUint16, "setUint16", 2) })
+	t.putStr("setUint32", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setUint32, "setUint32", 2) })
 
-	r.global.Uint8ClampedArray = r.newLazyObject(r.typedArrayCreator(r.newUint8ClampedArray, "Uint8ClampedArray", 1))
-	r.addToGlobal("Uint8ClampedArray", r.global.Uint8ClampedArray)
+	t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString("DataView"), false, false, true) })
 
-	r.global.Int8Array = r.newLazyObject(r.typedArrayCreator(r.newInt8Array, "Int8Array", 1))
-	r.addToGlobal("Int8Array", r.global.Int8Array)
+	return t
+}
 
-	r.global.Uint16Array = r.newLazyObject(r.typedArrayCreator(r.newUint16Array, "Uint16Array", 2))
-	r.addToGlobal("Uint16Array", r.global.Uint16Array)
+var dataViewProtoTemplate *objectTemplate
+var dataViewProtoTemplateOnce sync.Once
 
-	r.global.Int16Array = r.newLazyObject(r.typedArrayCreator(r.newInt16Array, "Int16Array", 2))
-	r.addToGlobal("Int16Array", r.global.Int16Array)
+func getDataViewProtoTemplate() *objectTemplate {
+	dataViewProtoTemplateOnce.Do(func() {
+		dataViewProtoTemplate = createDataViewProtoTemplate()
+	})
+	return dataViewProtoTemplate
+}
 
-	r.global.Uint32Array = r.newLazyObject(r.typedArrayCreator(r.newUint32Array, "Uint32Array", 4))
-	r.addToGlobal("Uint32Array", r.global.Uint32Array)
+func (r *Runtime) getDataViewPrototype() *Object {
+	ret := r.global.DataViewPrototype
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.DataViewPrototype = ret
+		r.newTemplatedObject(getDataViewProtoTemplate(), ret)
+	}
+	return ret
+}
 
-	r.global.Int32Array = r.newLazyObject(r.typedArrayCreator(r.newInt32Array, "Int32Array", 4))
-	r.addToGlobal("Int32Array", r.global.Int32Array)
+func (r *Runtime) getDataView() *Object {
+	ret := r.global.DataView
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.DataView = ret
+		ret.self = r.createDataView(ret)
+	}
+	return ret
+}
 
-	r.global.Float32Array = r.newLazyObject(r.typedArrayCreator(r.newFloat32Array, "Float32Array", 4))
-	r.addToGlobal("Float32Array", r.global.Float32Array)
+func (r *Runtime) getArrayBufferPrototype() *Object {
+	ret := r.global.ArrayBufferPrototype
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.ArrayBufferPrototype = ret
+		ret.self = r.createArrayBufferProto(ret)
+	}
+	return ret
+}
 
-	r.global.Float64Array = r.newLazyObject(r.typedArrayCreator(r.newFloat64Array, "Float64Array", 8))
-	r.addToGlobal("Float64Array", r.global.Float64Array)
+func (r *Runtime) getArrayBuffer() *Object {
+	ret := r.global.ArrayBuffer
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.ArrayBuffer = ret
+		ret.self = r.createArrayBuffer(ret)
+	}
+	return ret
 }

+ 23 - 21
builtin_weakmap.go

@@ -90,17 +90,6 @@ func (r *Runtime) needNew(name string) *Object {
 	return r.NewTypeError("Constructor %s requires 'new'", name)
 }
 
-func (r *Runtime) getPrototypeFromCtor(newTarget, defCtor, defProto *Object) *Object {
-	if newTarget == defCtor {
-		return defProto
-	}
-	proto := newTarget.self.getStr("prototype", nil)
-	if obj, ok := proto.(*Object); ok {
-		return obj
-	}
-	return defProto
-}
-
 func (r *Runtime) builtin_newWeakMap(args []Value, newTarget *Object) *Object {
 	if newTarget == nil {
 		panic(r.needNew("WeakMap"))
@@ -148,12 +137,12 @@ func (r *Runtime) builtin_newWeakMap(args []Value, newTarget *Object) *Object {
 func (r *Runtime) createWeakMapProto(val *Object) objectImpl {
 	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
 
-	o._putProp("constructor", r.global.WeakMap, true, false, true)
-	r.global.weakMapAdder = r.newNativeFunc(r.weakMapProto_set, nil, "set", nil, 2)
+	o._putProp("constructor", r.getWeakMap(), true, false, true)
+	r.global.weakMapAdder = r.newNativeFunc(r.weakMapProto_set, "set", 2)
 	o._putProp("set", r.global.weakMapAdder, true, false, true)
-	o._putProp("delete", r.newNativeFunc(r.weakMapProto_delete, nil, "delete", nil, 1), true, false, true)
-	o._putProp("has", r.newNativeFunc(r.weakMapProto_has, nil, "has", nil, 1), true, false, true)
-	o._putProp("get", r.newNativeFunc(r.weakMapProto_get, nil, "get", nil, 1), true, false, true)
+	o._putProp("delete", r.newNativeFunc(r.weakMapProto_delete, "delete", 1), true, false, true)
+	o._putProp("has", r.newNativeFunc(r.weakMapProto_has, "has", 1), true, false, true)
+	o._putProp("get", r.newNativeFunc(r.weakMapProto_get, "get", 1), true, false, true)
 
 	o._putSym(SymToStringTag, valueProp(asciiString(classWeakMap), false, false, true))
 
@@ -161,14 +150,27 @@ func (r *Runtime) createWeakMapProto(val *Object) objectImpl {
 }
 
 func (r *Runtime) createWeakMap(val *Object) objectImpl {
-	o := r.newNativeConstructOnly(val, r.builtin_newWeakMap, r.global.WeakMapPrototype, "WeakMap", 0)
+	o := r.newNativeConstructOnly(val, r.builtin_newWeakMap, r.getWeakMapPrototype(), "WeakMap", 0)
 
 	return o
 }
 
-func (r *Runtime) initWeakMap() {
-	r.global.WeakMapPrototype = r.newLazyObject(r.createWeakMapProto)
-	r.global.WeakMap = r.newLazyObject(r.createWeakMap)
+func (r *Runtime) getWeakMapPrototype() *Object {
+	ret := r.global.WeakMapPrototype
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.WeakMapPrototype = ret
+		ret.self = r.createWeakMapProto(ret)
+	}
+	return ret
+}
 
-	r.addToGlobal("WeakMap", r.global.WeakMap)
+func (r *Runtime) getWeakMap() *Object {
+	ret := r.global.WeakMap
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.WeakMap = ret
+		ret.self = r.createWeakMap(ret)
+	}
+	return ret
 }

+ 21 - 8
builtin_weakset.go

@@ -98,10 +98,10 @@ func (r *Runtime) createWeakSetProto(val *Object) objectImpl {
 	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
 
 	o._putProp("constructor", r.global.WeakSet, true, false, true)
-	r.global.weakSetAdder = r.newNativeFunc(r.weakSetProto_add, nil, "add", nil, 1)
+	r.global.weakSetAdder = r.newNativeFunc(r.weakSetProto_add, "add", 1)
 	o._putProp("add", r.global.weakSetAdder, true, false, true)
-	o._putProp("delete", r.newNativeFunc(r.weakSetProto_delete, nil, "delete", nil, 1), true, false, true)
-	o._putProp("has", r.newNativeFunc(r.weakSetProto_has, nil, "has", nil, 1), true, false, true)
+	o._putProp("delete", r.newNativeFunc(r.weakSetProto_delete, "delete", 1), true, false, true)
+	o._putProp("has", r.newNativeFunc(r.weakSetProto_has, "has", 1), true, false, true)
 
 	o._putSym(SymToStringTag, valueProp(asciiString(classWeakSet), false, false, true))
 
@@ -109,14 +109,27 @@ func (r *Runtime) createWeakSetProto(val *Object) objectImpl {
 }
 
 func (r *Runtime) createWeakSet(val *Object) objectImpl {
-	o := r.newNativeConstructOnly(val, r.builtin_newWeakSet, r.global.WeakSetPrototype, "WeakSet", 0)
+	o := r.newNativeConstructOnly(val, r.builtin_newWeakSet, r.getWeakSetPrototype(), "WeakSet", 0)
 
 	return o
 }
 
-func (r *Runtime) initWeakSet() {
-	r.global.WeakSetPrototype = r.newLazyObject(r.createWeakSetProto)
-	r.global.WeakSet = r.newLazyObject(r.createWeakSet)
+func (r *Runtime) getWeakSetPrototype() *Object {
+	ret := r.global.WeakSetPrototype
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.WeakSetPrototype = ret
+		ret.self = r.createWeakSetProto(ret)
+	}
+	return ret
+}
 
-	r.addToGlobal("WeakSet", r.global.WeakSet)
+func (r *Runtime) getWeakSet() *Object {
+	ret := r.global.WeakSet
+	if ret == nil {
+		ret = &Object{runtime: r}
+		r.global.WeakSet = ret
+		ret.self = r.createWeakSet(ret)
+	}
+	return ret
 }

+ 10 - 6
func.go

@@ -367,7 +367,7 @@ func (f *classFuncObject) construct(args []Value, newTarget *Object) *Object {
 			if v := r.vm.stack[r.vm.sp+1]; v != nil { // using residual 'this' value (a bit hacky)
 				instance = r.toObject(v)
 			} else {
-				panic(r.newError(r.global.ReferenceError, "Must call super constructor in derived class before returning from derived constructor"))
+				panic(r.newError(r.getReferenceError(), "Must call super constructor in derived class before returning from derived constructor"))
 			}
 		}
 		return instance
@@ -509,9 +509,9 @@ func (f *baseFuncObject) init(name unistring.String, length Value) {
 	f._putProp("name", stringValueFromRaw(name), false, false, true)
 }
 
-func (f *baseFuncObject) hasInstance(v Value) bool {
+func hasInstance(val *Object, v Value) bool {
 	if v, ok := v.(*Object); ok {
-		o := f.val.self.getStr("prototype", nil)
+		o := val.self.getStr("prototype", nil)
 		if o1, ok := o.(*Object); ok {
 			for {
 				v = v.self.proto()
@@ -523,13 +523,17 @@ func (f *baseFuncObject) hasInstance(v Value) bool {
 				}
 			}
 		} else {
-			f.val.runtime.typeErrorResult(true, "prototype is not an object")
+			panic(val.runtime.NewTypeError("prototype is not an object"))
 		}
 	}
 
 	return false
 }
 
+func (f *baseFuncObject) hasInstance(v Value) bool {
+	return hasInstance(f.val, v)
+}
+
 func (f *nativeFuncObject) defaultConstruct(ccall func(ConstructorCall) *Object, args []Value, newTarget *Object) *Object {
 	obj := f.createInstance(newTarget)
 	ret := ccall(ConstructorCall{
@@ -707,7 +711,7 @@ func (ar *asyncRunner) step(res Value, done bool, ex *Exception) {
 	}
 
 	// await
-	promise := r.promiseResolve(r.global.Promise, res)
+	promise := r.promiseResolve(r.getPromise(), res)
 	promise.self.(*Promise).addReactions(&promiseReaction{
 		typ:         promiseReactionFulfill,
 		handler:     &jobCallback{callback: ar.onFulfilled},
@@ -722,7 +726,7 @@ func (ar *asyncRunner) step(res Value, done bool, ex *Exception) {
 func (ar *asyncRunner) start(nArgs int) {
 	r := ar.f.runtime
 	ar.gen.vm = r.vm
-	ar.promiseCap = r.newPromiseCapability(r.global.Promise)
+	ar.promiseCap = r.newPromiseCapability(r.getPromise())
 	sp := r.vm.sp
 	ar.gen.enter()
 	ar.vmCall(r.vm, nArgs)

+ 3 - 3
object.go

@@ -871,7 +871,7 @@ func (o *Object) ordinaryToPrimitiveString() Value {
 		return v
 	}
 
-	panic(o.runtime.NewTypeError("Could not convert %v to primitive", o.self))
+	panic(o.runtime.NewTypeError("Could not convert %v (%T) to primitive", o.self, o.self))
 }
 
 func (o *Object) tryExoticToPrimitive(hint Value) Value {
@@ -916,8 +916,8 @@ func (o *baseObject) assertCallable() (func(FunctionCall) Value, bool) {
 	return nil, false
 }
 
-func (o *baseObject) vmCall(vm *vm, n int) {
-	vm.r.typeErrorResult(true, "Not a function: %s", o.val.toString())
+func (o *baseObject) vmCall(vm *vm, _ int) {
+	panic(vm.r.NewTypeError("Not a function: %s", o.val.toString()))
 }
 
 func (o *baseObject) assertConstructor() func(args []Value, newTarget *Object) *Object {

+ 1 - 1
object_dynamic.go

@@ -141,7 +141,7 @@ func (r *Runtime) NewDynamicArray(a DynamicArray) *Object {
 		a: a,
 		baseDynamicObject: baseDynamicObject{
 			val:       v,
-			prototype: r.global.ArrayPrototype,
+			prototype: r.getArrayPrototype(),
 		},
 	}
 	v.self = o

+ 1 - 1
object_goarray_reflect.go

@@ -59,7 +59,7 @@ func (c *valueArrayCache) shrink(newlen int) {
 func (o *objectGoArrayReflect) _init() {
 	o.objectGoReflect.init()
 	o.class = classArray
-	o.prototype = o.val.runtime.global.ArrayPrototype
+	o.prototype = o.val.runtime.getArrayPrototype()
 	o.baseObject._put("length", &o.lengthProp)
 }
 

+ 5 - 5
object_goreflect.go

@@ -122,24 +122,24 @@ func (o *objectGoReflect) init() {
 	switch o.fieldsValue.Kind() {
 	case reflect.Bool:
 		o.class = classBoolean
-		o.prototype = o.val.runtime.global.BooleanPrototype
+		o.prototype = o.val.runtime.getBooleanPrototype()
 		o.toString = o._toStringBool
 		o.valueOf = o._valueOfBool
 	case reflect.String:
 		o.class = classString
-		o.prototype = o.val.runtime.global.StringPrototype
+		o.prototype = o.val.runtime.getStringPrototype()
 		o.toString = o._toStringString
 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 		o.class = classNumber
-		o.prototype = o.val.runtime.global.NumberPrototype
+		o.prototype = o.val.runtime.getNumberPrototype()
 		o.valueOf = o._valueOfInt
 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 		o.class = classNumber
-		o.prototype = o.val.runtime.global.NumberPrototype
+		o.prototype = o.val.runtime.getNumberPrototype()
 		o.valueOf = o._valueOfUint
 	case reflect.Float32, reflect.Float64:
 		o.class = classNumber
-		o.prototype = o.val.runtime.global.NumberPrototype
+		o.prototype = o.val.runtime.getNumberPrototype()
 		o.valueOf = o._valueOfFloat
 	default:
 		o.class = classObject

+ 1 - 1
object_goslice.go

@@ -34,7 +34,7 @@ func (r *Runtime) newObjectGoSlice(data *[]interface{}, isPtr bool) *objectGoSli
 func (o *objectGoSlice) init() {
 	o.baseObject.init()
 	o.class = classArray
-	o.prototype = o.val.runtime.global.ArrayPrototype
+	o.prototype = o.val.runtime.getArrayPrototype()
 	o.lengthProp.writable = true
 	o.extensible = true
 	o.baseObject._put("length", &o.lengthProp)

+ 0 - 314
object_lazy.go

@@ -1,314 +0,0 @@
-package goja
-
-import (
-	"reflect"
-
-	"github.com/dop251/goja/unistring"
-)
-
-type lazyObject struct {
-	val    *Object
-	create func(*Object) objectImpl
-}
-
-func (o *lazyObject) className() string {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.className()
-}
-
-func (o *lazyObject) typeOf() String {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.typeOf()
-}
-
-func (o *lazyObject) getIdx(p valueInt, receiver Value) Value {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.getIdx(p, receiver)
-}
-
-func (o *lazyObject) getSym(p *Symbol, receiver Value) Value {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.getSym(p, receiver)
-}
-
-func (o *lazyObject) getOwnPropIdx(idx valueInt) Value {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.getOwnPropIdx(idx)
-}
-
-func (o *lazyObject) getOwnPropSym(s *Symbol) Value {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.getOwnPropSym(s)
-}
-
-func (o *lazyObject) hasPropertyIdx(idx valueInt) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.hasPropertyIdx(idx)
-}
-
-func (o *lazyObject) hasPropertySym(s *Symbol) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.hasPropertySym(s)
-}
-
-func (o *lazyObject) hasOwnPropertyIdx(idx valueInt) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.hasOwnPropertyIdx(idx)
-}
-
-func (o *lazyObject) hasOwnPropertySym(s *Symbol) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.hasOwnPropertySym(s)
-}
-
-func (o *lazyObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.defineOwnPropertyStr(name, desc, throw)
-}
-
-func (o *lazyObject) defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.defineOwnPropertyIdx(name, desc, throw)
-}
-
-func (o *lazyObject) defineOwnPropertySym(name *Symbol, desc PropertyDescriptor, throw bool) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.defineOwnPropertySym(name, desc, throw)
-}
-
-func (o *lazyObject) deleteIdx(idx valueInt, throw bool) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.deleteIdx(idx, throw)
-}
-
-func (o *lazyObject) deleteSym(s *Symbol, throw bool) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.deleteSym(s, throw)
-}
-
-func (o *lazyObject) getStr(name unistring.String, receiver Value) Value {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.getStr(name, receiver)
-}
-
-func (o *lazyObject) getOwnPropStr(name unistring.String) Value {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.getOwnPropStr(name)
-}
-
-func (o *lazyObject) setOwnStr(p unistring.String, v Value, throw bool) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.setOwnStr(p, v, throw)
-}
-
-func (o *lazyObject) setOwnIdx(p valueInt, v Value, throw bool) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.setOwnIdx(p, v, throw)
-}
-
-func (o *lazyObject) setOwnSym(p *Symbol, v Value, throw bool) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.setOwnSym(p, v, throw)
-}
-
-func (o *lazyObject) setForeignStr(p unistring.String, v, receiver Value, throw bool) (bool, bool) {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.setForeignStr(p, v, receiver, throw)
-}
-
-func (o *lazyObject) setForeignIdx(p valueInt, v, receiver Value, throw bool) (bool, bool) {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.setForeignIdx(p, v, receiver, throw)
-}
-
-func (o *lazyObject) setForeignSym(p *Symbol, v, receiver Value, throw bool) (bool, bool) {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.setForeignSym(p, v, receiver, throw)
-}
-
-func (o *lazyObject) hasPropertyStr(name unistring.String) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.hasPropertyStr(name)
-}
-
-func (o *lazyObject) hasOwnPropertyStr(name unistring.String) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.hasOwnPropertyStr(name)
-}
-
-func (o *lazyObject) _putProp(unistring.String, Value, bool, bool, bool) Value {
-	panic("cannot use _putProp() in lazy object")
-}
-
-func (o *lazyObject) _putSym(*Symbol, Value) {
-	panic("cannot use _putSym() in lazy object")
-}
-
-func (o *lazyObject) assertCallable() (call func(FunctionCall) Value, ok bool) {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.assertCallable()
-}
-
-func (o *lazyObject) vmCall(vm *vm, n int) {
-	obj := o.create(o.val)
-	o.val.self = obj
-	obj.vmCall(vm, n)
-}
-
-func (o *lazyObject) assertConstructor() func(args []Value, newTarget *Object) *Object {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.assertConstructor()
-}
-
-func (o *lazyObject) deleteStr(name unistring.String, throw bool) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.deleteStr(name, throw)
-}
-
-func (o *lazyObject) proto() *Object {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.proto()
-}
-
-func (o *lazyObject) hasInstance(v Value) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.hasInstance(v)
-}
-
-func (o *lazyObject) isExtensible() bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.isExtensible()
-}
-
-func (o *lazyObject) preventExtensions(throw bool) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.preventExtensions(throw)
-}
-
-func (o *lazyObject) iterateStringKeys() iterNextFunc {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.iterateStringKeys()
-}
-
-func (o *lazyObject) iterateSymbols() iterNextFunc {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.iterateSymbols()
-}
-
-func (o *lazyObject) iterateKeys() iterNextFunc {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.iterateKeys()
-}
-
-func (o *lazyObject) export(ctx *objectExportCtx) interface{} {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.export(ctx)
-}
-
-func (o *lazyObject) exportType() reflect.Type {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.exportType()
-}
-
-func (o *lazyObject) exportToMap(m reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.exportToMap(m, typ, ctx)
-}
-
-func (o *lazyObject) exportToArrayOrSlice(s reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.exportToArrayOrSlice(s, typ, ctx)
-}
-
-func (o *lazyObject) equal(other objectImpl) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.equal(other)
-}
-
-func (o *lazyObject) stringKeys(all bool, accum []Value) []Value {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.stringKeys(all, accum)
-}
-
-func (o *lazyObject) symbols(all bool, accum []Value) []Value {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.symbols(all, accum)
-}
-
-func (o *lazyObject) keys(all bool, accum []Value) []Value {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.keys(all, accum)
-}
-
-func (o *lazyObject) setProto(proto *Object, throw bool) bool {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.setProto(proto, throw)
-}
-
-func (o *lazyObject) getPrivateEnv(typ *privateEnvType, create bool) *privateElements {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.getPrivateEnv(typ, create)
-}
-
-func (o *lazyObject) sortLen() int {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.sortLen()
-}
-
-func (o *lazyObject) sortGet(i int) Value {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.sortGet(i)
-}
-
-func (o *lazyObject) swap(i int, j int) {
-	obj := o.create(o.val)
-	o.val.self = obj
-	obj.swap(i, j)
-}

+ 469 - 0
object_template.go

@@ -0,0 +1,469 @@
+package goja
+
+import (
+	"fmt"
+	"github.com/dop251/goja/unistring"
+	"math"
+	"reflect"
+	"sort"
+)
+
+type templatePropFactory func(*Runtime) Value
+
+type objectTemplate struct {
+	propNames []unistring.String
+	props     map[unistring.String]templatePropFactory
+
+	symProps     map[*Symbol]templatePropFactory
+	symPropNames []*Symbol
+
+	protoFactory func(*Runtime) *Object
+}
+
+type templatedObject struct {
+	baseObject
+	tmpl *objectTemplate
+
+	protoMaterialised bool
+}
+
+type templatedFuncObject struct {
+	templatedObject
+
+	f         func(FunctionCall) Value
+	construct func(args []Value, newTarget *Object) *Object
+}
+
+// This type exists because Array.prototype is supposed to be an array itself and I could not find
+// a different way of implementing it without either introducing another layer of interfaces or hoisting
+// the templates to baseObject both of which would have had a negative effect on the performance.
+// The implementation is as simple as possible and is not optimised in any way, but I very much doubt anybody
+// uses Array.prototype as an actual array.
+type templatedArrayObject struct {
+	templatedObject
+}
+
+func newObjectTemplate() *objectTemplate {
+	return &objectTemplate{
+		props: make(map[unistring.String]templatePropFactory),
+	}
+}
+
+func (t *objectTemplate) putStr(name unistring.String, f templatePropFactory) {
+	t.props[name] = f
+	t.propNames = append(t.propNames, name)
+}
+
+func (t *objectTemplate) putSym(s *Symbol, f templatePropFactory) {
+	if t.symProps == nil {
+		t.symProps = make(map[*Symbol]templatePropFactory)
+	}
+	t.symProps[s] = f
+	t.symPropNames = append(t.symPropNames, s)
+}
+
+func (r *Runtime) newTemplatedObject(tmpl *objectTemplate, obj *Object) *templatedObject {
+	if obj == nil {
+		obj = &Object{runtime: r}
+	}
+	o := &templatedObject{
+		baseObject: baseObject{
+			class:      classObject,
+			val:        obj,
+			extensible: true,
+		},
+		tmpl: tmpl,
+	}
+	obj.self = o
+	o.init()
+	return o
+}
+
+func (o *templatedObject) materialiseProto() {
+	if !o.protoMaterialised {
+		if o.tmpl.protoFactory != nil {
+			o.prototype = o.tmpl.protoFactory(o.val.runtime)
+		}
+		o.protoMaterialised = true
+	}
+}
+
+func (o *templatedObject) getStr(name unistring.String, receiver Value) Value {
+	ownProp := o.getOwnPropStr(name)
+	if ownProp == nil {
+		o.materialiseProto()
+	}
+	return o.getStrWithOwnProp(ownProp, name, receiver)
+}
+
+func (o *templatedObject) getSym(s *Symbol, receiver Value) Value {
+	ownProp := o.getOwnPropSym(s)
+	if ownProp == nil {
+		o.materialiseProto()
+	}
+	return o.getWithOwnProp(ownProp, s, receiver)
+}
+
+func (o *templatedObject) getOwnPropStr(p unistring.String) Value {
+	if v, exists := o.values[p]; exists {
+		return v
+	}
+	if f := o.tmpl.props[p]; f != nil {
+		v := f(o.val.runtime)
+		o.values[p] = v
+		return v
+	}
+	return nil
+}
+
+func (o *templatedObject) materialiseSymbols() {
+	if o.symValues == nil {
+		o.symValues = newOrderedMap(nil)
+		for _, p := range o.tmpl.symPropNames {
+			o.symValues.set(p, o.tmpl.symProps[p](o.val.runtime))
+		}
+	}
+}
+
+func (o *templatedObject) getOwnPropSym(s *Symbol) Value {
+	if o.symValues == nil && o.tmpl.symProps[s] == nil {
+		return nil
+	}
+	o.materialiseSymbols()
+	return o.baseObject.getOwnPropSym(s)
+}
+
+func (o *templatedObject) materialisePropNames() {
+	if o.propNames == nil {
+		o.propNames = append(([]unistring.String)(nil), o.tmpl.propNames...)
+	}
+}
+
+func (o *templatedObject) setOwnStr(p unistring.String, v Value, throw bool) bool {
+	existing := o.getOwnPropStr(p) // materialise property (in case it's an accessor)
+	if existing == nil {
+		o.materialiseProto()
+		o.materialisePropNames()
+	}
+	return o.baseObject.setOwnStr(p, v, throw)
+}
+
+func (o *templatedObject) setOwnSym(name *Symbol, val Value, throw bool) bool {
+	o.materialiseSymbols()
+	o.materialiseProto()
+	return o.baseObject.setOwnSym(name, val, throw)
+}
+
+func (o *templatedObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
+	ownProp := o.getOwnPropStr(name)
+	if ownProp == nil {
+		o.materialiseProto()
+	}
+	return o._setForeignStr(name, ownProp, val, receiver, throw)
+}
+
+func (o *templatedObject) proto() *Object {
+	o.materialiseProto()
+	return o.prototype
+}
+
+func (o *templatedObject) setProto(proto *Object, throw bool) bool {
+	o.protoMaterialised = true
+	ret := o.baseObject.setProto(proto, throw)
+	if ret {
+		o.protoMaterialised = true
+	}
+	return ret
+}
+
+func (o *templatedObject) setForeignIdx(name valueInt, val, receiver Value, throw bool) (bool, bool) {
+	return o.setForeignStr(name.string(), val, receiver, throw)
+}
+
+func (o *templatedObject) setForeignSym(name *Symbol, val, receiver Value, throw bool) (bool, bool) {
+	o.materialiseProto()
+	o.materialiseSymbols()
+	return o.baseObject.setForeignSym(name, val, receiver, throw)
+}
+
+func (o *templatedObject) hasPropertyStr(name unistring.String) bool {
+	if o.val.self.hasOwnPropertyStr(name) {
+		return true
+	}
+	o.materialiseProto()
+	if o.prototype != nil {
+		return o.prototype.self.hasPropertyStr(name)
+	}
+	return false
+}
+
+func (o *templatedObject) hasPropertySym(s *Symbol) bool {
+	if o.hasOwnPropertySym(s) {
+		return true
+	}
+	o.materialiseProto()
+	if o.prototype != nil {
+		return o.prototype.self.hasPropertySym(s)
+	}
+	return false
+}
+
+func (o *templatedObject) hasOwnPropertyStr(name unistring.String) bool {
+	if v, exists := o.values[name]; exists {
+		return v != nil
+	}
+
+	_, exists := o.tmpl.props[name]
+	return exists
+}
+
+func (o *templatedObject) hasOwnPropertySym(s *Symbol) bool {
+	if o.symValues != nil {
+		return o.symValues.has(s)
+	}
+	_, exists := o.tmpl.symProps[s]
+	return exists
+}
+
+func (o *templatedObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
+	existingVal := o.getOwnPropStr(name)
+	if v, ok := o._defineOwnProperty(name, existingVal, descr, throw); ok {
+		o.values[name] = v
+		if existingVal == nil {
+			o.materialisePropNames()
+			names := copyNamesIfNeeded(o.propNames, 1)
+			o.propNames = append(names, name)
+		}
+		return true
+	}
+	return false
+}
+
+func (o *templatedObject) defineOwnPropertySym(s *Symbol, descr PropertyDescriptor, throw bool) bool {
+	o.materialiseSymbols()
+	return o.baseObject.defineOwnPropertySym(s, descr, throw)
+}
+
+func (o *templatedObject) deleteStr(name unistring.String, throw bool) bool {
+	if val := o.getOwnPropStr(name); val != nil {
+		if !o.checkDelete(name, val, throw) {
+			return false
+		}
+		o.materialisePropNames()
+		o._delete(name)
+		if _, exists := o.tmpl.props[name]; exists {
+			o.values[name] = nil // white hole
+		}
+	}
+	return true
+}
+
+func (o *templatedObject) deleteSym(s *Symbol, throw bool) bool {
+	o.materialiseSymbols()
+	return o.baseObject.deleteSym(s, throw)
+}
+
+func (o *templatedObject) materialiseProps() {
+	for name, f := range o.tmpl.props {
+		if _, exists := o.values[name]; !exists {
+			o.values[name] = f(o.val.runtime)
+		}
+	}
+	o.materialisePropNames()
+}
+
+func (o *templatedObject) iterateStringKeys() iterNextFunc {
+	o.materialiseProps()
+	return o.baseObject.iterateStringKeys()
+}
+
+func (o *templatedObject) iterateSymbols() iterNextFunc {
+	o.materialiseSymbols()
+	return o.baseObject.iterateSymbols()
+}
+
+func (o *templatedObject) stringKeys(all bool, keys []Value) []Value {
+	if all {
+		o.materialisePropNames()
+	} else {
+		o.materialiseProps()
+	}
+	return o.baseObject.stringKeys(all, keys)
+}
+
+func (o *templatedObject) symbols(all bool, accum []Value) []Value {
+	o.materialiseSymbols()
+	return o.baseObject.symbols(all, accum)
+}
+
+func (o *templatedObject) keys(all bool, accum []Value) []Value {
+	return o.symbols(all, o.stringKeys(all, accum))
+}
+
+func (r *Runtime) newTemplatedFuncObject(tmpl *objectTemplate, obj *Object, f func(FunctionCall) Value, ctor func([]Value, *Object) *Object) *templatedFuncObject {
+	if obj == nil {
+		obj = &Object{runtime: r}
+	}
+	o := &templatedFuncObject{
+		templatedObject: templatedObject{
+			baseObject: baseObject{
+				class:      classFunction,
+				val:        obj,
+				extensible: true,
+			},
+			tmpl: tmpl,
+		},
+		f:         f,
+		construct: ctor,
+	}
+	obj.self = o
+	o.init()
+	return o
+}
+
+func (f *templatedFuncObject) source() String {
+	return newStringValue(fmt.Sprintf("function %s() { [native code] }", nilSafe(f.getStr("name", nil)).toString()))
+}
+
+func (f *templatedFuncObject) export(*objectExportCtx) interface{} {
+	return f.f
+}
+
+func (f *templatedFuncObject) assertCallable() (func(FunctionCall) Value, bool) {
+	if f.f != nil {
+		return f.f, true
+	}
+	return nil, false
+}
+
+func (f *templatedFuncObject) vmCall(vm *vm, n int) {
+	var nf nativeFuncObject
+	nf.f = f.f
+	nf.vmCall(vm, n)
+}
+
+func (f *templatedFuncObject) assertConstructor() func(args []Value, newTarget *Object) *Object {
+	return f.construct
+}
+
+func (f *templatedFuncObject) exportType() reflect.Type {
+	return reflectTypeFunc
+}
+
+func (f *templatedFuncObject) typeOf() String {
+	return stringFunction
+}
+
+func (f *templatedFuncObject) hasInstance(v Value) bool {
+	return hasInstance(f.val, v)
+}
+
+func (r *Runtime) newTemplatedArrayObject(tmpl *objectTemplate, obj *Object) *templatedArrayObject {
+	if obj == nil {
+		obj = &Object{runtime: r}
+	}
+	o := &templatedArrayObject{
+		templatedObject: templatedObject{
+			baseObject: baseObject{
+				class:      classArray,
+				val:        obj,
+				extensible: true,
+			},
+			tmpl: tmpl,
+		},
+	}
+	obj.self = o
+	o.init()
+	return o
+}
+
+func (a *templatedArrayObject) getLenProp() *valueProperty {
+	lenProp, _ := a.getOwnPropStr("length").(*valueProperty)
+	if lenProp == nil {
+		panic(a.val.runtime.NewTypeError("missing length property"))
+	}
+	return lenProp
+}
+
+func (a *templatedArrayObject) _setOwnIdx(idx uint32) {
+	lenProp := a.getLenProp()
+	l := uint32(lenProp.value.ToInteger())
+	if idx >= l {
+		lenProp.value = intToValue(int64(idx) + 1)
+	}
+}
+
+func (a *templatedArrayObject) setLength(l uint32, throw bool) bool {
+	lenProp := a.getLenProp()
+	oldLen := uint32(lenProp.value.ToInteger())
+	if l == oldLen {
+		return true
+	}
+	if !lenProp.writable {
+		a.val.runtime.typeErrorResult(throw, "length is not writable")
+		return false
+	}
+	ret := true
+	if l < oldLen {
+		a.materialisePropNames()
+		a.fixPropOrder()
+		i := sort.Search(a.idxPropCount, func(idx int) bool {
+			return strToArrayIdx(a.propNames[idx]) >= l
+		})
+		for j := a.idxPropCount - 1; j >= i; j-- {
+			if !a.deleteStr(a.propNames[j], false) {
+				l = strToArrayIdx(a.propNames[j]) + 1
+				ret = false
+				break
+			}
+		}
+	}
+	lenProp.value = intToValue(int64(l))
+	return ret
+}
+
+func (a *templatedArrayObject) setOwnStr(name unistring.String, value Value, throw bool) bool {
+	if name == "length" {
+		return a.setLength(a.val.runtime.toLengthUint32(value), throw)
+	}
+	if !a.templatedObject.setOwnStr(name, value, throw) {
+		return false
+	}
+	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
+		a._setOwnIdx(idx)
+	}
+	return true
+}
+
+func (a *templatedArrayObject) setOwnIdx(p valueInt, v Value, throw bool) bool {
+	if !a.templatedObject.setOwnStr(p.string(), v, throw) {
+		return false
+	}
+	if idx := toIdx(p); idx != math.MaxUint32 {
+		a._setOwnIdx(idx)
+	}
+	return true
+}
+
+func (a *templatedArrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
+	if name == "length" {
+		return a.val.runtime.defineArrayLength(a.getLenProp(), descr, a.setLength, throw)
+	}
+	if !a.templatedObject.defineOwnPropertyStr(name, descr, throw) {
+		return false
+	}
+	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
+		a._setOwnIdx(idx)
+	}
+	return true
+}
+
+func (a *templatedArrayObject) defineOwnPropertyIdx(p valueInt, desc PropertyDescriptor, throw bool) bool {
+	if !a.templatedObject.defineOwnPropertyStr(p.string(), desc, throw) {
+		return false
+	}
+	if idx := toIdx(p); idx != math.MaxUint32 {
+		a._setOwnIdx(idx)
+	}
+	return true
+}

+ 66 - 148
runtime.go

@@ -58,7 +58,10 @@ type global struct {
 	Date     *Object
 	Symbol   *Object
 	Proxy    *Object
+	Reflect  *Object
 	Promise  *Object
+	Math     *Object
+	JSON     *Object
 
 	AsyncFunction *Object
 
@@ -123,21 +126,11 @@ type global struct {
 	StringIteratorPrototype       *Object
 	RegExpStringIteratorPrototype *Object
 
-	ErrorPrototype          *Object
-	AggregateErrorPrototype *Object
-	TypeErrorPrototype      *Object
-	SyntaxErrorPrototype    *Object
-	RangeErrorPrototype     *Object
-	ReferenceErrorPrototype *Object
-	EvalErrorPrototype      *Object
-	URIErrorPrototype       *Object
-
-	GoErrorPrototype *Object
+	ErrorPrototype *Object
 
 	Eval *Object
 
-	thrower         *Object
-	throwerProperty Value
+	thrower *Object
 
 	stdRegexpProto *guardedObject
 
@@ -147,6 +140,13 @@ type global struct {
 	setAdder      *Object
 	arrayValues   *Object
 	arrayToString *Object
+
+	stringproto_trimEnd   *Object
+	stringproto_trimStart *Object
+
+	parseFloat, parseInt *Object
+
+	typedArrayValues *Object
 }
 
 type Flag int
@@ -399,14 +399,10 @@ func (e *Exception) Value() Value {
 	return e.val
 }
 
-func (r *Runtime) addToGlobal(name string, value Value) {
-	r.globalObject.self._putProp(unistring.String(name), value, true, false, true)
-}
-
 func (r *Runtime) createIterProto(val *Object) objectImpl {
 	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
 
-	o._putSym(SymIterator, valueProp(r.newNativeFunc(r.returnThis, nil, "[Symbol.iterator]", nil, 0), true, false, true))
+	o._putSym(SymIterator, valueProp(r.newNativeFunc(r.returnThis, "[Symbol.iterator]", 0), true, false, true))
 	return o
 }
 
@@ -423,68 +419,17 @@ func (r *Runtime) getIteratorPrototype() *Object {
 func (r *Runtime) init() {
 	r.rand = rand.Float64
 	r.now = time.Now
-	r.global.ObjectPrototype = r.newBaseObject(nil, classObject).val
-	r.globalObject = r.NewObject()
+
+	r.global.ObjectPrototype = &Object{runtime: r}
+	r.newTemplatedObject(getObjectProtoTemplate(), r.global.ObjectPrototype)
+
+	r.globalObject = &Object{runtime: r}
+	r.newTemplatedObject(getGlobalObjectTemplate(), r.globalObject)
 
 	r.vm = &vm{
 		r: r,
 	}
 	r.vm.init()
-
-	funcProto := r.newNativeFunc(func(FunctionCall) Value {
-		return _undefined
-	}, nil, " ", nil, 0)
-	r.global.FunctionPrototype = funcProto
-	funcProtoObj := funcProto.self.(*nativeFuncObject)
-
-	r.initObject()
-	r.initFunction()
-	r.initArray()
-	r.initString()
-	r.initGlobalObject()
-	r.initNumber()
-	r.initRegExp()
-	r.initDate()
-	r.initBoolean()
-	r.initProxy()
-	r.initReflect()
-
-	r.initErrors()
-
-	r.global.Eval = r.newNativeFunc(r.builtin_eval, nil, "eval", nil, 1)
-	r.addToGlobal("eval", r.global.Eval)
-
-	r.initMath()
-	r.initJSON()
-
-	r.initTypedArrays()
-	r.initSymbol()
-	r.initWeakSet()
-	r.initWeakMap()
-	r.initMap()
-	r.initSet()
-	r.initPromise()
-
-	r.global.thrower = r.newNativeFunc(r.builtin_thrower, nil, "", nil, 0)
-	r.global.throwerProperty = &valueProperty{
-		getterFunc: r.global.thrower,
-		setterFunc: r.global.thrower,
-		accessor:   true,
-	}
-	r.object_freeze(FunctionCall{Arguments: []Value{r.global.thrower}})
-
-	funcProtoObj._put("caller", &valueProperty{
-		getterFunc:   r.global.thrower,
-		setterFunc:   r.global.thrower,
-		accessor:     true,
-		configurable: true,
-	})
-	funcProtoObj._put("arguments", &valueProperty{
-		getterFunc:   r.global.thrower,
-		setterFunc:   r.global.thrower,
-		accessor:     true,
-		configurable: true,
-	})
 }
 
 func (r *Runtime) typeErrorResult(throw bool, args ...interface{}) {
@@ -508,11 +453,11 @@ func (r *Runtime) throwReferenceError(name unistring.String) {
 }
 
 func (r *Runtime) newReferenceError(name unistring.String) Value {
-	return r.newError(r.global.ReferenceError, "%s is not defined", name)
+	return r.newError(r.getReferenceError(), "%s is not defined", name)
 }
 
 func (r *Runtime) newSyntaxError(msg string, offset int) Value {
-	return r.builtin_new(r.global.SyntaxError, []Value{newStringValue(msg)})
+	return r.builtin_new(r.getSyntaxError(), []Value{newStringValue(msg)})
 }
 
 func newBaseObjectObj(obj, proto *Object, class string) *baseObject {
@@ -574,11 +519,11 @@ func (r *Runtime) NewTypeError(args ...interface{}) *Object {
 		f, _ := args[0].(string)
 		msg = fmt.Sprintf(f, args[1:]...)
 	}
-	return r.builtin_new(r.global.TypeError, []Value{newStringValue(msg)})
+	return r.builtin_new(r.getTypeError(), []Value{newStringValue(msg)})
 }
 
 func (r *Runtime) NewGoError(err error) *Object {
-	e := r.newError(r.global.GoError, err.Error()).(*Object)
+	e := r.newError(r.getGoError(), err.Error()).(*Object)
 	e.Set("value", err)
 	return e
 }
@@ -634,7 +579,7 @@ func (r *Runtime) initBaseJsFunction(f *baseJsFuncObject, strict bool) {
 	f.val = v
 	f.extensible = true
 	f.strict = strict
-	f.prototype = r.global.FunctionPrototype
+	f.prototype = r.getFunctionPrototype()
 }
 
 func (r *Runtime) newMethod(name unistring.String, length int, strict bool) (f *methodFuncObject) {
@@ -686,27 +631,6 @@ func (r *Runtime) newAsyncArrowFunc(name unistring.String, length int, strict bo
 	return
 }
 
-func (r *Runtime) newNativeFuncObj(v *Object, call func(FunctionCall) Value, construct func(args []Value, proto *Object) *Object, name unistring.String, proto *Object, length Value) *nativeFuncObject {
-	f := &nativeFuncObject{
-		baseFuncObject: baseFuncObject{
-			baseObject: baseObject{
-				class:      classFunction,
-				val:        v,
-				extensible: true,
-				prototype:  r.global.FunctionPrototype,
-			},
-		},
-		f:         call,
-		construct: r.wrapNativeConstruct(construct, proto),
-	}
-	v.self = f
-	f.init(name, length)
-	if proto != nil {
-		f._putProp("prototype", proto, false, false, false)
-	}
-	return f
-}
-
 func (r *Runtime) newNativeConstructor(call func(ConstructorCall) *Object, name unistring.String, length int64) *Object {
 	v := &Object{runtime: r}
 
@@ -716,7 +640,7 @@ func (r *Runtime) newNativeConstructor(call func(ConstructorCall) *Object, name
 				class:      classFunction,
 				val:        v,
 				extensible: true,
-				prototype:  r.global.FunctionPrototype,
+				prototype:  r.getFunctionPrototype(),
 			},
 		},
 	}
@@ -773,7 +697,7 @@ func (r *Runtime) newNativeFuncAndConstruct(v *Object, call func(call FunctionCa
 				class:      classFunction,
 				val:        v,
 				extensible: true,
-				prototype:  r.global.FunctionPrototype,
+				prototype:  r.getFunctionPrototype(),
 			},
 		},
 		f:         call,
@@ -788,7 +712,7 @@ func (r *Runtime) newNativeFuncAndConstruct(v *Object, call func(call FunctionCa
 	return f
 }
 
-func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, construct func(args []Value, proto *Object) *Object, name unistring.String, proto *Object, length int) *Object {
+func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, name unistring.String, length int) *Object {
 	v := &Object{runtime: r}
 
 	f := &nativeFuncObject{
@@ -797,18 +721,13 @@ func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, construct func(ar
 				class:      classFunction,
 				val:        v,
 				extensible: true,
-				prototype:  r.global.FunctionPrototype,
+				prototype:  r.getFunctionPrototype(),
 			},
 		},
-		f:         call,
-		construct: r.wrapNativeConstruct(construct, proto),
+		f: call,
 	}
 	v.self = f
 	f.init(name, intToValue(int64(length)))
-	if proto != nil {
-		f._putProp("prototype", proto, false, false, false)
-		proto.self._putProp("constructor", v, true, false, true)
-	}
 	return v
 }
 
@@ -823,7 +742,7 @@ func (r *Runtime) newWrappedFunc(value reflect.Value) *Object {
 					class:      classFunction,
 					val:        v,
 					extensible: true,
-					prototype:  r.global.FunctionPrototype,
+					prototype:  r.getFunctionPrototype(),
 				},
 			},
 			f: r.wrapReflectFunc(value),
@@ -843,11 +762,11 @@ func (r *Runtime) newNativeFuncConstructObj(v *Object, construct func(args []Val
 				class:      classFunction,
 				val:        v,
 				extensible: true,
-				prototype:  r.global.FunctionPrototype,
+				prototype:  r.getFunctionPrototype(),
 			},
 		},
 		f:         r.constructToCall(construct, proto),
-		construct: r.wrapNativeConstruct(construct, proto),
+		construct: r.wrapNativeConstruct(construct, v, proto),
 	}
 
 	f.init(name, intToValue(int64(length)))
@@ -857,13 +776,11 @@ func (r *Runtime) newNativeFuncConstructObj(v *Object, construct func(args []Val
 	return f
 }
 
-func (r *Runtime) newNativeFuncConstruct(construct func(args []Value, proto *Object) *Object, name unistring.String, prototype *Object, length int64) *Object {
-	return r.newNativeFuncConstructProto(construct, name, prototype, r.global.FunctionPrototype, length)
+func (r *Runtime) newNativeFuncConstruct(v *Object, construct func(args []Value, proto *Object) *Object, name unistring.String, prototype *Object, length int64) *Object {
+	return r.newNativeFuncConstructProto(v, construct, name, prototype, r.getFunctionPrototype(), length)
 }
 
-func (r *Runtime) newNativeFuncConstructProto(construct func(args []Value, proto *Object) *Object, name unistring.String, prototype, proto *Object, length int64) *Object {
-	v := &Object{runtime: r}
-
+func (r *Runtime) newNativeFuncConstructProto(v *Object, construct func(args []Value, proto *Object) *Object, name unistring.String, prototype, proto *Object, length int64) *Object {
 	f := &nativeFuncObject{}
 	f.class = classFunction
 	f.val = v
@@ -871,11 +788,10 @@ func (r *Runtime) newNativeFuncConstructProto(construct func(args []Value, proto
 	v.self = f
 	f.prototype = proto
 	f.f = r.constructToCall(construct, prototype)
-	f.construct = r.wrapNativeConstruct(construct, prototype)
+	f.construct = r.wrapNativeConstruct(construct, v, prototype)
 	f.init(name, intToValue(length))
 	if prototype != nil {
 		f._putProp("prototype", prototype, false, false, false)
-		prototype.self._putProp("constructor", v, true, false, true)
 	}
 	return v
 }
@@ -939,7 +855,7 @@ func (r *Runtime) builtin_newBoolean(args []Value, proto *Object) *Object {
 }
 
 func (r *Runtime) builtin_new(construct *Object, args []Value) *Object {
-	return r.toConstructor(construct)(args, nil)
+	return r.toConstructor(construct)(args, construct)
 }
 
 func (r *Runtime) builtin_thrower(call FunctionCall) Value {
@@ -1013,21 +929,18 @@ func (r *Runtime) constructToCall(construct func(args []Value, proto *Object) *O
 	}
 }
 
-func (r *Runtime) wrapNativeConstruct(c func(args []Value, proto *Object) *Object, proto *Object) func(args []Value, newTarget *Object) *Object {
+func (r *Runtime) wrapNativeConstruct(c func(args []Value, proto *Object) *Object, ctorObj, defProto *Object) func(args []Value, newTarget *Object) *Object {
 	if c == nil {
 		return nil
 	}
 	return func(args []Value, newTarget *Object) *Object {
-		var p *Object
+		var proto *Object
 		if newTarget != nil {
-			if pp, ok := newTarget.self.getStr("prototype", nil).(*Object); ok {
-				p = pp
-			}
-		}
-		if p == nil {
-			p = proto
+			proto = r.getPrototypeFromCtor(newTarget, ctorObj, defProto)
+		} else {
+			proto = defProto
 		}
-		return c(args, p)
+		return c(args, proto)
 	}
 }
 
@@ -1289,7 +1202,7 @@ repeat:
 		return uint32(intVal)
 	}
 fail:
-	panic(r.newError(r.global.RangeError, "Invalid array length"))
+	panic(r.newError(r.getRangeError(), "Invalid array length"))
 }
 
 func toIntStrict(i int64) int {
@@ -1317,11 +1230,11 @@ func (r *Runtime) toIndex(v Value) int {
 	num := v.ToInteger()
 	if num >= 0 && num < maxInt {
 		if bits.UintSize == 32 && num >= math.MaxInt32 {
-			panic(r.newError(r.global.RangeError, "Index %s overflows int", v.String()))
+			panic(r.newError(r.getRangeError(), "Index %s overflows int", v.String()))
 		}
 		return int(num)
 	}
-	panic(r.newError(r.global.RangeError, "Invalid index %s", v.String()))
+	panic(r.newError(r.getRangeError(), "Invalid index %s", v.String()))
 }
 
 func (r *Runtime) toBoolean(b bool) Value {
@@ -1422,11 +1335,11 @@ func (r *Runtime) compile(name, src string, strict, inGlobal bool, evalVm *vm) (
 		switch x1 := err.(type) {
 		case *CompilerSyntaxError:
 			err = &Exception{
-				val: r.builtin_new(r.global.SyntaxError, []Value{newStringValue(x1.Error())}),
+				val: r.builtin_new(r.getSyntaxError(), []Value{newStringValue(x1.Error())}),
 			}
 		case *CompilerReferenceError:
 			err = &Exception{
-				val: r.newError(r.global.ReferenceError, x1.Message),
+				val: r.newError(r.getReferenceError(), x1.Message),
 			} // TODO proper message
 		}
 	}
@@ -1854,12 +1767,12 @@ func (r *Runtime) toValue(i interface{}, origValue reflect.Value) Value {
 		}
 	case func(FunctionCall) Value:
 		name := unistring.NewFromString(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name())
-		return r.newNativeFunc(i, nil, name, nil, 0)
+		return r.newNativeFunc(i, name, 0)
 	case func(FunctionCall, *Runtime) Value:
 		name := unistring.NewFromString(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name())
 		return r.newNativeFunc(func(call FunctionCall) Value {
 			return i(call, r)
-		}, nil, name, nil, 0)
+		}, name, 0)
 	case func(ConstructorCall) *Object:
 		name := unistring.NewFromString(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name())
 		return r.newNativeConstructor(i, name, 0)
@@ -2810,16 +2723,6 @@ func (r *Runtime) createIterResultObject(value Value, done bool) Value {
 	return o
 }
 
-func (r *Runtime) newLazyObject(create func(*Object) objectImpl) *Object {
-	val := &Object{runtime: r}
-	o := &lazyObject{
-		val:    val,
-		create: create,
-	}
-	val.self = o
-	return val
-}
-
 func (r *Runtime) getHash() *maphash.Hash {
 	if r.hash == nil {
 		r.hash = &maphash.Hash{}
@@ -2979,7 +2882,7 @@ func (r *Runtime) iterableToList(iterable Value, method func(FunctionCall) Value
 
 func (r *Runtime) putSpeciesReturnThis(o objectImpl) {
 	o._putSym(SymSpecies, &valueProperty{
-		getterFunc:   r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0),
+		getterFunc:   r.newNativeFunc(r.returnThis, "get [Symbol.species]", 0),
 		accessor:     true,
 		configurable: true,
 	})
@@ -3209,3 +3112,18 @@ func assertCallable(v Value) (func(FunctionCall) Value, bool) {
 func (r *Runtime) InstanceOf(left Value, right *Object) (res bool) {
 	return instanceOfOperator(left, right)
 }
+
+func (r *Runtime) methodProp(f func(FunctionCall) Value, name unistring.String, nArgs int) Value {
+	return valueProp(r.newNativeFunc(f, name, nArgs), true, false, true)
+}
+
+func (r *Runtime) getPrototypeFromCtor(newTarget, defCtor, defProto *Object) *Object {
+	if newTarget == defCtor {
+		return defProto
+	}
+	proto := newTarget.self.getStr("prototype", nil)
+	if obj, ok := proto.(*Object); ok {
+		return obj
+	}
+	return defProto
+}

+ 7 - 0
runtime_test.go

@@ -3028,3 +3028,10 @@ func BenchmarkAsciiStringMapGet(b *testing.B) {
 		}
 	}
 }
+
+func BenchmarkNew(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		New()
+	}
+}

+ 2 - 2
string_ascii.go

@@ -209,7 +209,7 @@ func (s asciiString) ToNumber() Value {
 }
 
 func (s asciiString) ToObject(r *Runtime) *Object {
-	return r._newString(s, r.global.StringPrototype)
+	return r._newString(s, r.getStringPrototype())
 }
 
 func (s asciiString) SameAs(other Value) bool {
@@ -258,7 +258,7 @@ func (s asciiString) StrictEquals(other Value) bool {
 }
 
 func (s asciiString) baseObject(r *Runtime) *Object {
-	ss := r.stringSingleton
+	ss := r.getStringSingleton()
 	ss.value = s
 	ss.setLength()
 	return ss.val

+ 1 - 1
string_imported.go

@@ -88,7 +88,7 @@ func (i *importedString) ToBoolean() bool {
 }
 
 func (i *importedString) ToObject(r *Runtime) *Object {
-	return r._newString(i, r.global.StringPrototype)
+	return r._newString(i, r.getStringPrototype())
 }
 
 func (i *importedString) SameAs(other Value) bool {

+ 2 - 2
string_unicode.go

@@ -402,7 +402,7 @@ func (s unicodeString) ToNumber() Value {
 }
 
 func (s unicodeString) ToObject(r *Runtime) *Object {
-	return r._newString(s, r.global.StringPrototype)
+	return r._newString(s, r.getStringPrototype())
 }
 
 func (s unicodeString) equals(other unicodeString) bool {
@@ -447,7 +447,7 @@ func (s unicodeString) StrictEquals(other Value) bool {
 }
 
 func (s unicodeString) baseObject(r *Runtime) *Object {
-	ss := r.stringSingleton
+	ss := r.getStringSingleton()
 	ss.value = s
 	ss.setLength()
 	return ss.val

+ 3 - 1
typedarrays.go

@@ -901,6 +901,8 @@ func (r *Runtime) _newTypedArrayObject(buf *arrayBufferObject, offset, length, e
 }
 
 func (r *Runtime) newUint8ArrayObject(buf *arrayBufferObject, offset, length int, proto *Object) *typedArrayObject {
+	// Note, no need to use r.getUint8Array() here or in the similar methods below, because the value is already set
+	// by the time they are called.
 	return r._newTypedArrayObject(buf, offset, length, 1, r.global.Uint8Array, (*uint8Array)(&buf.data), proto)
 }
 
@@ -939,7 +941,7 @@ func (r *Runtime) newFloat64ArrayObject(buf *arrayBufferObject, offset, length i
 func (o *dataViewObject) getIdxAndByteOrder(getIdx int, littleEndianVal Value, size int) (int, byteOrder) {
 	o.viewedArrayBuf.ensureNotDetached(true)
 	if getIdx+size > o.byteLen {
-		panic(o.val.runtime.newError(o.val.runtime.global.RangeError, "Index %d is out of bounds", getIdx))
+		panic(o.val.runtime.newError(o.val.runtime.getRangeError(), "Index %d is out of bounds", getIdx))
 	}
 	getIdx += o.byteOffset
 	var bo byteOrder

+ 7 - 7
value.go

@@ -203,7 +203,7 @@ func (i valueInt) ToBoolean() bool {
 }
 
 func (i valueInt) ToObject(r *Runtime) *Object {
-	return r.newPrimitiveObject(i, r.global.NumberPrototype, classNumber)
+	return r.newPrimitiveObject(i, r.getNumberPrototype(), classNumber)
 }
 
 func (i valueInt) ToNumber() Value {
@@ -243,7 +243,7 @@ func (i valueInt) StrictEquals(other Value) bool {
 }
 
 func (i valueInt) baseObject(r *Runtime) *Object {
-	return r.global.NumberPrototype
+	return r.getNumberPrototype()
 }
 
 func (i valueInt) Export() interface{} {
@@ -299,7 +299,7 @@ func (b valueBool) ToBoolean() bool {
 }
 
 func (b valueBool) ToObject(r *Runtime) *Object {
-	return r.newPrimitiveObject(b, r.global.BooleanPrototype, "Boolean")
+	return r.newPrimitiveObject(b, r.getBooleanPrototype(), "Boolean")
 }
 
 func (b valueBool) ToNumber() Value {
@@ -337,7 +337,7 @@ func (b valueBool) StrictEquals(other Value) bool {
 }
 
 func (b valueBool) baseObject(r *Runtime) *Object {
-	return r.global.BooleanPrototype
+	return r.getBooleanPrototype()
 }
 
 func (b valueBool) Export() interface{} {
@@ -604,7 +604,7 @@ func (f valueFloat) ToBoolean() bool {
 }
 
 func (f valueFloat) ToObject(r *Runtime) *Object {
-	return r.newPrimitiveObject(f, r.global.NumberPrototype, "Number")
+	return r.newPrimitiveObject(f, r.getNumberPrototype(), "Number")
 }
 
 func (f valueFloat) ToNumber() Value {
@@ -664,7 +664,7 @@ func (f valueFloat) StrictEquals(other Value) bool {
 }
 
 func (f valueFloat) baseObject(r *Runtime) *Object {
-	return r.global.NumberPrototype
+	return r.getNumberPrototype()
 }
 
 func (f valueFloat) Export() interface{} {
@@ -1097,7 +1097,7 @@ func (s *Symbol) ExportType() reflect.Type {
 }
 
 func (s *Symbol) baseObject(r *Runtime) *Object {
-	return r.newPrimitiveObject(s, r.global.SymbolPrototype, classObject)
+	return r.newPrimitiveObject(s, r.getSymbolPrototype(), classObject)
 }
 
 func (s *Symbol) hash(*maphash.Hash) uint64 {

+ 15 - 15
vm.go

@@ -2475,7 +2475,7 @@ func (_pushArrayItem) exec(vm *vm) {
 	if arr.length < math.MaxUint32 {
 		arr.length++
 	} else {
-		vm.throw(vm.r.newError(vm.r.global.RangeError, "Invalid array length"))
+		vm.throw(vm.r.newError(vm.r.getRangeError(), "Invalid array length"))
 		return
 	}
 	val := vm.stack[vm.sp-1]
@@ -2497,7 +2497,7 @@ func (_pushArraySpread) exec(vm *vm) {
 		if arr.length < math.MaxUint32 {
 			arr.length++
 		} else {
-			vm.throw(vm.r.newError(vm.r.global.RangeError, "Invalid array length"))
+			vm.throw(vm.r.newError(vm.r.getRangeError(), "Invalid array length"))
 			return
 		}
 		arr.values = append(arr.values, val)
@@ -2545,7 +2545,7 @@ type newRegexp struct {
 }
 
 func (n *newRegexp) exec(vm *vm) {
-	vm.push(vm.r.newRegExpp(n.pattern.clone(), n.src, vm.r.global.RegExpPrototype).val)
+	vm.push(vm.r.newRegExpp(n.pattern.clone(), n.src, vm.r.getRegExpPrototype()).val)
 	vm.pc++
 }
 
@@ -3828,7 +3828,7 @@ func (n *newAsyncArrowFunc) exec(vm *vm) {
 }
 
 func (vm *vm) alreadyDeclared(name unistring.String) Value {
-	return vm.r.newError(vm.r.global.SyntaxError, "Identifier '%s' has already been declared", name)
+	return vm.r.newError(vm.r.getSyntaxError(), "Identifier '%s' has already been declared", name)
 }
 
 func (vm *vm) checkBindVarsGlobal(names []unistring.String) {
@@ -4598,7 +4598,7 @@ func (formalArgs createArgsMapped) exec(vm *vm) {
 	}
 
 	args._putProp("callee", vm.stack[vm.sb-1], true, false, true)
-	args._putSym(SymIterator, valueProp(vm.r.global.arrayValues, true, false, true))
+	args._putSym(SymIterator, valueProp(vm.r.getArrayValues(), true, false, true))
 	vm.push(v)
 	vm.pc++
 }
@@ -4623,8 +4623,8 @@ func (formalArgs createArgsUnmapped) exec(vm *vm) {
 	}
 
 	args._putProp("length", intToValue(int64(vm.args)), true, false, true)
-	args._put("callee", vm.r.global.throwerProperty)
-	args._putSym(SymIterator, valueProp(vm.r.global.arrayValues, true, false, true))
+	args._put("callee", vm.r.newThrowerProperty(false))
+	args._putSym(SymIterator, valueProp(vm.r.getArrayValues(), true, false, true))
 	vm.push(args.val)
 	vm.pc++
 }
@@ -5057,7 +5057,7 @@ func (c *newClass) create(protoParent, ctorParent *Object, vm *vm, derived bool)
 }
 
 func (c *newClass) exec(vm *vm) {
-	proto, cls := c.create(vm.r.global.ObjectPrototype, vm.r.global.FunctionPrototype, vm, false)
+	proto, cls := c.create(vm.r.global.ObjectPrototype, vm.r.getFunctionPrototype(), vm, false)
 	sp := vm.sp
 	vm.stack.expand(sp + 1)
 	vm.stack[sp] = proto
@@ -5086,7 +5086,7 @@ func (c *newDerivedClass) exec(vm *vm) {
 			superClass = sc
 		}
 	} else {
-		superClass = vm.r.global.FunctionPrototype
+		superClass = vm.r.getFunctionPrototype()
 	}
 
 	proto, cls := c.create(protoParent, superClass, vm, true)
@@ -5103,7 +5103,7 @@ type newStaticFieldInit struct {
 }
 
 func (c *newStaticFieldInit) exec(vm *vm) {
-	f := vm.r.newClassFunc("", 0, vm.r.global.FunctionPrototype, false)
+	f := vm.r.newClassFunc("", 0, vm.r.getFunctionPrototype(), false)
 	if c.numPrivateFields > 0 || c.numPrivateMethods > 0 {
 		vm.createPrivateType(f, c.numPrivateFields, c.numPrivateMethods)
 	}
@@ -5117,7 +5117,7 @@ func (vm *vm) loadThis(v Value) {
 	if v != nil {
 		vm.push(v)
 	} else {
-		vm.throw(vm.r.newError(vm.r.global.ReferenceError, "Must call super constructor in derived class before accessing 'this'"))
+		vm.throw(vm.r.newError(vm.r.getReferenceError(), "Must call super constructor in derived class before accessing 'this'"))
 		return
 	}
 	vm.pc++
@@ -5202,7 +5202,7 @@ func (resolveThisDynamic) exec(vm *vm) {
 			}
 		}
 	}
-	panic(vm.r.newError(vm.r.global.ReferenceError, "Compiler bug: 'this' reference is not found in resolveThisDynamic"))
+	panic(vm.r.newError(vm.r.getReferenceError(), "Compiler bug: 'this' reference is not found in resolveThisDynamic"))
 }
 
 type defineComputedKey int
@@ -5464,15 +5464,15 @@ func (vm *vm) exceptionFromValue(x interface{}) *Exception {
 		}
 	case referenceError:
 		ex = &Exception{
-			val: vm.r.newError(vm.r.global.ReferenceError, string(x1)),
+			val: vm.r.newError(vm.r.getReferenceError(), string(x1)),
 		}
 	case rangeError:
 		ex = &Exception{
-			val: vm.r.newError(vm.r.global.RangeError, string(x1)),
+			val: vm.r.newError(vm.r.getRangeError(), string(x1)),
 		}
 	case syntaxError:
 		ex = &Exception{
-			val: vm.r.newError(vm.r.global.SyntaxError, string(x1)),
+			val: vm.r.newError(vm.r.getSyntaxError(), string(x1)),
 		}
 	default:
 		/*