一些你可能不熟悉的JS知识点总结


Posted in Javascript onMarch 15, 2019

 暂时性死区

只要块级作用域存在let命令,它所声明的变量就“绑定”这个区域,不再受外部的影响。这么说可能有些抽象,举个例子:

var temp = 123;
if(true) {
 console.log(temp);
 let temp;
}

 结果:

> ReferenceError: temp is not defined

 在代码块内,使用let声明变量之前,该变量都是不可用的。在语法上,称为“暂时性死区”。(temporal dead zone)

ES6规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。

call和apply方法

这两个方法都可以改变一个函数的上下文对象,只是接受参数的方式不一样。
call接收的是逗号分隔的参数。

apply接收的是参数列表。

相信你肯定看到过这样的代码:

var arr = [1, 2, 3];
var max = Function.prototype.apply.call(Math.max, null, arr);
console.log(max); // 3

那么对这段代码怎么理解呢?

1.将Function.prototype.apply看成一个整体

(Function.prototype.apply).call(Math.max, null, arr)

2.func.call(context, args)可以转化为context.func(args)

所以代码被转换为:

Math.max.apply(undefined, arr)

基本上到这一步已经没必要去解释了。

那么你有没有试过将call和apply互换位置呢?

var arr = [1, 2, 3];
var max = Function.prototype.call.apply(Math.max, null, arr);
console.log(max); // -Infinity

为什么的它的输出结果为-Infinity呢?

因为apply的第二参数必须为数组,这里并不是,所以参数不能正确的传递给call函数。
根据func.apply(context, args)可以转化为context.func(args)。所以被转化成了Math.max.call(), 直接调用则会输出-Infinity。

如果想要正确调用,则应这样书写:

var arr = [1, 2, 3];
var max = Function.prototype.call.apply(Math.max, arr);
console.log(max); // 3

为了巩固以上内容,且看一个面试题:

var a = Function.prototype.call.apply(function(a){return a;}, [0,4,3]);
alert(a);

分析弹出的a值为多少?

// 将call方法看成一个整体
(Function.prototype.call).apply(function(a){return a;}, [0,4,3]);

// func.apply(context, args)可以转化为context.func(...args)
(function(a){return a;}).call(0, 4, 3);

// 所以结果很明显,输出4

Proxy对象

作用:用来自定义对象中的操作。

let p = new Proxy(target, handler)

target 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。

且看一个的小栗子:

// onChange 即要进行的监听操作
var watch = (object, onChange) => {
 const handler = {
  // 如果属性对应的值为对象,则返回一个新的Proxy对象
  get(target, property, receiver) {
   try {
    return new Proxy(target[property], handler);
   } catch (err) {
    return Reflect.get(target, property, receiver);
   }
  },
  // 定义或修改对象属性
  defineProperty(target, property, descriptor) {
   onChange('define',property);
   return Reflect.defineProperty(target, property, descriptor);
  },
  // 删除对象属性
  deleteProperty(target, property) {
   onChange('delete',property);
   return Reflect.deleteProperty(target, property);
  }
 };

 return new Proxy(object, handler);
};


// 测试对象
var obj = {
 name: 'bjw',
 age: 22,
 child: [1, 2, 3]
}

// 对象代理
var p = watch(obj1, (type, property) => { 
 console.log(`类型:${type}, 修改的属性:${property}`)
});

p.name = 'qwe'
 类型:define, 修改的属性:name
 "qwe"

p.child
 Proxy {0: 1, 1: 2, 2: 3, length: 3}
 
p.child.push(4)
 类型:define, 修改的属性:3
 类型:define, 修改的属性:length
 4

p.child.length = 2
 类型:define, 修改的属性:length
 2

p.child
 Proxy {0: 1, 1: 2, length: 2}

 如果关注Vue进展的话,可能已经知道Vue3.0中将通过Proxy来替换原来的Object.defineProperty来实现数据响应式。之所以要用Proxy替换原来的API原因在于Proxy无需一层层递归为每个属性添加代理,一次即可完成以上操作。性能上更好,并且原本的实现有一些数据更新不能监听到,但Proxy可以完美监听到任何方式的数据改变,相信通过上面的例子已经能够感受到Proxy带来的优势了。唯一的缺点可能就是浏览器兼容性不太好了。

 Reflect对象

