Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript体系结构 #3

Open
moyahuang opened this issue Mar 29, 2020 · 3 comments
Open

JavaScript体系结构 #3

moyahuang opened this issue Mar 29, 2020 · 3 comments

Comments

@moyahuang
Copy link
Owner

moyahuang commented Mar 29, 2020

类型

值类型 vs. 引用类型

考点
1. 区别

三点:存储位置,访问机制,变量间赋值不同。
前者:栈区,变量访问的是值本身,在栈区复制一份。
后者:堆区(+栈区),变量访问的是其堆区的引用对象,复制的是栈区的地址。

2. 类型判断

两个关键字+构造函数
typeof 可正确返回除null以外的值类型以及函数类型,typeof null === 'object'
instanceof 可判否所有的引用类型,注意[] instanceof Array === true 且 [] instanceof Object === true
constructor 对象的原型对象都有属性constructor指向其构造函数,因此也可用来判否引用类型
注:上面的方式都有其缺陷,借用Object.prototype.toString可以判断任意类型,详见下方

3. 深浅拷贝
深浅拷贝的概念是相对于引用类型存在的。
数组的浅拷贝方法:

newArr=arr.concat() //1
newArr=arr.slice() //2

对象的浅拷贝方法

function shallowCopy(obj){
    var newObj={};
    for(var attr in obj){
        if(obj.hasOwnProperty(attr)){
            newObj[attr]=obj[attr];
        }
    }
    return newObj;
}

深拷贝方法:
深拷贝可能会碰到的坑

  • Symbol属性
  • 属性值为undefined或方法
  • 特殊引用类型Date/RegExp
  • 循环引用(环)
  1. JSON.parse(JSON.stringify(obj))
    这种方式可以满足大多数场景的需求
  2. 递归(基于上述对象的浅拷贝方法)
    粗糙版
function isObject(obj){
  return typeof obj==='object'&&obj!==null;
}
function deepClone(obj){
  var newObj=Array.isArray(obj)?[]:{};

  for(var key in obj){
    if(obj.hasOwnProperty(key)){
      newObj[key]=isObject(obj[key])?deepClone(obj[key]):obj[key];
    }
  }
  return newObj;
}

上面这种方法的缺陷可见这篇文章的分析
精致版(精致版除了没有解决Symbol属性的问题,其他近乎完美

function cloneDeep(obj, hash = new WeakMap()) {
  // 1
  if (obj == null) return obj;

  // 2
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);

  // 3 原始类型和函数类型的处理
  // 由于操作函数的行为一般只是执行,不涉及到修改,故返回即可
  if (typeof obj !== "object") return obj;

  // 4 循环引用
  if (hash.has(obj)) return hash.get(obj);

  // 5 处理对象和数组
  // 克隆后,使用弱引用WeakMap以对象作为key,将值存起来
  const result = new obj.constructor();
  hash.set(obj, result);

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      result[key] = cloneDeep(obj[key], hash);
    }
  }

  return result;
}

隐式类型转换

JS有6种值类型,null, undefined, symbol比较特殊,“正常”的也就3种number, string, boolean,而类型转换的目标也就这三种类型。

注意:number类型有1种特殊值NaN

隐式类型转换的3个场景分别对应3种转换目标boolean, number, string

  • if/!等逻辑判断
  • 数学运算(包括弱等==,算术以及关系运算)
  • 输出

2种源类型:值类型和对象类型。

值类型的转换都有固定的规则,比较简单只列举特殊的几个:

转换为数字时:

  • undefined → NaN
  • null → 0
  • ' ' → 0
对象类型的转换规则

所有的对象转换为布尔型都为true,所以需要讨论的情况是目标为number以及string

根据场景的不同,对象类型转换时所谓的hint也不同

hint='string'时,方法不存在或者返回值不为原始类型时,依次调用对象的以下方法:

[Symbol.toPrimitive]→toString→valueOf→throw Error

hint='number'和hint='default'

[Symbol.toPrimitive]→valueOf→toString→throw Error

