JavaScript基础

JS中除了使用new关键字还有什么方法可以创建对象?

可以通过Object.create(proto, [, propertiesObject])实现。详见:Object.create()

JS原型与原型链

普通对象与函数对象

JS中,对象分普通对象函数对象,Object、Function是JS自带的函数对象。凡是通过new Function()创建的对象都是函数对象,其他的都是普通对象。

javascript
typeof Object // "function", 函数对象
typeof Function // "function", 函数对象

function f1 () {}
var f2 = function () {}
var f3 = new Function('str', 'console.log(str)')

var o1 = new f1()
var o2 = {}
var o3 = new Object()

typeof f1 // "function", 函数对象
typeof f2 // "function", 函数对象
typeof f3 // "function", 函数对象

typeof o1 // "object", 普通对象
typeof o2 // "object", normal object
typeof o3 // "object", normal object
typeof Object // "function", 函数对象
typeof Function // "function", 函数对象

function f1 () {}
var f2 = function () {}
var f3 = new Function('str', 'console.log(str)')

var o1 = new f1()
var o2 = {}
var o3 = new Object()

typeof f1 // "function", 函数对象
typeof f2 // "function", 函数对象
typeof f3 // "function", 函数对象

typeof o1 // "object", 普通对象
typeof o2 // "object", normal object
typeof o3 // "object", normal object

原型对象

每当定义一个对象(函数)时,对象中都会包含一些预定义的属性。其中,函数对象会有一个prototype属性,就是我们所说的原型对象(普通对象没有prototype,但有_proto_属性;函数对象同时含有prototype和__proto__属性)。

原型对象其实就是普通对象(Function.prototype除外,它是函数对象,单同时它又没有prototype属性)。

javascript
function f1 () {}
console.log(f1.prototype) // Object{} with two properties constructor and __proto__
typeof f1.prototype // "object"

typeof Object.proto

// 特例,没必要记住,平常根本用不到
typeof Function.prototype // "function"
typeof Function.prototype.prototype // "undefined"
typeof Object.prototype // "object"
function f1 () {}
console.log(f1.prototype) // Object{} with two properties constructor and __proto__
typeof f1.prototype // "object"

typeof Object.proto

// 特例,没必要记住,平常根本用不到
typeof Function.prototype // "function"
typeof Function.prototype.prototype // "undefined"
typeof Object.prototype // "object"

原型对象的主要作用是用于继承:

javascript
var Person = function (name) {
  this.name = name
}

Person.prototype.getName = function () {
  return this.name
}

var yakima = new Person('yakima')
yakima.getName() // "yakima"
var Person = function (name) {
  this.name = name
}

Person.prototype.getName = function () {
  return this.name
}

var yakima = new Person('yakima')
yakima.getName() // "yakima"

原型链

上面提到原型对象的主要作用是用于继承,其具体的实现就是通过原型链实现的。创建对象(不论是普通对象还是函数对象)时,都有一个叫做__proto__的内置属性,用于指向创建它的函数对象的原型对象(即函数对象的prototype属性)

javascript
yakima.__proto__ === Person.prototype // true,对象的内置__proto__对象指向创建该对象的函数对象的prototype

Person.prototype.__proto__ === Object.prototype // true

// 继续,Object.prototype对象也有__proto__属性,但它比较特殊,为null
Object.prototype.__proto__ === null // true

typeof null // "object"
yakima.__proto__ === Person.prototype // true,对象的内置__proto__对象指向创建该对象的函数对象的prototype

Person.prototype.__proto__ === Object.prototype // true

// 继续,Object.prototype对象也有__proto__属性,但它比较特殊,为null
Object.prototype.__proto__ === null // true

typeof null // "object"

这个由__proto__串起来的直到Object.prototype.proto ==> null对象的链称为原型链。

  1. yakima的__proto__属性指向Person.prototype对象;
  2. Person.prototype对象的__proto__属性指向Object.prototype对象;
  3. Object.prototype对象的__proto__属性指向null对象

说明(下面这几种看完忘掉就可以了^_^)

Object是函数对象,是通过new Function ()创建的,所以Object.__proto__指向Function.prototype

javascript
Object.__proto__ === Function.prototype // true
Object.__proto__ === Function.prototype // true

Function是函数对象,是通过new Function()创建的,所以Function.__proto__指向Function.prototype。本类创建本类。。。唐僧也说过类似的话的,人是人他妈生的,妖是妖他妈生的。

javascript
Function.__proto__ === Function.prototype // true
Function.__proto__ === Function.prototype // true

另外:

javascript
Function.prototype.__proto__ === Object.prototype // true
Function.prototype.__proto__ === Object.prototype // true

constructor

