导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

冷门技能

创建一个对象的方式有哪几种?

const obj = new Object()
obj.name = 'Lance'
const obj = { name: 'Lance' }
function createObj(name) {
  const obj = new Object()
  obj.name = name
  return obj
}
const obj = createObj('Lance')
function Person(name) {
  this.name = name
}
const person = new Person('Lance')

⭐ Javascript中,有一个函数,执行时对象查找时,永远不会去查找原型,这个函数是?

hasOwnProperty

hasOwnProperty返回一个布尔值,指出一个对象是否具有指定名称的属性。此方法不会检查该对象的原型链中是否具有该属性;该属性必须是对象本身的一个成员。

使用方法: object.hasOwnProperty(proName) 其中参数object是必选项。一个对象的实例。 proName是必选项。一个属性名称的字符串值。 如果 object 具有指定名称的属性,那么JavaScript中hasOwnProperty函数方法返回 true,反之则返回 false。

⭐ instanceof 实现原理

只要右边变量的 prototype 在左边变量的原型链上即可。

思路

function myInstanceof(obj, Func) {
  let __proto__ = obj.__proto__; // 拿到对象原型
  let prototype = Func.prototype; // 拿到构造函数原型
  while (true) {
    if (__proto__ === null) { // 退出条件: 找到了 Object的基类 Object.prototype.__proto__
      return false;
    }
    if (__proto__ === prototype) { // 在对象的原型链上找到了构造函数
      return true;
    }
    __proto__ = __proto__.__proto__; // 沿着原型链 __proto__ 一层一层向上查找
  }
}

优化版 (处理兼容问题)

Object.getPrototypeOf ( ):用来获取某个实例对象的原型(内部[[prototype]]属性的值,包含proto属性)

function myInstanceof(obj, Func) {
  let __proto__ = Object.getPrototypeOf(obj);
  let prototype = Func.prototype;

  while (true) {
    if (__proto__ === null) {
      return false;
    }
    if (__proto__ === prototype) {
      return true;
    }
    __proto__ = Object.getPrototypeOf(__proto__);
  }
}

new 操作符为什么能创建一个实例对象?

分析一下 new 的整个过程:

⭐ 实现一个 new

function _new(fn, ...args) {
  const obj = Object.create(fn.prototype)
  const ret = fn.apply(obj, args)
  return ret instanceof Object ? ret : obj
}
// 测试
function Person(name, age) {
  this.name = name
  this.age = age
  this.sayHi = function() {}
}
Person.prototype.run = function() {}
console.log(_new(Person, 'Lance', 19))
console.log(new Person('Jerry', 20))

如何判断当前脚本运行在浏览器还是node环境中?

this === window ? 'browser' : 'node';
通过判断Global对象是否为window,如果不为window,当前脚本没有运行在浏览器中

⭐ 写一下深拷贝

// 不解决循环引用
function deepClone(obj) {
    // 处理 null 或者 undefined
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }

    // 处理数组
    if (Array.isArray(obj)) {
        var arrCopy = [];
        for (var i = 0; i < obj.length; i++) {
            arrCopy[i] = deepClone(obj[i]);
        }
        return arrCopy;
    }

    // 处理普通对象
    var objCopy = {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            objCopy[key] = deepClone(obj[key]);
        }
    }
    return objCopy;
}
var test1 = {};
var test2 = {};
test1.test2 = test2;
test2.test1 = test1;

function deepClone(source, hashMap = new WeakMap()) {
  if (source == undefined || typeof source !== 'object') {
  	return source;
  }
  if (source instanceof Date) {
  	return new Date(source);
  }
  if (source instanceof RegExp) {
  	return new RepExp(source);
  }

  const hasItem = hashMap.get(source);
  if (hasItem) return hasItem;

  const target = new source.constructor();
  hashMap.set(source, target);

  for (const key in source) {
  	if (Object.prototype.hasOwnProperty.call(source, key)) {
    	target[key] = deepClone(source[key], hashMap);
    }
  }
  return target;
}

console.log(deepClone(test2));

