前端面试题 JavaScript之语言基础

js的基本数据类型有哪些

Undefined、Null、Boolean、Number、String // Object属于复杂数据类型
  • javascript中typeof的结果有六个,分别是boolean,number,string,object,function,undefined
  • 其中typeof null === ‘object’

为什么typeof null === ‘object’

js中null是原始(primitive)数据类型之一,通常认为这是一个错误,null表示值为空,常用来释放内存。
原理是这样的,不同的对象在底层都表示为二进制,js中二进制前三位为0都会被判定为object,null的二进制表示全是0,自然前三位也是0,因此typeof的结果为object

参考:《你不知道的JavaScript(上卷)》p103,注释部分

介绍一下js对象的属性描述符

Object.getOwnPropertyDescriptor(myObject, "a")
// 返回值如下
{
    value: 2,  // 属性值,
    writable: true,  // 是否可修改
    enumerable: true,  // 是否可遍历,可遍历属性将出现在for..in.. 中
    configurable: true // 是否可以通过defineProperty方法修改属性描述符 
}
// configurable: false有一个例外,writable属性可由true改为false,但是不能fasle改true 
// 设置getter和setter时,value和writable属性会被忽略

如何评价js中万物皆是对象这句话

对象仅是js6种语言类型(string,number,boolean,null,undefined,object)之一
考虑下面的例子

var strPrimitive = "I am a string"
var strObject = new String("I am a string")
strPrimitive instanceof String // => false
strObject instanceof String // => true
console.log(strPrimitive.length) //=> 13
console.log(strObject.length) // => 13

length是String对象的属性,必要时语言会自动将字符串字面量转换为String对象,Number等内置对象也是如此,因此会有js中万物皆是对象的错觉

参考:《你不知道的JavaScript(上卷)》第三章——对象

使用new调用函数(发生构造函数调用时)会发生什么

  1. 创建(构造)一个全新的对象
  2. 这个新对象会被执行原型连接,即proto属性指向构造函数prototype
  3. 这个新对象会绑定函数调用的this
  4. 若指定的返回值不是对象,则返回新创建的对象,若是对象,则返回指定的对象

js中的面向对象

  1. 一切对象的父对象是Object
  2. js不存在类,构造函数是类的一部分,js中“类”是构造函数的一部分,Fun.prototype
  3. 其他语言,类生成实例是一种拷贝,但是js并没有拷贝对象的副本,而只是创建了对构造函数prototype属性的关联
  4. js的原型继承更类似于java类中的静态函数,或者称为委托(即创建关联后,可以委托访问另外一个对象的委托的属性(属于原型的属性)),或者称为差异继承,继承之后再赋值,可以屏蔽继承来的属性

参考:《你不知道的Javascript(上卷)》p151

js的属性设置与屏蔽

js中,不属于自己的属性,可以去原型链中找,如果原型继承之后,再设置同名属性,会有三种情况发生

  1. 原型链上层存在foo普通数据访问属性,且没有标记为只读(writable:false),那就会直接在对象上添加一个名为foo的新属性,foo是屏蔽属性
  2. 原型链上层存在foo,但是标记为只读,那么无法创建屏蔽属性,赋值语句会被忽略,严格模式报错(TypeError)
  3. 原型链上层存在foo,但是不是普通数据访问属性,而是访问器属性(setter),一定会调用该setter,foo不会被添加到对象上,也不会重新定义setter

参考:《你不知道的Javascript(上卷)》p145

js属性隐式屏蔽

var a = {x:1};
var b = {};
b.__proto__ = a;
b.x++ // 等价于b.x = b.x + 1 => b.x = 1 + 1,会给b创建一个新属性
console.log(a.x, b.x);// => 1,2