原型对象中都有个constructor属性,用来引用它的函数对象。这是一种循环引用。

javascript
Person.prototype.constructor === Person // true
Function.prototype.constructor === Function // true
Object.prototype.constructor === Object // true
Person.prototype.constructor === Person // true
Function.prototype.constructor === Function // true
Object.prototype.constructor === Object // true

综合理解

原型和原型链是JS实现继承的一种模型。

javascript
var Animal = function () {}
var Dog = function () {}

Animal.price = 2000
Dog.prototype = Animal

var tidy = new Dog()

console.log(Dog.price) // undefined
console.log(tidy.price) // 200
var Animal = function () {}
var Dog = function () {}

Animal.price = 2000
Dog.prototype = Animal

var tidy = new Dog()

console.log(Dog.price) // undefined
console.log(tidy.price) // 200

对上例的分析:

  • Dog自身没有price属性,沿着__proto__属性往上找,因为Dog赋值时的Dog = function () {}其实使用new Function ()创建的Dog,所以,Dog.proto ==> Function.prototype, Function.prototype.proto ===> Object.prototype,而Object.prototype.proto ==> null。很明显,整条链上都找不到price属性,只能返回undefined了;
  • tidy自身没有price属性,沿着__proto__属性往上找,因为tidy对象是Dog函数对象的实例,tidy.proto ==> Dog.prototype ==> Animal,从而tidy.price获取到了Animal.price的值。

判断JS全局变量是否存在

javascript
if (typeof localStorage !== 'undefined') {
  // 此时访问localStorage不会出现引用错误
}
if (typeof localStorage !== 'undefined') {
  // 此时访问localStorage不会出现引用错误
}

或者

javascript
if ('localStorage' in self) { // 浏览器端全局处window/this/self三者彼此全等
  // 此时访问 localStorage 绝对不会出现引用错误
}
if ('localStorage' in self) { // 浏览器端全局处window/this/self三者彼此全等
  // 此时访问 localStorage 绝对不会出现引用错误
}

注意二者的区别:

javascript
var a // 或 var a = undefined
'a' in self // true
typeof a // 'undefined'
var a // 或 var a = undefined
'a' in self // true
typeof a // 'undefined'
  • var a = undefined或者var a相当于是给window对象添加了a属性,但是未赋值,即window.a === undefined为true
  • typeof a就是返回其变量类型,未赋值或者声明类型为undefined的变量,其类型就是undefined

判断对象是否相等 (only for enumerable properties)

javascript
function isObjectEqual (obj1, obj2) {
  if (typeof obj1 !== 'object' || typeof obj2 !== 'object') {
  return obj1 === obj2
  }
  // if refer to the same location
  if (obj1 === obj2) {
  return true
  }

  var keys1 = Object.keys(obj1)
  var keys2 = Object.keys(obj2)

  if (keys1.length !== keys2.length) {
  return false
  }

  if (keys1.length === 0 && keys2.length === 0) {
  return true
  }

  for (var i = 0, len = keys1.length; i < len; i++) {
  if (!isObjectEqual(obj1[keys1[i]], obj2[keys2[i]])) {
    return false
  }
  }
  return true
}
function isObjectEqual (obj1, obj2) {
  if (typeof obj1 !== 'object' || typeof obj2 !== 'object') {
  return obj1 === obj2
  }
  // if refer to the same location
  if (obj1 === obj2) {
  return true
  }

  var keys1 = Object.keys(obj1)
  var keys2 = Object.keys(obj2)

  if (keys1.length !== keys2.length) {
  return false
  }

  if (keys1.length === 0 && keys2.length === 0) {
  return true
  }

  for (var i = 0, len = keys1.length; i < len; i++) {
  if (!isObjectEqual(obj1[keys1[i]], obj2[keys2[i]])) {
    return false
  }
  }
  return true
}

说明生成器函数的写法,以及yield语句的作用

ES6引入了Generator函数,示例如下:

javascript
function* hello (name) {
  yield `hello ${name}!`
  yield 'I am glad to meet you!'
  if (0.6 > 0.5) {
  yield `It is a good day!`
  }
  yield 'See you later!'
}

// Generator函数执行后会返回一个迭代器,通过调用next方法依次yeild相应的值
var iterator = hello('Yakima')

iterator.next() // 返回{value: "hello Yakima!", done: false}
iterator.next() // 返回{value: "I am glad to meet you!", done: false}
iterator.next() // 返回{value: "It is a good day!", done: false}
iterator.next() // 返回{value: "See you later!", done: false}
iterator.next() // 返回{value: undefined, done: true}
iterator.next() // 返回{value: undefined, done: true}
function* hello (name) {
  yield `hello ${name}!`
  yield 'I am glad to meet you!'
  if (0.6 > 0.5) {
  yield `It is a good day!`
  }
  yield 'See you later!'
}