Object.defineProperty(target, key, options),options可传什么参数?

⭐ 实现一个单例

// ES5
function People(name, age) {
  this.name = name;
  this.age = age;
}

let Singleton = function(Fn) {
  let _instance = null;
  return function(...args) {
    if (_instance) return _instance;
    return _instance = new Fn(...args);
  }
}

let peopleSingleton = Singleton(People);

let ming = new peopleSingleton('ming', 18);
let fang = new peopleSingleton('fang', 18);
console.log(ming, fang);
// ES6

class People {
  constructor(name, age) {
    if (typeof People._instance === 'object') return People._instance;
    this.name = name;
    this.age = age;
    return People._instance = this;
  }
}

let ming = new People('ming', 20);
let fang = new People('fang', 19);
console.log(ming, fang);

⭐ 判断JS对象是否存在循环引用

const obj = {
 a: 1,
 b: 2,
}

obj.c = obj

// isHasCircle函数, 存在环输出true,不存在的话输出false
isHasCircle(obj)
function isHasCircle(obj) {
    let hasCircle = false; // 是否循环
    const map = new Map(); // map存对象

    function loop(obj) {
        const keys = Object.keys(obj)

        keys.forEach(key => {
            const value = obj[key]
            if (typeof value == 'object' && value !== null) {
                if (map.has(value)) { // 如果有,判定有循环,return退出迭代
                    hasCircle = true
                    return
                } else { // 如果没有,添加元素进map,继续递归元素
                    map.set(value, value)
                    loop(value)
                }
            }
        })

    }

    loop(obj)

    return hasCircle
}

⭐ JSON 序列化的缺点

JSON.stringify()将值转换为相应的JSON格式:

JavaScript 继承方式有哪些?

Professor.prototype = {
	name: 'Mr.Zhang',
  tSkill: 'Java'
}
function Professor() {}
var professor = new Professor(); // 让子类 prototype 等于 父类的实例化对象

Teacher.prototype = professor;
function Teacher() {
	this.name = 'Mr.Wang';
  this.mSkill = 'JS';
}
var teacher = new Teacher();

Student.prototype = teacher;
function Student() {
	this.name = 'Mr.Li';
  this.pSkill = 'HTML';
}
var student = new Student();
console.log(student);
Teacher.prototype.wife = 'Ms.Liu';
function Teacher(name, mSkill) {
	this.name = name;
  this.mSkill = mSkill;
}
function Student(name, mSkill, age, major) {
	Teacher.apply(this, [name, mSkill]);
  this.age = age;
  this.major = major;
}
var student = new Student('Mr.B', 'JS', 18, 'Computer');
console.log(student);
function Teacher() {
	this.name = 'Mr.Wang';
  this.mSkill = 'JS';
}
Teacher.prototype = {
	pSkill: 'JQ'
}
var teacher = new Teacher();

Student.prototype = Teacher.prototype;
function Student() {
	this.name = 'Mr.Li';
}

var student = new Student();
Student.prototype.name = 'student';
console.log(student);
console.log(Teacher.prototype);
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayHi = function() {
        console.log("sayHi");
    }
}

Person.prototype.eat = function() {
    console.log("eat");
}

function Student(name, age, score) {
    Person.call(this, name, age);
    this.score = score;
    this.study = function() {
        console.log("study");
    }
}

var inherit = (function () {
	function Buffer() {}
	return function (Target, Origin) {
    Buffer.prototype = Origin.prototype;
    Target.prototype = new Buffer();
    // 还原构造器
    Target.prototype.constructor = Target;
    // 指定继承源
    Target.prototype.super_class = Origin;
  }
})();

inherit(Student, Person);
var stu1 = new Student("Lance", 19, 100);
console.dir(stu1);

预测对象原型

var obj1 = Object.create(null);
obj1.a = 1;

var obj2 = Object.create(obj1);
obj2.b = 2;

console.log(obj2.__proto__); // ?

原型存在在原型链上,但现在链不存在了(就是最终到达 Object.prototype 的这个链儿 在 obj1 这断掉了)

