你须要知道的 javascript 的细节

如今的前端框架层出不穷,3个月就要从新入门一次前端的现状,让咱们来不及学好基础就开始上手框架。经常就由于这样,咱们会很快到达基础 基础技术瓶颈,基础是全部技术的核心,在跳槽季从新温故了一遍 javascript 基础,有收获,整理出来分享给你们。

对象

变量能够当对象使用

javascript 中全部的变量均可以当作对象使用,除了undefinednull ,咱们测试下javascript

false.toString() // "false"

[1,2,3].toString() //"1,2,3"

1..toString() //"1"

({a:'33'}).toString() //"[object Object]"
undefined.toString() //Uncaught TypeError

null.toString()   //Uncaught TypeError

数值和对象虽然能调用 toString 方法,可是在写法上须要注意下html

number 调用时不能直接数值后面直接调用toString 方法,由于 js 会将点运算符解析为数值的小数点前端

1.toString() //Uncaught SyntaxError

1..toString() //"1"

对象直接调用toString 方法时,须要用小括号包裹起来,否则js 会将对象的花括号识别成块,从而报错java

{a:'33'}.toString()  // Uncaught SyntaxError

({a:'33'}).toString() // "[object Object]"

对象删除属性

删除对象的属性惟一的方法是使用 delete 操做符,设置元素属性为 undefined 或则 null 并不能真正删除,只是移除了属性和值的关联
var test = {
    name:'bbt',
    age:'18',
    love:'dog'
}

test.name = undefined
test.age = null
delete test.love

for (var i in test){
  console.log(i+':'+test[i])
}

运行结果es6

name:undefined
age:null
undefined

只有 love 被正则删除,nameage 仍是能被遍历到面试

构造函数

javascript 中,经过关键字 new 调用的函数就被认为是构造函数,咱们能够经过构造函数建立对象实例

可是在使用过程当中你必定发现了,每实例化一个对象,都会在实例对象上创造构造函数的方法和属性。假若建立的实例比较多,重复建立同一个方法去开辟内存空间就会显得十分浪费,咱们能够经过把被常常复用的方法放在原型链上。编程

原型继承

javascript 和一些咱们所了解的面向对象编程的语言不太同样,在 es6 语法之前,咱们是经过原型链来实现方法和属性的继承
function Child(){
  this.name = 'bbt'
}

Child.prototype = {
    title:'baba',
    method: function() {}
};

function Grandson(){}

//设置 Grandson 的 prototype 为 Child 的实例
Grandson.prototype = new Child()

//为 Grandson 的原型添加添加属性 age
Grandson.prototype.age = 40

// 修正 Grandson.prototype.constructor 为 Grandson 自己
Grandson.prototype.constructor = Grandson;

var xiaomin = new Grandson()

//原型链以下
xiaomin // Grandson的实例
    Grandson.prototype // Child的实例
         Grandson.prototype //{title:'baba',...}
            Object.prototype
                {toString: ... /* etc. */};

对象的属性查找,javascript 会在原型链上向上查找属性,直到查到 原型链顶部,因此,属性在原型链的越上端,查找的时间会越长,查找性能和复用属性方面须要开发者本身衡量下。数组

获取自身对象属性

hasOwnProperty 方法可以判断一个对象是否包含自定义属性,而不是在原型链上的属性前端框架

var test = {hello:'123'}

Object.prototype.name = 'bbt'

test.name  //'bbt'
test.hasOwnProperty('hello') //true
test.hasOwnProperty('name') //false

for in 循环能够遍历对象原型链上的全部属性,如此咱们将 hasOwnProperty 结合循环for in 可以获取到对象自定义属性微信

var test = {hello:'222'}
Object.prototype.name = 'bbt'

for(var i in test){
  console.log(i) // 输出两个属性,hello ,name
}


for(var i in test){
  if(test.hasOwnProperty(i)){
    console.log(i)//只输出 hello
  }
}

除了上面的方法,getOwnPropertyNames Object.keys 方法,可以返回对象自身的全部属性名,也是接受一个对象做为参数,返回一个数组,包含了该对象自身的全部属性名。

var test = {hello:'222'}
Object.prototype.name = 'bbt'

Object.keys(test) //["hello"]
Object.getOwnPropertyNames(test) //["hello"]

getOwnPropertyNamesObject.keys 的用法有什么区别呢

Object.keys方法只返回可枚举的属性,Object.getOwnPropertyNames 方法还返回不可枚举的属性名。

var a = ['Hello', 'World'];

Object.keys(a) // ["0", "1"]

Object.getOwnPropertyNames(a) // ["0", "1", "length"]  // length 是不可枚举属性

函数

函数声明的变量提高

咱们一般会使用函数声明或函数赋值表达式来定义一个函数,函数声明和变量声明同样都存在提高的状况,函数能够在声明前调用,可是不能够在赋值前调用

函数声明

foo(); // 正常运行,由于foo在代码运行前已经被建立
function foo() {}

函数表达式