ES5中如何创建不可变对象

  1. 不变性

     var myObject = {};
     Object.defineProperty(myObject,"a", {
         value: 12,
         writable: false, // 不可修改
         configurable: false  // 不可重定义或者删除
     })
     delete myObject.a   // 删除或赋值无效,严格模式报错
     console.log(myObject.a) // => 12
    
  2. 禁止扩展

     var myObject = { a: 2 };
     Object.preventExtensions(myObject);
     myObject.b = 3;
     console.log(myObject.b); // => undefined
    
  3. 密封 seal

    Object.seal(..)。密封的作用是给对象调用Object.preventExtensions(..),使之不能扩展,另外所有现有属性标记为configurable:false。
    所以密封之后,不能添加新属性,也不能重新配置或者删除属性(但是可能可以修改,不改变writable的值)

  4. 冻结 freeze

    Object.freeze(..)。调用Object.seal(..),并把数据访问属性标记为writable:false,使属性不可修改。
    但是freeze方法只能避免自身属性的直接修改,不能限制引用类型的修改(如需深度冻结,可用递归,或者遍历等方法实现)

ES6中的for..of.. 与ES5中的for..in..有什么区别

for..of.. 是遍历值,for..in..是遍历键

什么是词法作用域,js中如何改变词法作用域

  1. 简单地说,词法作用域就是定义在词法阶段的作用域,换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里决定的,因此当词法分析器处理代码时,会保持作用域不变
  2. js中改变词法作用域通常有2种方法,eval与with,eval传入字符串,将字符串当做js代码直接执行,with针对传入的对象创建一个块级作用域(但是var声明并不会限制在这个块内,而是添加到with所处的函数作用域中)
/* eval */
var b = 2;
function foo(str, a){
    eval(str);
    console.log(a, b)
}
foo("var b = 3;", 1); // => 1, 3,作为参数传入的str,可以在执行时改变作用域

/* with */
var a = { b: 3, c: 4};
var b = 100;
with(a){
    b = 10; c = 20; d = 30;
}
console.log(a.b, a.c, a.d, b) // => 10 20 undefined 100,
// with改变了词法作用域,with语句中,先会查找变量是否是a的属性,不是再向上查找
// 如果属性不存在,并不会给a新增一个属性
// 因此b=10,等价于 a.b = 10, 外部的b不受影响
// d = 30,等价于window.d = 30 ,隐式地声明了一个全局变量
// with语句代码可读性很差,而且容易引起错误,严格模式下已被禁用

with和eval为什么会导致性能下降

  1. with作用域查找时,先查找with传入的对象是否具有该属性,不存在则向上查找,增加了查找的次数
  2. eval语句运行时,需要先解析为js代码,再编译运行,增加了编译前的时间
  3. with和eval都会在运行时修改或创建新的作用域,依次来欺骗其他在书写时确定的词法作用域。通常,js引擎会在编译阶段进行数项的编译优化,有些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到标识符。 如果引擎在代码中发现了eval或with,它只能简单地假设关于标识符位置的判断都是无效的,因此无法在词法分析阶段明确地知道eval会接收到什么样的代码,这些代码会对作用域如何进行修改,也无法预先知道传递给with用来创建新词法作用域的对象到底是什么。 最悲观的情况是,代码中出现了eval和with,所有的优化都是没有意义的,最简单的做法是完全不做优化,因此with和eval会导致性能下降。

参考: 《你不知道的JavaScript(上卷)》 第二章(词法作用域)

js中有哪些内置对象

Object是js所有对象的父对象

  • 数据封装类对象:Object、Array、Boolean、Number 和 String
  • 其他对象:Function、Arguments、Math、Date、RegExp、Error

根据存储位置的不同,js数据类型可以分为?

  1. 原始数据类型( Undefined,Null,Boolean,Number,String )
  2. 引用数据类型 ( 对象,包好数组,函数等)

原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,频繁使用;
引用数据类型存储在堆(heap)中,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能;
引用数据类型在栈中存储了指针,指向堆中该实体的起始地址。
解释器使用引用数据类型时,先检索栈中的地址,取得地址后从堆中获得实体。
栈内存的分配与回收由系统控制,但堆内存的分配与回收需手动控制,
常常将引用值指针设置为null回收内存