⭐ (a == 1 && a == 2 && a == 3) 有可能是 true 吗?

// 第一种
const a = {
  i: 0,
  toString() {
    return ++this.i
  }
}
console.log(a == 1 && a == 2 && a == 3) // true

// 第二种
const a = [1, 2, 3]
a.join = a.shift
console.log(a == 1 && a == 2 && a == 3) // true

// 第三种
let i = 0
Object.defineProperty(global, 'a', {
  get() {
    return ++i
  }
})
console.log(a == 1 && a == 2 && a == 3) // true

⭐ _ + _ + _ + ...

  1. _ + _ 为 'ab'
  2. _ + _ + _ 为 'abc'
  3. ...
  4. 一直到 'a...z' 26个字母为止
Object.defineProperty(window, '_', {
  get() {
    this.curCharCode = this.curCharCode || 'a'.charCodeAt(0); // 找到起始ascii位
    this.curChar = String.fromCharCode(this.curCharCode); // 赋值给要返回的值
    if (this.curCharCode >= 'a'.charCodeAt(0) + 26) return ''; // 一旦大于等于26 就返回空字符串
    this.curCharCode++; // 起始位自增
    return this.curChar;
  }
})

console.log(_ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _);

⭐ 要求设计 LazyMan 类,实现以下功能

LazyMan('Tony');
// Hi I am Tony
LazyMan('Tony').sleep(10).eat('lunch');
// Hi I am Tony
// 等待了10秒...
// I am eating lunch
LazyMan('Tony').eat('lunch').sleep(10).eat('dinner');
// Hi I am Tony
// I am eating lunch
// 等待了10秒...
// I am eating diner
LazyMan('Tony').eat('lunch').eat('dinner').sleepFirst(5).sleep(10).eat('junk food');
// Hi I am Tony
// 等待了5秒...
// I am eating lunch
// I am eating dinner
// 等待了10秒...
// I am eating junk food

答案:

class LazyManClass {
    constructor (name) {
        this.name = name
        this.task = []
        console.log('Hi I am', name)
        **setTimeout(() => {
            this.next()
        }, 0)**
    }
    eat (str) {
        this.task.push(() => {
            console.log('I am eating', str)
            this.next()
        })
        return this
    }
    sleep (n) {
        this.task.push(() => {
            setTimeout(() => {
                console.log('等待了' + n + 's')
                this.next()
            }, n * 1000)
        })
        return this
    }
    sleepFirst (n) {
        this.task.unshift(() => {
            setTimeout(() => {
                console.log('等待了' + n + 's')
                this.next()
            }, n * 1000)
        })
        return this
    }
    next () {
        let fn = this.task.shift()
        fn && fn()
    }
};
let LazyMan = function (name) {
    return new LazyManClass(name)
};

解析:

这道题的关键点有如下几个:

题目来源:Daily-Interview-Question 第56题

⭐ 对象扁平化

const obj = {
    a: {
        b: 1,
        c: 2,
        d: { e: 5 },
    },
    b: [1, 3, { a: 2, b: 3 }],
    c: 3,
    d: []
}
/*
    {
        'a.b': 1,
        'a.c': 2,
        'a.d.e': 5,
        'b[0]': 1,
        'b[1]': 3,
        'b[2].a': 2,
        'b[2].b': 3
         c: 3,
         d: []
    }
*/

function flatObj(obj) {
  let ret = {};
  function walk(key, value) {
    if (typeof value === 'object' && value !== null) { // 是对象时
      if (Array.isArray(value)) { // 是数组时
        value.forEach((item, idx) => {
          walk(`${key}[${idx}]`, item);
        });
        if (value.length === 0) ret[key] = [];
      } else { // 是对象时
        Object.entries(value).forEach(([k, value]) => {
          walk(`${key}.${k}`, value);
        });
        if (Object.entries(value).length === 0) ret[key] = {};
      }
    } else { // 不是对象时
      ret[key] = value;
    }
  }
  // 第一次遍历 walk
  Object.entries(obj).forEach(([key, value]) => walk(key, value));
  return ret;
}

console.log(flatObj(obj));