foo; // 'undefined'
foo(); // 出错:TypeError
var foo = function() {};

变量提高是在代码解析的时候进行的,foo() 方法调用的时候,已经在解析阶段将 foo 定义过了。赋值语句只在代码运行时才进行,因此在赋值前调用会报错

一种比较少用的函数赋值操做,将命名函数赋值给一个变量,此时的函数名只对函数内部可见

var test = function foo(){
  console.log(foo) //正常输出
}

console.log(foo) //Uncaught ReferenceError

this 的工做原理

javascript 中 , this 是一个比较难理解的点,不一样的调用环境会致使 this 的不一样指向,可是惟一不变的是 this 老是指向一个对象

简单的说,this 就是属性和方法当前所在的对象(函数执行坐在的做用域),平时使用的 this 的状况能够大体分为5种

调用方式 指向
1. 全局范围调用 指向 window 全局对象
2. 函数调用 指向 window 全局变量
3. 对象的方法调用 指向方法调用的对象
4. 构造函数调用 指向构造函数建立的实例
5. 经过,call ,apply ,bind 显示的指定 this指向 和传参有关

Function.call

语法:function.call(thisArg, arg1, arg2, …), thisArg表示但愿函数被调用的做用域, arg1, arg2, …表示但愿被传入函数额参数 , 若是参数为空、 nullundefined,则默认传入全局对象。

代码示例

var name = 'xiaomin'
var test = {name : 'bbt'}

function hello( _name ){
  _name ?console.log(this.name,_name): console.log(this.name)
}

hello() //xiaomin
hello.call(test) //bbt
hello.call(test,'xiaohong') //bbt xiaohong
hello.call() //xiaomin
hello.call(null) //xiaomin
hello.call(undefined) //xiaomin

Function.apply

语法和 call 方法相似,不一样的是,传入调用函数的参数变成以数组的形式传入,即 func.apply(thisArg, [argsArray])

改造上面的示例就是

hello.apply(test,['xiaomin'])

Function.bind

bind方法用于将函数体内的 this绑定到某个对象,而后返回一个新函数。
var d = new Date();
d.getTime()

var print = d.getTime; //赋值后 getTime 已经不指向 d 实例
print() // Uncaught TypeError

解决方法

var print = d.getTime.bind(d)

容易出错的地方

容易出错的地方,函数调用,this 老是指向 window 全局变量,因此在对象的方法里若是有函数的调用的话(闭包的状况),this 是会指向 全局对象的,不会指向调用的对象,具体示例以下

var name = 'xiaomin'
var test = {
  name : 'bbt'
}
test.method = function(){
  function hello(){
      console.log(this.name)
    }
    hello()
}

// 调用
test.method() // 输出 xiaomin

若是须要将 this 指向调用的对象,能够将对象的 this 指向存储起来,一般咱们使用 that 变量来作这个存储。改进以后的代码

var name = 'xiaomin'
var test = {
  name : 'bbt'
}
test.method = function(){
  var that = this
  function hello(){
      console.log(that.name)
    }
    hello()
}

// 调用
test.method() // 输出 bbt

闭包和引用

闭包咱们能够理解成是在函数内部定义的函数

javascript 中,内部做用域能够访问到外部做用域的变量,可是外部做用域不能访问内部做用域,须要访问的时候,咱们须要经过建立闭包,来操做内部变量

function test(_count){
  var count = _count

  return {
    inc:function(){
      count++
    },
    get:function(){
      return count
    }
  }
}

var a = test(4)
a.get()//4
a.inc()
a.get()//5

闭包中常会出错的面试题

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 0);
}

不少同窗会以为,上面的代码会正常输出0到9,可是实际是输出十次10。遇到这个题目,除了闭包的概念要理解清楚,你还须要知道,setTimeout 内的代码会被异步执行,代码会先执行全部的同步代码,即上面的这段代码会先将 for 循环执行,此时 i 的值为 10,console.log(i) 一直引用着全局变量的 i 因此会输出十次 10

改进代码,咱们在 for 循环里建立一个闭包,把循环自增的 i 做为参数传入

for(var i = 0; i < 10; i++) {
    (function(e) {
        setTimeout(function() {
            console.log(e);
        }, 1000);
    })(i);
}

setTimeout && setInterval

javascript 是异步的单线程运行语言,其余代码运行的时候可能会阻塞 setTimeout && setInterval 的运行
console.log(1)
setTimeout(function(){
  console.log(2)
}, 0);
console.log(3)

输出结果: 1,3,2  //setTimeout 被阻塞

处理阻塞的方法是将setTimeoutsetInterval放在回调函数里执行

function test(){
      setTimeout(function(){
          console.log(2)
    }, 0);
}

setTimeoutsetInterval 被调用时会返回一个 ID 用来清除定时器

手工清除某个定时器

var id = setTimeout(foo, 1000);
clearTimeout(id);

清楚全部的定时器

var lastId = setTimeout(function(){
  console.log('11')
}, 0);

for(var i=0;i<lastId;i++;){
  clearTimeout(i);
}

获取最后一个定时器的id,遍历清除定时器,能够清除全部的定时器。

类型

包装对象

数值、字符串、布尔值——在必定条件下,也会自动转为对象,也就是原始类型的“包装对象”。

咱们能够经过构造函数,将原始类型转化为对应的对象即包装对象,从而是原始类型可以方便的调用某些方法

数值,字符串,布尔值的类型转换函数分别是 Number,String,Boolean,在调用的时候在函数前面加上New 就变成了构造函数,可以蒋对应的原始类型转化为“包装对象”

var v1 = new Number(123);
var v2 = new String('abc');
var v3 = new Boolean(true);

typeof v1 // "object"
typeof v2 // "object"
typeof v3 // "object"

v1 === 123 // false
v2 === 'abc' // false
v3 === true // false

类型转换

类型转换分为强制类型转换和自动转换,javascript 是动态类型语言,在到吗解析运行时,须要的数据类型和传入的数据类型不一致的时候,javascript 会进行自动类型转化。固然,你也能够经过类型转换方法进行强制类型装换。

平常开发中,咱们最经常使用的数据类型自动转换不过就下面三种状况

不一样数据类型之间相互运算

'2'+4 // '24'

对非布尔值进行布尔运算

if('22'){
  console.log('hello')
}

对非数据类型使用一元运算符

+'12'  //12

咱们也经过 Number ,String,Boolean 来进行强制数据类型转换。强制类型转化的规则有点复杂,咱们来了解一下。

Number 转换 引用阮老师的详细解释

第一步,调用对象自身的valueOf方法。若是返回原始类型的值,则直接对该值使用Number函数,再也不进行后续步骤。

第二步,若是 valueOf 方法返回的仍是对象,则改成调用对象自身的 toString 方法。若是 toString 方法返回原始类型的值,则对该值使用 Number 函数,再也不进行后续步骤。

第三步,若是 toString 方法返回的是对象,就报错。

String 转换方法一样也是经过调用原对象的 toString 方法和 valueOf 方法,可是不一样的是 String 函数会先调用 toString 方法进行转换

Boolean 的转换规则会相对简单一些,除了几个特殊的值,都会被转化为 true

undefined
null
+0或-0
NaN
''(空字符串)

可是要注意

Boolean('false') //true

typeof

typeof 操做符返回数据类型,可是因为 javascript 设计的历史缘由, typeof 现已经不能知足咱们如今对于类型判断的要求了
Value Class Type
"foo" String string
new String("foo") String object
1.2 Number number
new Number(1.2) Number object
true Boolean boolean
new Boolean(true) Boolean object
new Date() Date object
new Error() Error object
[1,2,3] Array object
new Array(1, 2, 3) Array object
new Function("") Function functio
/abc/g RegExp object (function in Nitro/V8)
new RegExp("meow") RegExp object (function in Nitro/V8)
{} Object object
new Object() Object object
null null object

咱们能够看到,typeof 不能区分对象的数组和日期,还会把 null 判断成对象,那咱们通常是何时用 typeof 呢。咱们能够用来判断一个已经定义的变量是否被赋值。

var a
if(typeof a == 'undefined'){
  console.log('a 已经被定义')
}

instanceof

instanceof 操做符一般用来判断,一个对象是否在另外一个对象的原型链上,须要注意的是 instanceof 的左值是对象,右值是构造函数
// defining constructors
function C() {}
function D() {}

var o = new C();

// true, because: Object.getPrototypeOf(o) === C.prototype
o instanceof C;

// false, because D.prototype is nowhere in o's prototype chain
o instanceof D;

#### Object.prototype.toString

那么咱们有没有能够用来区分变量数据类型的方法呢,有, Object.prototype.toString

一些原始数据类型也有 toString 方法,可是一般他们的 toString 方法都是改造过的,不能进行 数据类型判断,因此咱们须要用 Object 原型链上的 toString 方法

var a = 1234
a.toString() // '1234'

Object.prototype.toString.call(a) // "[object Number]"

不一样类型返回的结果以下:

1. 数值 [object Number]
 2. 字符串 [object String]
 3.布尔值 [object Boolean]
 4.undefined [object undefined]
 5.null  [object Null]
 6.数组 [object Array]
 7.arguments [object Arguments]
 8.函数 [object function]
 9.Error [object Error]
 10.Date [object Date]
 11.RegExp [object RegExp]
 12.其余对象 [object object]

那么咱们就可以经过 Object.prototype.toString 方法,封装一个能够判断变量数据类型的函数了

function type(obj) {
    return Object.prototype.toString.call(obj).slice(8, -1);
}

type(function(){}) //"Function"
此次咱们从对象、函数、类型三方面入手了解了 javascript 中容易被忽视或则说比较难理解的地方,我会继续将我在学习中积累的内容分享给你们,若是你们以为文章有须要改进或则有其余想要了解的内容的,欢迎私信,评论或则微信我,646321933
相关文章
相关标签/搜索