为什么要有这样一个对象?

  • 用一个单一的全局对象去存储这些方法,能够保持其他的JavaScript代码整洁、干净。(不然的话得通过原型链调用)
  • 将一些命令式的操作delete、in使用函数代替,目的是为了让代码更好维护,避免出现更多的保留字。

Reflect对象拥有以下静态方法:

Reflect.apply
Reflect.construct
Reflect.defineProperty
Reflect.deleteProperty
Reflect.enumerate // 废弃的
Reflect.get
Reflect.getOwnPropertyDescriptor
Reflect.getPrototypeOf
Reflect.has
Reflect.isExtensible
Reflect.ownKeys
Reflect.preventExtensions
Reflect.set
Reflect.setPrototypeOf

具体函数细节:

Reflect.apply(target, this, arguments)

// target:目标函数
// this:绑定的上下文对象
// arguments:函数的参数列表
Reflect.apply(target, this, arguments)

const arr = [2, 3, 4, 5, 6];
let max;
// ES6
max = Reflect.apply(Math.max, null, arr)

// ES5 
max = Math.max.apply(null, arr);
max = Function.prototype.apply.call(Math.max, null, arr);

Reflect.construct(target, argumentsList[, newTarget])

// 这个方法,提供了一种新的不使用new来调用构造函数的方法
function A(name) {
 console.log('Function A is invoked!');
 this.name = name;
}
A.prototype.getName = function() {
 return this.name;
};

function B(age) {
 console.log('Function B is invoked!');
 this.age = age;
}
B.prototype.getAge = function() {
 return this.age;
};


// 测试 (这两种是一致的)
var tom = new A('tom');
var tom = Reflect.construct(A, ['tom']);


// jnney继承了A的实例属性,同时继承了B的共享属性
// 简单来说,A构造函数被调用,但是 jnney.__proto__ === B.prototype
var jnney = Reflect.construct(A, ['jnney'], B);

Reflect.defineProperty(target, propertyKey, attributes)

这个方法和Object.definePropperty(属性定义失败,会抛出一个错误,成功则返回该对象)相似,不过Reflect.defineProperty(属性定义失败,返回false,成功则返回true)返回的是一个Boolean值。

let obj = {};

let obj1 = Object.defineProperty(obj, 'name', {
 enumerable: true,
 value: 'bjw' 
});

// 这里会返回false 因为我们上面定义name这个属性是不可修改的,
// 然后我们又在这里修改了name属性,所以修改失败返回值为false
let result1 = Reflect.defineProperty(obj, 'name', {
 configurable: true,
 enumerable: true,
 value: 'happy'
});
console.log(result1); // false

Reflect.deleteProperty(target, propertyKey)

let obj = {
 name: 'dreamapple',
 age: 22
};

let r1 = Reflect.deleteProperty(obj, 'name');
console.log(r1); // true
let r2 = Reflect.deleteProperty(obj, 'name');
console.log(r2); // true
let r3 = Reflect.deleteProperty(Object.freeze(obj), 'age');
console.log(r3); // false

Reflect.get(target, propertyKey[, receiver])

Reflect.set(target, propertyKey, value[, receiver])

这个方法用来读取/设置一个对象的属性,target是目标对象,propertyKey是我们要读取的属性,receiver是可选的,如果propertyKeygetter函数里面有this值,那么receiver就是这个this所代表的上下文。

Reflect.getOwnPropertyDescriptor(target, propertyKey)

这个方法与Object.getOwnPropertyDescriptor方法类似,其中target是目标对象,propertyKey是对象的属性,如果这个属性存在属性描述符的话就返回这个属性描述符;如果不存在的话,就返回undefined。(如果第一个参数不是对象的话,那么Object.getOwnPropertyDescriptor会将这个参数强制转换为对象,而方法 Reflect.getOwnPropertyDescriptor会抛出一个错误。)

var obj = {age: 22}
Reflect.getOwnPropertyDescriptor(obj, 'age')
{value: 22, writable: true, enumerable: true, configurable: true}

Reflect.getPrototypeOf(target)

Reflect.setPrototypeOf(target, prototype)

这个方法与Object.getPrototypeOf方法是一样的,都是返回一个对象的原型,也就是内部的[[Prototype]]属性的值。

Reflect.setPrototypeOfObject.setPrototypeOf方法的作用是相似的,设置一个对象的原型,如果设置成功的话,这个对象会返回一个true;如果设置失败,这个对象会返回一个false。

Reflect.has(target, propertyKey)

这个方法相当于ES5的in操作符,就是检查一个对象上是否含有特定的属性;我们继续来实践这个方法:

function A(name) {
 this.name = name || 'dreamapple';
}
A.prototype.getName = function() {
 return this.name;
};

var a = new A();

console.log('name' in a); // true
console.log('getName' in a); // true

let r1 = Reflect.has(a, 'name');
let r2 = Reflect.has(a, 'getName');
console.log(r1, r2); // true true

Reflect.isExtensible(target)

这个函数检查一个对象是否是可以扩展的,也就是是否可以添加新的属性。(要求target必须为一个对象,否则会抛出错误)

let obj = {};
let r1 = Reflect.isExtensible(obj);
console.log(r1); // true
// 密封这个对象
Object.seal(obj);
let r2 = Reflect.isExtensible(obj);
console.log(r2); // false

模块化

使用模块化,可以为我们带来以下好处:

  • 解决命名冲突
  • 提供复用性
  • 提高代码可维护性

立即执行函数

在早期,使用立即执行函数实现模块化,通过函数作用域解决了命名冲突、污染全局作用域的问题。

AMD 和 CMD

这两种实现方式已经很少见到,具体的使用方式如下:

// AMD
define(['./a', './b'],function(a, b){
 // 模块加载完毕可以使用
 a.do();
 b.do(); 
});

// CMD
define(function(require, exports, module){
 // 加载模块
 var a = require('./a'); 
});

CommonJS

CommonJS最早是Node在使用,目前可以在Webpack中见到它。

// a.js
module.exports = {
 a: 1
}

// or 
exports.a = 1;

// 在b.js中可以引入
var module = require('./a');
module.a // log 1

难点解析:

// module 基本实现
var module = {
 id: 'xxx',
 exports: {}
}

var exports = module.exports;
// 所以,通过对exports重新赋值,不能导出变量

ES Module

ES Module 是原生实现模块化方案。

// 导入模块
import xxx form './a.js';
import { xxx } from './b.js';

// 导出模块
export function a(){}

// 默认导出
export default {};
export default function(){}

ES Module和CommonJS区别

  • CommonJS支持动态导入,也就是require(${path}/xx.js),ES Module不支持
  • CommonJS是同步导入,因为用于服务器端,文件都在本地,同步导入即使卡住主线程影响也不大。而ES Module是异步导入,因为用于浏览器,需要下载文件,采用同步导入会对渲染有很大影响
  • CommonJS在导出时都是值拷贝,就算导出值变了,导入的值也不会改变。如果想更新值,必须重新导入一次。但是ES Module采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟导出值变化
  • ES Module 会编译成 require/exports 来执行的

手写简单版本的Promise

const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';

function MyPromise(fn) {
 const _this = this;
 _this.state = PENDING;
 _this.value = null;
 _this.resolvedCallbacks = [];
 _this.rejectedCallbacks = [];


 // resolve函数
 function resolve(value) {
  if (_this.state === PENDING) {
   _this.state = RESOLVED;
   _this.value = value;
   _this.resolvedCallbacks.map(cb => cb(_this.value));
  }
 }

 // rejected函数
 function reject(value) {
  if (_this.state === PENDING) {
   _this.state = REJECTED;
   _this.value = value;
   _this.rejectedCallbacks.map(cb => cb(_this.value));
  }
 }

 // 当创建对象的时候,执行传进来的执行器函数
 // 并且传递resolve和reject函数
 try {
  fn(resolve, reject);
 } catch (e) {
  reject(e);
 }
}

// 为Promise原型链上添加then函数
MyPromise.prototype.then = function (onFulfilled, onRejected) {
 const _this = this;
 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
 onRejected = typeof onRejected === 'function' ? onRejected : r => {
  throw r;
 }
 if (_this.state === PENDING) {
  _this.resolvedCallbacks.push(onFulfilled);
  _this.rejectedCallbacks.push(onRejected);
 }
 if (_this.state === RESOLVED) {
  onFulfilled(_this.value);
 }
 if (_this.state === REJECTED) {
  onRejected(_this.value);
 }
 return _this;
}



// 测试
new MyPromise(function (resolve, reject) {
 setTimeout(() => {
  resolve('hello');
 }, 2000);
}).then(v => {
 console.log(v);
}).then(v => {
 console.log(v + "1");
})

这篇文章就介绍到这了,需要的朋友可以参考一下。