js创建对象的几种方式?

  1. 对象字面量的方式

     person={ name:"Tom", age: 3 };
    
  2. function无参构造函数

     function Person(){}
         var person = new Person();
         person.name = "Tom";
         person.age = "25";
         return person;
     }
    
  3. 构造函数(this)

     function Person(name,age){
        this.name = name;
        this.age = age;
     }
     var person = new Person("Tom", 20);
     console.log(person.name);
    
  4. 用工厂方式创建

     var person = new Object();
     person.name = "Tom";
     person.age = 3;
    
  5. 用原型方式来创建

     function Person(){};
     Person.prototype.name = "Tom";
     Person.prototype.age = 3;
     // 原型方式创建的对象,原型是所有对象共同使用的。
     var person1 = new Person();
     var person2 = new Person();
    
  6. 用混合方式来创建

     function Person(name, age){
       this.name = name;
       this.age = age; 
     }
     Person.prototype.hello = function(){
        alert("my name is " + this.name);
     }
     var person =new Person("Tom",27);
     person.hello();
    

说几条写js的基本规范?

  1. 不要在同一行声明多个变量。
  2. 请使用 ===/!==来比较true/false或者数值
  3. 使用对象字面量替代new Array这种形式
  4. 不要使用全局函数。
  5. switch语句必须带有default分支
  6. 函数不应该有时候有返回值,有时候没有返回值。
  7. for循环必须使用大括号
  8. if语句必须使用大括号
  9. for-in循环中的变量 应该使用var关键字明确限定作用域,从而避免作用域污染。

js原型,原型链,特点

  1. 原型 每个函数都有一个prototype属性,如果这个函数被当做一个构造函数(或称为构造器)使用,其创建的对象有一个隐式引用(proto,称为对象的原型)指向构造函数的prototype,通过这个构造函数无论创建多少对象,这些对象的proto都指向构造函数的prototype属性。

  2. 原型链 原型也是一个对象,因此原型也有一个非空引用指向自己的原型(所有对象最终的原型指向Object内置对象),由此形成原型链

     arr = new Array() // 数组继承自Object
     Array.prototype === arr.__proto__ // true
     Object.prototype === arr.__proto__.__proto__  === Array.prototype.__proto__// true,
    
  3. 特点

    • 对象的原型是一个引用值,创建的每一个对象并没有属于自己的原型副本,因此改变原型时,与之相关的对象也会继承该改变
    • 对象属性查找时,先查找自己是否具有该属性,不存在则查找原型的属性,不存在则查找原型的原型的属性,直至内置对象Object

      arr = new Array()
      arr.hello = "I am a Array"
      arr.hello // 自己的属性
      arr.slice // 继承属性,继承自 arr.__proto__(Array.prototype)
      arr.toString // 继承属性,继承自 arr.__proto__.proto__(Object.prototype)
      arr.world // 沿着原型链查找自内置对象Object,仍没有找到,返回undefined
      
  4. 相关判断

     //(1)instanceof 检查一个对象是否是一个构造函数的实例,原型链上的均可
     var arr = new Array()
     arr instanceof Object  // =>  true 
     Array instanceof Object  // =>  true
    
     //(2)in 判断对象是否存在某属性
     'toString' in arr // => true,in包含继承属性
    
     //(3)hasOwnProperty 判断对象中是否存在某本地属性,不含继承属性
     arr.hasOwnProperty('toString')  // => false,toString继承自Object
    
     //(4)isPrototypeOf 判断原型对象与实例之间的关系
     Object.prototype.isPrototypeOf(arr) // => true,原型链上的均可
    
     //(5)constructor,通过构造函数判断类型
     Array.prototype.constructor === Array // =>  true
     arr.__proto__.constructor === Array  // =>  true
    

