一些你可能不熟悉的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 相关文章推荐
Prototype使用指南之string.js
Jan 10 Javascript
jquery png 透明解决方案(推荐)
Aug 21 Javascript
关于JS控制代码暂停的实现方法分享
Oct 11 Javascript
JavaScript的递归之递归与循环示例介绍
Aug 05 Javascript
jQuery解决input元素的blur事件和其他非表单元素的click事件冲突问题
Aug 15 Javascript
jQuery实现的表格展开伸缩效果实例
Sep 07 Javascript
JavaScript鼠标特效大全
Sep 13 Javascript
扩展Bootstrap Tooltip插件使其可交互的方法
Nov 07 Javascript
微信小程序中多个页面传参通信的学习与实践
May 05 Javascript
Angular 4依赖注入学习教程之FactoryProvider配置依赖对象(五)
Jun 04 Javascript
基于JavaScript实现弹幕特效
Aug 27 Javascript
Vue中实现回车键切换焦点的方法
Feb 19 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
PHP写MySQL数据 实现代码
2009/06/15 PHP
php随机取mysql记录方法小结
2014/12/27 PHP
JS中confirm,alert,prompt函数使用区别分析
2010/04/01 Javascript
Javascript 通过json自动生成Dom的代码
2010/04/01 Javascript
javascript仿php的print_r函数输出json数据
2013/09/13 Javascript
js hover 定时器(实例代码)
2013/11/12 Javascript
javascript实现10个球随机运动、碰撞实例详解
2015/07/08 Javascript
javascript日期操作详解(脚本之家整理)
2015/09/05 Javascript
js实现旋转木马效果
2017/03/17 Javascript
jQuery层级选择器_动力节点节点Java学院整理
2017/07/04 jQuery
vue复合组件实现注册表单功能
2017/11/06 Javascript
vue.js简单配置axios的方法详解
2017/12/13 Javascript
基于Vue开发数字输入框组件
2017/12/19 Javascript
js函数柯里化的方法和作用实例分析
2020/04/11 Javascript
NodeJS配置CORS实现过程详解
2020/12/02 NodeJs
Python函数可变参数定义及其参数传递方式实例详解
2015/05/25 Python
Python中的pygal安装和绘制直方图代码分享
2017/12/08 Python
一个月入门Python爬虫学习,轻松爬取大规模数据
2018/01/03 Python
Python 读取指定文件夹下的所有图像方法
2018/04/27 Python
Python IDLE清空窗口的实例
2018/06/25 Python
Tensorflow 同时载入多个模型的实例讲解
2018/07/27 Python
python实现对指定字符串补足固定长度倍数截断输出的方法
2018/11/15 Python
Python脚本利用adb进行手机控制的方法
2019/07/08 Python
pytorch中的上采样以及各种反操作,求逆操作详解
2020/01/03 Python
Python实现遗传算法(二进制编码)求函数最优值方式
2020/02/11 Python
详解如何使用CSS3中的结构伪类选择器和伪元素选择器
2020/01/06 HTML / CSS
Juicy Couture Beauty官方网站:香水和化妆品
2019/03/12 全球购物
如何利用cmp命令比较文件
2013/09/23 面试题
高一地理教学反思
2014/01/18 职场文书
农林环境专业求职信
2014/03/13 职场文书
教师批评与自我批评(群众路线)
2014/10/15 职场文书
仙境之桥观后感
2015/06/16 职场文书
重阳节活动主持词
2015/07/04 职场文书
使用pytorch实现线性回归
2021/04/11 Python
为什么MySQL不建议使用SELECT *
2022/04/03 MySQL
spring IOC容器的Bean管理XML自动装配过程
2022/05/30 Java/Android