注:

  1. default是指某些特殊情况(如+,关系运算时,无法确定转换为string还是number时,规则和number一样,不需要特殊记忆
  2. Array,Object的valueOf返回的是本身
{}==!{}//的转换过程如下
{}==false//1.
'[object Objct]'==0//2
NaN==0//3.
特殊的数学运算:关系和+

关系运算可以通过以小(弱等)见大(若不等, >, <等)的方法推导

弱等(==)
  • 运算数二者为null和undefined时,不进行类型转换,无条件返回true
  • 运算数其一为NaN时,不进行类型转换,无条件返回false
  • 运算数其一为布尔型,不管三七二十一,先转换成数字
  • 自动类型转换的终点是两边都为stringnumber, 若运算数两者都有,则会将一方转换为数字类型
+
  • 作为一元运算符时会将运算数转换为数字
  • 作为二元运算符时,若有任意运算数为字符串时,+会作为字符串拼接符
  • 对象类型中的Date会先调用toString而不是valueOf
+"hello"//NaN
+[]//NaN
new Date+1//结果为string类型

原型和原型链

关于原型你需要知道的

  1. 对象是可扩展的,即可自由添加属性和方法
  2. 所有对象都有__proto__属性,属性值为一个普通对象,即其'原型'对象
  3. 所有函数都有prototype属性,属性值仍然是一个普通对象
  4. 函数也是对象
  5. 所有函数(包括构造函数)的__proto__指向对象Function.prototype
  6. 对象的__proto__对象指向其构造函数的prototype对象
Function.__proto__===Function.prototype;//true
Object.__proto__===Function.prototype//true
Function.prototype===Object.prototype;//因为Function.prototype也是普通对象,普通对象的构造函数即Object 第六条

所谓原型链即访问某属性和方法时,从对象本身查找,如没找到则沿着__proto__逐层查找,直到找到终点Object.prototype(Object.prototype.proto=null)。

继承

继承的核心就是将子类构造函数的原型对象设为父类的实例

注意事项:

  1. 原型对象应该有construtor属性,该属性值应指向构造函数,且该属性不可枚举
  2. 如果父类中属性值为对象类型时,子类实例对该属性的改变将会影响父类实例,这个问题可以用"借用构造函数"的方式解决
  3. 为了复用父对象方法,应该在父构造函数的原型对象上添加方法

示例

function Animal(type){
  this.type=type;
  this.sound="hello";
  this.color=['red','green','blue'];
}

Animal.prototype.sayHello=function(){
  console.log(`${this.type} say ${this.sound}`);
}

function Cat(type, sound){
  Animal.call(this, type);
  this.sound=sound;
}

Cat.prototype=new Animal();
Object.defineProperty(Cat.prototype, 'constructor', {
  value: Cat,
  enumerable: false
});

测试用例

var cat=new Cat("波斯","meow");
cat.sayHello(); // 波斯 say meow
cat.color.push('cyan'); //修改color属性值
console.log(cat.__proto__.constructor);//[Function Cat]
console.log(cat.color); //[ 'red', 'green', 'blue', 'cyan' ]

var animal=new Animal('any');
console.log(animal.color);//[ 'red', 'green', 'blue'] 

作用域和作用域链

概念:自由变量,闭包
考点

  • this

异步

@moyahuang
Copy link
Owner Author

moyahuang commented Apr 1, 2020

看懂源码必备知识点

  • javascript的逻辑运算符&&和||

    //写出下面答案
    var res={} || 'hello'
    var res=null || undefined
    var res={} && 'hello'
    var res=null && undefined

    答案:{} ; undefined; 'hello'; null
    解释:a || b,若a为true返回a表达式,否则返回b表达式;a为false返回b表达式;&&与之相反。

  • js的内置对象Function, Object的原型对象的关系

    Object.toString === Object.prototype.toString

    答案:false
    解释:因此Object.toString.call()和Object.prototype.toString.call()所接受的参数和结果都不同。
    Object, Function虽然是内置对象,但更是构造函数。所有函数的原型对象
    proto(已不推荐使用)都指向Function.prototype。特殊性原则:函数比对象特殊,因此Object
    和Function的__proto__指向以函数的规则为准。(PS. 别忘了原型链的终点是Object.prototype哦)

  • 什么叫plainObject?
    我的理解是一般指由Object构造函数构造出来的对象就是plainObject。
    所以window, document,new NonObjectConstructor,**Object.create(null)**等都不是plainObject。
    注意:在jQuery3.0中认为Object.create({})创建的对象是plainObject。虽然和普通对象仍有区别,例
    如Object.create({})的__proto__属性不指向Object.prototype,但是其构造函数属性值为Object。

    var obj=Object.create({});
    obj.__proto__===Object.prototype; //false
    obj.constructor===Object; //true

@moyahuang
Copy link
Owner Author

moyahuang commented Apr 1, 2020

跟着yayu学习JavaScript实用算法

  • 防抖debounce
    为什么防抖?
    某些事件(例如keyup,keydown,mousemove,resize等)发生频率太快,而实际上用户往往不需要那么频繁的触发事件响应,从而影响用户体验
    算法思想:

    • 还原事件响应函数的词法环境(甚至返回值),通过增加可控的响应时长,来控制实际响应频率
    • 事件发生后,设定定时器等待wait时间之后才触发事件;若定时器未结束,发生新的事件,则停止原来的定时,重启新的定时器
     function debounce(fn, wait, immediate){
      var timer,result;
      var debounced=function(){
          var that=this;
          var args=arguments;
          clearTimeout(timer);
          if(immediate){
              timer=setTimeout(function(){
                  timer=null;
              },wait);
              if(!timer)result=fn.call(that,args);
          }else{
              timer=setTimeout(function(){
                  result=fn.call(that,args);
              }, wait);
          }
      }
      debounced.cancel=function(){
          clearTimeout(timer);
          timer=null;
      }
      return debounced;
      }
  • 节流throttle

    //todo;
  • 数组去重

  • 类型判断(包括plainObject, 空对象,类数组对象,DOM元素等)

    //核心是调用Object.prototype.toString
    function type(args){
       return Object.prototype.toString.call(args).split(" ")[1].slice(0,-1).toLowerCase();
    }
     //下面这种方法是经过我简化的,与原文不同,通过众多测试用例的判断暂时没出现问题
    function isPlainObject(obj){
       var proto;
       if(type(obj)!=='object')  return false;
       proto=Object.getPrototypeOf(obj);
       if(!proto)  return false;
       return proto.constructor === Object;
    }
    
    //对于什么叫类数组 可以自行根据场景定义 没有标准定义 jQuery中的实现条件就很宽松 略
    
    function isEmptyObject(obj){
      var item;
      for(item in obj) return false;
      return true
    }
    
    function isDOM(obj){
      return !!(obj && obj.nodeType === 1);
    }
  • 深浅拷贝

  • 求并/差/交集

@moyahuang
Copy link
Owner Author

moyahuang commented Apr 1, 2020

手写原生方法

模拟实现一个new的效果

模拟new的效果要注意:

  • 将对象加入到原型链上
  • 构造函数结果若为obj则返回该obj,否则返回this
function create(){
    var obj=new Object();
    var constructor=[].shift.call(arguments);//获取构造函数
    obj.__proto__=constructor.prototype;
    var res=constructor.**apply**(obj, arguments);//将构造函数的属性挂在obj上
    return res instanceof Object?res:obj;
}

如何将arguments转换为数组
[].prototype.slice(arguments)

模拟实现一个 bind 的效果

实现一个 call/apply 函数

手写Promise

手写XMLHttpRequest

  • 监听XHR状态 readystate 4表示响应报文已全部获取
  • 监听XHR响应状态码 2xx表示成功
  • 响应主体属性由responseText获取

使用示例

var xhr=new XMLHttpRequest();

xhr.open("post","http://example.com/post", true);//false表示异步请求

xhr.onreadystatechange=function(){
    if(xhr.readystate === 4){
        if(xhr.status>=200 && xhr<300){
            alert(xhr.responseText);
        }
    }
}
var data={ name:"moya", age:13};
//模拟POST表单提交
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

xhr.send(transform(data)));//参数不能为空

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant