goldEli / blog

Blog
MIT License
2 stars 1 forks source link

JavaScript #3

Open goldEli opened 6 years ago

goldEli commented 6 years ago

变量类型

基本类型:Null Undefined String Number Boolean Symbol

引用类型:Object Function

判断类型的方法:type of

判断实例与构造函数依赖的方法:instanceof

原型和原型链

在 JavaScript 中万物皆对象。Null 是所有对象的源头,Null就像上帝,Function 和 Object 就是亚当和夏娃,它们通过 prototype 和 constructor 繁衍后代,prototype 提供基因,constructor 就是子宫。

用代码来描述:

// 对象可扩展
var obj = {};
obj.b = 1
var arr = [];
arr.b = 1
function fn() {}
fn.b = 1 

// 判断对象中是否存在该属性
obj.hasOwnProperty('b') === true

// __proto__
console.log(obj.__proto__)
console.log(arr.__proto__)
console.log(fn.__proto__)

// 只有函数有 prototype
console.log(obj.prototype) // undefined
console.log(arr.prototype)  // undefined
console.log(fn.prototype)

// 应用类型的 __proto__ 属性指向它构造函数的 prototype 属性
console.log(fn.__proto__ === fn.constructor.prototype)                
console.log(fn.__proto__ === Function.prototype)                
console.log(arr.__proto__ === Array.prototype)                
console.log(obj.__proto__ === Object.prototype) 

当对象 obj 在访问属性的时候,会在自身属性中寻找,如果自身没有,就会通过 obj.__proto__ (即构造函数的 prototype)中去寻找。如果还没有就会继续通过 obj.__proto__.__proto__ 中去寻找,一直向上寻找,就形成了原型链。如果在最上层也没找到,就会返回 undefined

最上层是什么 —— Object.prototype.__proto__ === null

作用域和闭包

作用域

作用域的目的是确定变量的访问权限,也就是为了避免变量污染。

首先 JavaScript 采用的是词法作用域,也就是静态作用域,即创建的时候就确定好了。

在ES6以前,JavaScript 只有全局作用域和函数作用域,没有块级作用域,不像 Java 那样,一个花括号就是一个作用域。

在ES6中 引入了 let 关键字,才有了块级作用域。

作用域链

当变量查找时,会从当前上下文中的变量对象中查找,如果没有,会从父级上下文的变量对象中查找,直到找到全局的变量。由多个执行上下文的变量对象构成的链表就叫作用域链

执行上下文

console.log(a) // undefined
var a = 1
console.log(b) // 报错
b = 2

当一段 JavaScript 执行时,要先经历一个解析阶段,就是把要用的变量放入全局的执行上下文,比如解析到 var a 时,会将 a 扔到全局执行上下文中。

开始执行代码,当用到 a 变量时,a 还未被赋值,被得到 undefined。

b 未使用 var 关键字来定义,所以不会放到全局执行上下文中,当用到 b 时,会报错,b 未定义。

函数也有执行上下文,比全局执行上下文多 this arguments

闭包

闭包指的是,有权限访问访问其他作用域中变量的函数。

理解闭包核心:JavaScript的函数作用域是在函数创建的时候定义的,而不是在执行的时候创建的

看下面的例子:

function out() {
    var name = "js"
    return function inner(){
        console.log(name)
    }
}
var fn = out() // fn 引用了 out 的作用域
fn() // 当 fn 执行的时候就可以访问到 out 的作用域中的 name 变量

垃圾回收机制

引用计数

当声明一个变量,将一个引用类型赋值给该变量,则当前引用次数为1,如果同一个值赋给另一个变量,则引用次数加1。相反,如果拥有该值的变量拥有了其他值则减1,当值的引用次数为0时,垃圾回收期下次运行时,就会回收。

下面这个例子:

function func() {
    var a = new Object()
    var b = new Object()

    a.someOtherObject = b
    b.otherObject = a
}
// 这两个值引用次数都为2,永远不会回收

标记清除

当函数中声明一个变量,则标记这个变量为“进入环境”,只要进入相应的环境你就可以访问到它。当变量离开环境时,则将其标记会“离开环境”

异步

JavaScript 实现异步的方法?

由于回调函数的写法会导致,函数嵌套,造成代码横向发展,而不是纵向发展,从而难看难维护。

Promise 应运而生。

基本用法:

function func(resolve, reject){
 setTimeout(function(){
  resolve('func')
 },500)
}
new Promise(func)
.then(function(data){
 console.log(data)
 return 'next'
})
.then(function(data){
 console.log(data)
})

Promise 解决了代码横向发展的问题,但一眼望去全是 .then,可读性很差。

所以又出现了 generator 来解决这个问题:

generator 基础应用:

    var fetch = require('node-fetch');

    function* gen(){
      var url = 'https://api.github.com/users/github';
      var result = yield fetch(url);
      console.log(result.bio);
    }

    var g = gen();
    var result = g.next();

    result.value.then(function(data){
      return data.json();
    }).then(function(data){
      g.next(data);
    });

generator 是 ES6 对协程的实现,yield 可以暂停函数。generator 需要手动通过 .next() 来启动函数继续执行,加上 * 和 yield 让人很难理解。

async/await 目前来说终极的解决方案:

基本用法:

    function timeout(ms) {
      return new Promise((resolve) => {
        setTimeout(resolve, ms);
      });
    }

    async function asyncPrint(value, ms) {
      await timeout(ms);
      console.log(value)
    }

   // 5秒后打印 hello world
   asyncPrint('hello world', 5000) 

async/await 其实就是 generator 的语法糖,* 号相当于 async,yield 相当于 await。await 有了返回值后会继续执行不需要像 generator 需要通过 .next 来启动函数继续执行

ES6/7常用特性