js如何实现继承

  1. 构造函数的继承

     //(1)构造函数绑定
     function Cat(name,color){
         Animal.apply(this, arguments);
         this.name = name;
         this.color = color;
     }
    
     //(2)原型模式
     Cat.prototype = new Animal(); //原型指向Animal,继承Animal的所有属性
     Cat.prototype.constructor = Cat; //改变构造函数的指向
     var cat1 = new Cat("大毛","黄色");
    
     //(3)直接继承原型
     Cat.prototype = Animal.prototype;
     Cat.prototype.constructor = Cat;
     var cat1 = new Cat("大毛","黄色");
    
     //(4)空对象中介,常常封装为extend函数
     var F = function(){};
     F.prototype = Animal.prototype;
     Cat.prototype = new F();
     Cat.prototype.constructor = Cat;
     //(5)拷贝继承, 拷贝被继承对象原型到新对象原型中
    
  2. 非构造函数的继承

     //(1)object方法(改变原型指向)
     function object(o) {
         function F() {}
         F.prototype = o;
         return new F();
     }
     //(2)浅拷贝,缺点是拷贝的值若为引用对象,改变会有副作用
     //(3)深拷贝,现在jQuery库使用的继承方法
    

参考1:阮一峰-构造函数的继承
参考2: 阮一峰-非构造函数的继承

instanceof运算符作用,请给出其等价实现

A instanceof B, 用于判断A对象的原型链中是否有构造函数(或称构造器)B的原型属性

function instance_of(V, F) {
  var O = F.prototype;
  V = V.__proto__;
  while (true) {
    if (V === null)
      return false;
    if (O === V)
      return true;
    V = V.__proto__;
  }
}

参考1: mozilla对instanceof的定义
参考2: stackoverflow的回答

js大(小)于连续判断

和python,coffeescript不同,js无真正意义上的连续判断,如下所示

var a = 5
var b = 2
if( 4 < a < b) {
    // 等价于(4 < 5) < 2 => true(1) < 2 => true
    console.log(a) // 5
} else {
    console.log(b)
}

变量提升

  1. 函数作用域内部,变量提升

     var foo = 1;
     function bar() {
         foo = 10;
         return;
         function foo() {}
     }
     bar();
     alert(foo);  // => 1
    
  2. 变量提升,赋值不提升

     var foo = 1;
     function bar() {
         alert(typeof foo);
         var foo = "hello world";
     }
     bar() // => undefined
    
  3. 变量提升之函数是一等公民

     var foo = 1;
     function bar() {
         alert(typeof foo);
         function foo() {};
         var foo = "hello world";
     }
     bar()  // => function,函数声明与赋值均提升
    
  4. 换一种赋值方法,函数降为二等公民

     var foo = 1;
     function bar() {
         alert(typeof foo);
         var foo = function() {};
     }
     bar()  // => undefined, 函数赋值不提升
    
  5. 变量重复定义,后来为准

     var foo = 1;
     function bar() {
         function foo(){};
         alert(typeof foo);
         var foo = "hello world"
         alert(typeof foo);
     }
     bar()  // => function, string
    

new操作符之返回值

x = 1;
function bar() {
    this.x = 2;
    return x;
}
var foo = new bar();
alert(foo.x); // 2, new操作符返回A, 返回 A是对象? A :新建对象

函数的实参与形参个数与arguments对象

function foo(a) {
    alert(arguments.length)
    alert(foo.length)
}
foo(1, 2, 3) // 3, 1, arguments是实参,foo.length是形参
function foo(){}
delete foo.length;
alert(typeof foo.length); // => number, foo.length是继承属性,不可删除
function foo(a) {
    arguments[0] = 2;
    alert(a);
}
foo(1); // 2

函数声明

var foo = function bar() {};
alert(typeof bar) // => undefined
gzdaijie            updated 2018-05-04 01:47:18

results matching ""

    No results matching ""