// Generator函数执行后会返回一个迭代器,通过调用next方法依次yeild相应的值
var iterator = hello('Yakima')

iterator.next() // 返回{value: "hello Yakima!", done: false}
iterator.next() // 返回{value: "I am glad to meet you!", done: false}
iterator.next() // 返回{value: "It is a good day!", done: false}
iterator.next() // 返回{value: "See you later!", done: false}
iterator.next() // 返回{value: undefined, done: true}
iterator.next() // 返回{value: undefined, done: true}

Generator函数与常见的函数的差异:

  • 通常的函数以function开始,而Generator函数以function*开始;
  • 在Generator函数内部,yield是一个关键字,和return有点像。不同点在于,所有函数(包括Generator函数)都只能返回一次,而在Generator函数中可以yield任意次。yield表达式暂停了Generator函数的执行,然后可以从暂停的地方恢复执行。

常见的函数不能暂停执行,而Generator函数可以,这是两者最大的区别。

ES5中的闭包、作用域、变量/函数提升(hoisting)

写出下面代码的执行结果:

javascript
// 当前位于全局作用域下
function testObject () {
  alert(this)
}

testObject()
// 当前位于全局作用域下
function testObject () {
  alert(this)
}

testObject()

上题的答案:在chrome中会弹出[object Window]

javascript
var msg = 'String A'
function test () {
  alert(msg)
  var msg = 'String A'
  alert(msg)
}
test()
var msg = 'String A'
function test () {
  alert(msg)
  var msg = 'String A'
  alert(msg)
}
test()

上题的分析与答案:在函数内部声明的变量在函数内部会覆盖掉全局同名变量。在JS预解析时,定义变量的行为会在变量作用域内的顶部实现(hoisting),但是变量的赋值行为并不会提前,所以上述代码等价于如下代码,所以第一次alert弹出的是undefined,第二次alert弹出的是“String A”。

javascript
var msg = 'String A'
function test () {
  var msg
  alert(msg)
  msg = 'String A'
  alert(msg)
}
var msg = 'String A'
function test () {
  var msg
  alert(msg)
  msg = 'String A'
  alert(msg)
}

写出下面代码a、b、c三行的输出分别是什么?

javascript
// mark A
function fun (n, o) {
  console.log(o)
  return {
  // mark B
  fun: function (m) {
    // mark C
    return fun(m, n)
  }
  }
}
var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3)
var b = fun(0).fun(1).fun(2).fun(3)
var c = fun(0).fun(1); c.fun(2); c.fun(3)

// 答案:
// undefined, 0, 0, 0
// undefined, 0, 1, 2
// undefined, 0, 1, 1
// mark A
function fun (n, o) {
  console.log(o)
  return {
  // mark B
  fun: function (m) {
    // mark C
    return fun(m, n)
  }
  }
}
var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3)
var b = fun(0).fun(1).fun(2).fun(3)
var c = fun(0).fun(1); c.fun(2); c.fun(3)

// 答案:
// undefined, 0, 0, 0
// undefined, 0, 1, 2
// undefined, 0, 1, 1

首先,可以分析得到的结论:标记A下面的fun函数和标记C下面return的fun是同一个函数,标记B下面的fun属性对应的函数不同于标记A和标记C下方的函数。下文为了行文方便,将各个标记处下方的函数方便叫做A、B、C函数。

a行的分析:

  • a = fun(0):即a = fun (0) {console.log(undefined) return { // ... } },故输出undefined;
  • a.fun(1):相当于给B函数传了一个参数1,返回了C函数传参(1, 0)执行后的结果,即A函数传参(1, 0)后执行的结果,故输入0;
  • a.fun(2)和a.fun(2)同上,因为一开始a = fun(0)已经将n的值定为0了,后面console.log出来的就都是0了;

b行的分析:

  • fun(0):毫无疑问输出undefined;
  • fun(0).fun(1):参考a行的分析,可知这里输出的是0;
  • fun(0).fun(1).fun(2):类似的,输出1;
  • fun(0).fun(1).fun(2).fun(3):类似的,输出2;

c行的分析:

  • fun(0).fun(1):参见上面的分析,输出undefined、0;
  • c.fun(2)、c.fun(3):参见之前的分析,输出1、1。

扩展运算符

扩展运算符 ... 进行对象的拷贝是浅拷贝还是深拷贝,为什么

mark:

只回答了浅拷贝,不知道为什么

面试官提示我去转换一下代码看看它在 es5 里面是怎么样

ES6+的新特性