diff --git a/package.json b/package.json
index 290165e721..6cc3b5fa11 100644
--- a/package.json
+++ b/package.json
@@ -35,7 +35,6 @@
"babel-runtime": "^6.9.2",
"global": "4.3.0",
"lodash-compat": "3.10.2",
- "object.assign": "^4.0.4",
"safe-json-parse": "4.0.0",
"tsml": "1.0.1",
"videojs-font": "2.0.0",
diff --git a/src/js/button.js b/src/js/button.js
index 5f03d2f044..37be19a935 100644
--- a/src/js/button.js
+++ b/src/js/button.js
@@ -4,7 +4,7 @@
import ClickableComponent from './clickable-component.js';
import Component from './component';
import log from './utils/log.js';
-import assign from 'object.assign';
+import {assign} from './utils/obj';
/**
* Base class for all buttons.
diff --git a/src/js/clickable-component.js b/src/js/clickable-component.js
index 9d1db5a36f..0e03856674 100644
--- a/src/js/clickable-component.js
+++ b/src/js/clickable-component.js
@@ -7,7 +7,7 @@ import * as Events from './utils/events.js';
import * as Fn from './utils/fn.js';
import log from './utils/log.js';
import document from 'global/document';
-import assign from 'object.assign';
+import {assign} from './utils/obj';
/**
* Clickable Component which is clickable or keyboard actionable,
diff --git a/src/js/extend.js b/src/js/extend.js
index aa2df7baa6..02a3a6b320 100644
--- a/src/js/extend.js
+++ b/src/js/extend.js
@@ -1,4 +1,5 @@
import log from './utils/log';
+import {isObject} from './utils/obj';
/*
* @file extend.js
@@ -51,7 +52,7 @@ const extendFn = function(superClass, subClassMethods = {}) {
let methods = {};
- if (typeof subClassMethods === 'object') {
+ if (isObject(subClassMethods)) {
if (typeof subClassMethods.init === 'function') {
log.warn('Constructor logic via init() is deprecated; please use constructor() instead.');
subClassMethods.constructor = subClassMethods.init;
diff --git a/src/js/media-error.js b/src/js/media-error.js
index 42709b2b40..b0b3236811 100644
--- a/src/js/media-error.js
+++ b/src/js/media-error.js
@@ -1,7 +1,7 @@
/**
* @file media-error.js
*/
-import assign from 'object.assign';
+import {assign, isObject} from './utils/obj';
/**
* A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
@@ -33,7 +33,7 @@ function MediaError(value) {
} else if (typeof value === 'string') {
// default code is zero, so this is a custom error
this.message = value;
- } else if (typeof value === 'object') {
+ } else if (isObject(value)) {
// We assign the `code` property manually because native `MediaError` objects
// do not expose it as an own/enumerable property of the object.
diff --git a/src/js/menu/menu-item.js b/src/js/menu/menu-item.js
index eb322ae9fd..04c912ba85 100644
--- a/src/js/menu/menu-item.js
+++ b/src/js/menu/menu-item.js
@@ -3,7 +3,7 @@
*/
import ClickableComponent from '../clickable-component.js';
import Component from '../component.js';
-import assign from 'object.assign';
+import {assign} from '../utils/obj';
/**
* The component for a menu item. `
`
diff --git a/src/js/player.js b/src/js/player.js
index f85d263fad..b573331c43 100644
--- a/src/js/player.js
+++ b/src/js/player.js
@@ -19,7 +19,7 @@ import * as stylesheet from './utils/stylesheet.js';
import FullscreenApi from './fullscreen-api.js';
import MediaError from './media-error.js';
import safeParseTuple from 'safe-json-parse/tuple';
-import assign from 'object.assign';
+import {assign} from './utils/obj';
import mergeOptions from './utils/merge-options.js';
import textTrackConverter from './tracks/text-track-list-converter.js';
import ModalDialog from './modal-dialog';
diff --git a/src/js/slider/slider.js b/src/js/slider/slider.js
index 0ea91b3ca1..11f7b4eaf6 100644
--- a/src/js/slider/slider.js
+++ b/src/js/slider/slider.js
@@ -3,7 +3,7 @@
*/
import Component from '../component.js';
import * as Dom from '../utils/dom.js';
-import assign from 'object.assign';
+import {assign} from '../utils/obj';
/**
* The base functionality for a slider. Can be vertical or horizontal.
diff --git a/src/js/tech/flash.js b/src/js/tech/flash.js
index c3274e8bfb..25e2c7c0f1 100644
--- a/src/js/tech/flash.js
+++ b/src/js/tech/flash.js
@@ -12,7 +12,7 @@ import { createTimeRange } from '../utils/time-ranges.js';
import FlashRtmpDecorator from './flash-rtmp';
import Component from '../component';
import window from 'global/window';
-import assign from 'object.assign';
+import {assign} from '../utils/obj';
const navigator = window.navigator;
diff --git a/src/js/tech/html5.js b/src/js/tech/html5.js
index e5330f80db..e684d5f046 100644
--- a/src/js/tech/html5.js
+++ b/src/js/tech/html5.js
@@ -11,7 +11,7 @@ import tsml from 'tsml';
import * as browser from '../utils/browser.js';
import document from 'global/document';
import window from 'global/window';
-import assign from 'object.assign';
+import {assign} from '../utils/obj';
import mergeOptions from '../utils/merge-options.js';
import toTitleCase from '../utils/to-title-case.js';
diff --git a/src/js/utils/dom.js b/src/js/utils/dom.js
index 125a0308f3..491910b243 100644
--- a/src/js/utils/dom.js
+++ b/src/js/utils/dom.js
@@ -7,6 +7,7 @@ import window from 'global/window';
import * as Guid from './guid.js';
import log from './log.js';
import tsml from 'tsml';
+import {isObject} from './obj';
/**
* Detect if a value is a string with any non-whitespace characters.
@@ -65,7 +66,7 @@ function classRegExp(className) {
* - False otherwise
*/
export function isEl(value) {
- return !!value && typeof value === 'object' && value.nodeType === 1;
+ return isObject(value) && value.nodeType === 1;
}
/**
@@ -674,7 +675,7 @@ export function getPointerPosition(el, event) {
* - False otherwise
*/
export function isTextNode(value) {
- return !!value && typeof value === 'object' && value.nodeType === 3;
+ return isObject(value) && value.nodeType === 3;
}
/**
diff --git a/src/js/utils/log.js b/src/js/utils/log.js
index f9c8972ea5..4667439c20 100644
--- a/src/js/utils/log.js
+++ b/src/js/utils/log.js
@@ -4,6 +4,7 @@
*/
import window from 'global/window';
import {IE_VERSION} from './browser';
+import {isObject} from './obj';
let log;
@@ -51,7 +52,7 @@ export const logByType = (type, args, stringify = !!IE_VERSION && IE_VERSION < 1
// objects and arrays for those less-capable browsers.
if (stringify) {
args = args.map(a => {
- if (a && typeof a === 'object' || Array.isArray(a)) {
+ if (isObject(a) || Array.isArray(a)) {
try {
return JSON.stringify(a);
} catch (x) {
diff --git a/src/js/utils/merge-options.js b/src/js/utils/merge-options.js
index b5b2d19128..8ecd69a6cb 100644
--- a/src/js/utils/merge-options.js
+++ b/src/js/utils/merge-options.js
@@ -3,20 +3,7 @@
* @module merge-options
*/
import merge from 'lodash-compat/object/merge';
-
-/**
- * Check if an object direct descentant of Object and
- * not of Array or RegExp.
- *
- * @param {Mixed} obj
- * The object to check
- */
-function isPlain(obj) {
- return !!obj &&
- typeof obj === 'object' &&
- obj.toString() === '[object Object]' &&
- obj.constructor === Object;
-}
+import {isPlain} from './obj';
/**
* Merge customizer. video.js simply overwrites non-simple objects
diff --git a/src/js/utils/obj.js b/src/js/utils/obj.js
index 3b17010168..a6896e2712 100644
--- a/src/js/utils/obj.js
+++ b/src/js/utils/obj.js
@@ -28,6 +28,7 @@
* @return {Mixed}
* The new accumulated value.
*/
+const toString = Object.prototype.toString;
/**
* Array-like iteration for objects.
@@ -63,3 +64,55 @@ export function reduce(object, fn, initial = 0) {
return Object.keys(object).reduce(
(accum, key) => fn(accum, object[key], key), initial);
}
+
+/**
+ * Object.assign-style object shallow merge/extend.
+ *
+ * @param {Object} target
+ * @param {Object} ...sources
+ * @return {Object}
+ */
+export function assign(target, ...sources) {
+ if (Object.assign) {
+ return Object.assign(target, ...sources);
+ }
+
+ sources.forEach(source => {
+ if (!source) {
+ return;
+ }
+
+ each(source, (value, key) => {
+ target[key] = value;
+ });
+ });
+
+ return target;
+}
+
+/**
+ * Returns whether a value is an object of any kind - including DOM nodes,
+ * arrays, regular expressions, etc. Not functions, though.
+ *
+ * This avoids the gotcha where using `typeof` on a `null` value
+ * results in `'object'`.
+ *
+ * @param {Object} value
+ * @return {Boolean}
+ */
+export function isObject(value) {
+ return !!value && typeof value === 'object';
+}
+
+/**
+ * Returns whether an object appears to be a "plain" object - that is, a
+ * direct instance of `Object`.
+ *
+ * @param {Object} value
+ * @return {Boolean}
+ */
+export function isPlain(value) {
+ return isObject(value) &&
+ toString.call(value) === '[object Object]' &&
+ value.constructor === Object;
+}
diff --git a/src/js/video.js b/src/js/video.js
index b25bec4be4..48ae816dfd 100644
--- a/src/js/video.js
+++ b/src/js/video.js
@@ -26,6 +26,7 @@ import log from './utils/log.js';
import * as Dom from './utils/dom.js';
import * as browser from './utils/browser.js';
import * as Url from './utils/url.js';
+import {isObject} from './utils/obj';
import computedStyle from './utils/computed-style.js';
import extendFn from './extend.js';
import merge from 'lodash-compat/object/merge';
@@ -117,7 +118,7 @@ function videojs(id, options, ready) {
videojs.hooks('beforesetup').forEach(function(hookFunction) {
const opts = hookFunction(tag, mergeOptions(options));
- if (!opts || typeof opts !== 'object' || Array.isArray(opts)) {
+ if (!isObject(opts) || Array.isArray(opts)) {
videojs.log.error('please return an object in beforesetup hooks');
return;
}
diff --git a/test/unit/utils/obj.test.js b/test/unit/utils/obj.test.js
index 5af4ae09fa..dcca4cd001 100644
--- a/test/unit/utils/obj.test.js
+++ b/test/unit/utils/obj.test.js
@@ -4,6 +4,23 @@ import * as Obj from '../../../src/js/utils/obj';
QUnit.module('utils/obj');
+class Foo {
+ constructor() {}
+ toString() {
+ return 'I am a Foo!';
+ }
+}
+
+const passFail = (assert, fn, descriptor, passes, failures) => {
+ Object.keys(passes).forEach(key => {
+ assert.ok(fn(passes[key]), `${key} IS ${descriptor}`);
+ });
+
+ Object.keys(failures).forEach(key => {
+ assert.notOk(fn(failures[key]), `${key} IS NOT ${descriptor}`);
+ });
+};
+
QUnit.test('each', function(assert) {
const spy = sinon.spy();
@@ -56,3 +73,35 @@ QUnit.test('reduce', function(assert) {
assert.strictEqual(third.c, -3);
assert.strictEqual(third.d, -4);
});
+
+QUnit.test('isObject', function(assert) {
+ passFail(assert, Obj.isObject, 'an object', {
+ 'plain object': {},
+ 'constructed object': new Foo(),
+ 'array': [],
+ 'regex': new RegExp('.'),
+ 'date': new Date()
+ }, {
+ null: null,
+ function() {},
+ boolean: true,
+ number: 1,
+ string: 'xyz'
+ });
+});
+
+QUnit.test('isPlain', function(assert) {
+ passFail(assert, Obj.isPlain, 'a plain object', {
+ 'plain object': {}
+ }, {
+ 'constructed object': new Foo(),
+ 'null': null,
+ 'array': [],
+ 'function'() {},
+ 'regex': new RegExp('.'),
+ 'date': new Date(),
+ 'boolean': true,
+ 'number': 1,
+ 'string': 'xyz'
+ });
+});