Javascript 相关文章推荐
js 弹出新页面避免被浏览器、ad拦截的一种新方法
Apr 30 Javascript
JavaScript与HTML的结合方法详解
Nov 23 Javascript
详解Bootstrap四种图片样式
Jan 04 Javascript
学习JavaScript设计模式之责任链模式
Jan 18 Javascript
第二篇Bootstrap起步
Jun 21 Javascript
微信js-sdk上传与下载图片接口用法示例
Oct 12 Javascript
JS小数转换为整数的方法分析
Jan 07 Javascript
完美解决浏览器跨域的几种方法(汇总)
May 08 Javascript
Bootstrap 模态对话框只加载一次 remote 数据的完美解决办法
Jul 09 Javascript
微信小程序数据存储与取值详解
Jan 30 Javascript
layui.tree组件的使用以及搜索节点功能的实现
Sep 26 Javascript
js实现简单抽奖功能
Nov 24 Javascript
使用element-ui table expand展开行实现手风琴效果
Mar 15 #Javascript
element-ui组件table实现自定义筛选功能的示例代码
Mar 15 #Javascript
vue过滤器用法实例分析
Mar 15 #Javascript
vue v-for循环重复数据无法添加问题解决方法【加track-by='索引'】
Mar 15 #Javascript
详解vue移动端项目代码拆分记录
Mar 15 #Javascript
小程序自定义单页面、全局导航栏的实现代码
Mar 15 #Javascript
使用webpack搭建vue项目实现脚手架功能
Mar 15 #Javascript
You might like
thinkPHP多语言切换设置方法详解
2016/11/11 PHP
php微信公众号开发(3)php实现简单微信文本通讯
2016/12/15 PHP
PHP PDOStatement::nextRowset讲解
2019/02/01 PHP
Thinkphp5.0框架视图view的循环标签用法示例
2019/10/12 PHP
js 加载并解析XML字符串的代码
2009/12/13 Javascript
原生Js实现按的数据源均分时间点幻灯片效果(已封装)
2010/12/28 Javascript
jQuery 回车事件enter使用示例
2014/02/18 Javascript
jQuery实现新消息在网页标题闪烁提示
2015/06/23 Javascript
JS+CSS实现另类带提示效果的竖向导航菜单
2015/10/15 Javascript
JS实现关闭当前页而不弹出提示框的方法
2016/06/22 Javascript
AngularJS 避繁就简的路由
2016/07/01 Javascript
微信小程序 连续旋转动画(this.animation.rotate)详解
2017/04/07 Javascript
vue省市区三联动下拉选择组件的实现
2017/04/28 Javascript
JavaScript在控件上添加倒计时功能的实现代码
2017/07/04 Javascript
基于Vue过渡状态实例讲解
2017/09/14 Javascript
微信小程序实现action-sheet弹出底部菜单功能【附源码下载】
2017/12/09 Javascript
JavaScript生成一个不重复的ID的方法示例
2019/09/16 Javascript
[08:29]DOTA2每周TOP10 精彩击杀集锦vol.7
2014/06/25 DOTA
Python函数中*args和**kwargs来传递变长参数的用法
2016/01/26 Python
举例讲解Python中的迭代器、生成器与列表解析用法
2016/03/20 Python
用python制作游戏外挂
2018/01/04 Python
Python日志模块logging基本用法分析
2018/08/23 Python
Python实现定时执行任务的三种方式简单示例
2019/03/30 Python
Pytorch 定义MyDatasets实现多通道分别输入不同数据方式
2020/01/15 Python
使用python-pptx包批量修改ppt格式的实现
2020/02/14 Python
浅谈CSS3动画的回调处理
2016/07/21 HTML / CSS
Bibloo荷兰:女士、男士和儿童的服装、鞋子和配饰
2019/02/25 全球购物
俄罗斯药房连锁店:ASNA
2020/06/20 全球购物
会计自我鉴定范文
2013/10/06 职场文书
夜大毕业生自我鉴定
2013/10/31 职场文书
心得体会怎么写
2013/12/30 职场文书
党校培训自我鉴定范文
2014/03/20 职场文书
优秀家长事迹材料
2014/05/17 职场文书
应届生求职信范文
2014/05/26 职场文书
婚内房产协议书范本
2014/10/02 职场文书
Java基于字符界面的简易收银台
2021/06/26 Java/Android