在现代Web开发中,JavaScript 是不可或缺的编程语言。掌握其核心功能和原理对于开发者至关重要。本文通过手写实现JavaScript的一些关键功能和算法,帮助读者深入理解其工作原理,提升编程技能。无论你是初学者还是有经验的开发者,都能从中受益。
- /**
- * 自定义Promise函数模块
- */
- (function () {
-
- const PENDING = 'pending'
- const RESOLVED = 'resolved'
- const REJECTED = 'rejected'
-
-
- /**
- * Promise构造函数
- * @param {*} executor 执行器函数
- */
- function Promise(executor) {
- const self = this
- // 给promise对象指定status属性,初始值为pending
- self.status = PENDING
- // 给promise对象指定一个用于存储结果数据的属性
- self.data = undefined
- // 每个元素的结构 { onResolved(){}, onRejected() }
- self.callbacks = []
-
-
- function resolve(value) {
- // 此处做判断,使得promise的状态只能修改一次
- if (self.status === PENDING) {
- // 将状态改为 resolved
- self.status = RESOLVED
- // 保存value数据
- self.data = value
-
- // 如果有待执行的callback函数,立即异步执行回调
- if (self.callbacks.length > 0) {
- setTimeout(() => { // 表示在异步队列中执行
- self.callbacks.forEach(callbacksObj => {
- callbacksObj.onResolved(value)
- })
- }, 0);
- }
- }
- }
- function reject(reason) {
- // 此处做判断,使得promise的状态只能修改一次
- if (self.status === PENDING) {
- // 将状态改为 resolved
- self.status = REJECTED
- // 保存value数据
- self.data = reason
-
- // 如果有待执行的callback函数,立即异步执行回调
- if (self.callbacks.length > 0) {
- setTimeout(() => { // 表示在异步队列中执行
- self.callbacks.forEach(callbacksObj => {
- callbacksObj.onRejected(reason)
- })
- }, 0);
- }
- }
- }
-
- // 立即执行器
- try {
- executor(resolve, reject);
- } catch (error) { // 如果执行器抛出异常,promise状态变为rejected状态
- reject(error)
- }
- }
-
- /**
- * Promise原型对象的then方法
- */
- Promise.prototype.then = function (onResolved, onRejected) {
- const self = this
- onResolved = typeof onResolved === 'function' ? onResolved : value => value
- // 指定默认的失败的回调(实现错误/异常穿透的关键点)
- onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
-
-
- return new Promise((resolve, reject) => {
-
- /**
- * 处理onResolve和onRejected函数
- * @param {*} callback
- */
- function resolvePromise(callback) {
- try {
- const result = callback(self.data)
- if (result instanceof Promise) {
- result.then(resolve, reject)
- } else if (result !== null && (typeof result === 'object' || typeof result === 'function')) {
- // 拿到result.then
- const then = result.then;
- if (typeof then === 'function') {
- then(resolve, reject)
- } else {
- resolve(then)
- }
- } else {
- resolve(result)
- }
- } catch (error) {
- reject(error)
- }
- }
-
-
- if (self.status === RESOLVED) {
- setTimeout(() => {
- /**
- * 1. 如果抛出异常,return 的 promise就会失败,reason就是error
- * 2. 如果回调函数返回不是promise,return的promise就会成功,value就是返回值
- * 3. 如果回调函数返回的是一个promise,return的promise的结果就是这个promise的结果,value就是返回值
- */
- resolvePromise(onResolved)
- }, 0);
- } else if (self.status === REJECTED) {
- setTimeout(() => {
- resolvePromise(onRejected)
- }, 0);
- } else {
- self.callbacks.push({
- onResolved(value) {
- resolvePromise(onResolved)
- },
- onRejected(reason) {
- resolvePromise(onRejected)
- }
- })
- }
- })
- }
-
- /**
- * Promise原型对象的catch方法
- */
- Promise.prototype.catch = function (onRejected) {
- return this.then(undefined, onRejected)
- }
-
- /**
- * Promise函数对象的resolve方法
- *
- * 返回一个指定结果的成功的promise
- */
- Promise.resolve = function (value) {
- return new Promise((resolve, reject) => {
- if (value instanceof Promise) {
- value.then(resolve, reject)
- } else {
- resolve(value)
- }
- })
- }
-
- /**
- * Promise函数对象的reject方法
- */
- Promise.reject = function (reason) {
- return new Promise((resolve, reject) => {
- reject(reason)
- })
- }
-
- /**
- * Promise函数对象的all方法
- */
- Promise.all = function (promises) {
- // 判断数组
- if (!(promises instanceof Array)) {
- return Promise.reject(new Error('params must be a Array!'))
- }
- // 用来保存所有数据成功value的数组
- const resultArray = new Array(promises.length)
- // 用来保存成功数量的计数
- let resultCount = 0
- return new Promise((resolve, reject) => {
- // 遍历获取每个promise的结果
- promises.forEach((p, index) => {
- Promise.resolve(p).then(value => {
- resultCount++
- resultArray[index] = value
- // 如果全部都成功了,将return的promise变为成功
- if (resultCount === promises.length) {
- // 表示成功
- resolve(resultArray)
- }
- }, reason => {
- reject(reason)
- })
- })
- })
- }
-
- /**
- * Promise函数对象的race方法
- */
- Promise.race = function (promises) {
- // 判断数组
- if (!(promises instanceof Array)) {
- return Promise.reject(new Error('params must be a Array!'))
- }
-
- return new Promise((resolve, reject) => {
- // 遍历数组
- promises.forEach((p, index) => {
- Promise.resolve(p).then(value => {
- resolve(value)
- }, reason => {
- reject(reason)
- })
- })
- })
- }
-
- // ---------------------------------------实现自己的方法---------------------------------------
- /**
- * 返回一个promise对象,它在指定的时间后才确定成功结果
- * @param {*} value
- * @param {*} time
- */
- Promise.resolveDelay = function (value, time) {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- if (value instanceof Promise) {
- value.then(resolve, reject)
- } else {
- resolve(value)
- }
- }, time);
- })
- }
-
-
- /**
- * 返回一个promise对象,它在指定的时间后才确定失败结果
- * @param {*} reason
- * @param {*} time
- */
- Promise.rejectDelay = function (reason, time) {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- reject(reason)
- }, time);
- })
- }
-
-
-
- // 向外暴露函数
- window.Promise = Promise
- })(window)
- call 接收多个参数,第一个为函数上下文也就是this,后边参数为函数本身的参数。
-
- let obj = {
- name: "一个"
- }
-
- function allName(firstName, lastName) {
- console.log(this)
- console.log(`我的全名是“${firstName}${this.name}${lastName}”`)
- }
- // 很明显此时allName函数是没有name属性的
- allName('我是', '前端') //我的全名是“我是前端” this指向window
- allName.call(obj, '我是', '前端') //我的全名是“我是一个前端” this指向obj
- 复制代码
- apply
- apply接收两个参数,第一个参数为函数上下文this,第二个参数为函数参数只不过是通过一个数组的形式传入的。
-
- allName.apply(obj, ['我是', '前端'])//我的全名是“我是一个前端” this指向obj
- 复制代码
- bind
- bind 接收多个参数,第一个是bind返回值返回值是一个函数上下文的this,不会立即执行。
-
- let obj = {
- name: "一个"
- }
-
- function allName(firstName, lastName, flag) {
- console.log(this)
- console.log(`我的全名是"${firstName}${this.name}${lastName}"我的座右铭是"${flag}"`)
- }
- allName.bind(obj) //不会执行
- let fn = allName.bind(obj)
- fn('我是', '前端', '好好学习天天向上')
-
- // 也可以这样用,参数可以分开传。bind后的函数参数默认排列在原函数参数后边
- fn = allName.bind(obj, "你是")
- fn('前端', '好好学习天天向上')
- 复制代码
- 接下来搓搓手实现call、apply和bind
-
- 实现call
- let Person = {
- name: 'Tom',
- say() {
- console.log(this)
- console.log(`我叫${this.name}`)
- }
- }
-
- // 先看代码执行效果
- Person.say() //我叫Tom
- Person1 = {
- name: 'Tom1'
- }
-
- // 我们尝试用原生方法call来实现this指向Person1
- Person.say.call(Person1) //我叫Tom1
-
- 复制代码
- 通过第一次打印执行和第二次打印执行我发现,如果Person1有say方法那么Person1直接执行Person1.say() 结果就是我是Tom1,是的call就是这么实现的。 再看代码
-
- Function.prototype.MyCall = function(context) {
- //context就是demo中的Person1
- // 必须此时调用MyCall的函数是say方法,那么我们只需要在context上扩展一个say方法指向调用MyCall的say方法这样this
- console.log(this)
- context.say = this //Mycall里边的this就是我们虚拟的say方法
- context.say()
- }
- // 测试
- Person.say.MyCall(Person1)//我叫Tom1
- 复制代码
- perfect!爆棚的满足感!不过拿脚趾头想想也不会这么简单,继续完善 我们自己找茬 1、call支持多个参数,有可能一个也不没有 2、考虑多参数时要把参数传给扩展方法。 3、给上下文定义的函数要保持唯一不能是say 4、扩展完我们需要吧自定义函数删除 接下来针对找茬问题一一解决
- let Person = {
- name: 'Tom',
- say() {
- console.log(this)
- console.log(`我叫${this.name}`)
- }
- }
- Person1 = {
- name: 'Tom1'
- }
- //如果没有参数
- Person.say.call()
- 复制代码
- 没有指定this,this指向window
- 我们也要这样
-
- Function.prototype.MyCall = function(context) {
- // 如果没有参数我们参考call的处理方式
- context = context || window
- //context就是demo中的Person1
- // 必须此时调用MyCall的函数是say方法,那么我们只需要在context上扩展一个say方法指向调用MyCall的say方法这样this
- context.say = this //Mycall里边的this就是我们虚拟的say方法
- context.say()
- }
-
- Person.say.MyCall()
- 复制代码
- 没毛病! 继续解决
- // 找茬2:我们默认定义context.say = this fn如果已经被占用 嘎嘎 sb了。 不怕 搞定它
-
- // say需要是一个唯一值 是不是突然想到es6的新类型 Symbol fn = Symbol() 不过我们装逼不嫌事大 都说自己实现了
-
- function mySymbol(obj) {
- // 不要问我为什么这么写,我也不知道就感觉这样nb
- let unique = (Math.random() + new Date().getTime()).toString(32).slice(0, 8)
- // 牛逼也要严谨
- if (obj.hasOwnProperty(unique)) {
- return mySymbol(obj) //递归调用
- } else {
- return unique
- }
- }
- //接下来我们一并把多参数和执行完删除自定义方法删除掉一块搞定
- Function.prototype.myCall1 = function(context) {
- // 如果没有传或传的值为空对象 context指向window
- context = context || window
- let fn = mySymbol(context)
- context.fn = this //给context添加一个方法 指向this
- // 处理参数 去除第一个参数this 其它传入fn函数
- let arg = [...arguments].slice(1) //[...xxx]把类数组变成数组,arguments为啥不是数组自行搜索 slice返回一个新数组
- context.fn(...arg) //执行fn
- delete context.fn //删除方法
- }
-
- let Person = {
- name: 'Tom',
- say(age) {
- console.log(this)
- console.log(`我叫${this.name}我今年${age}`)
- }
- }
-
- Person1 = {
- name: 'Tom1'
- }
-
- Person.say.call(Person1,18)//我叫Tom1我今年18
- 复制代码
- 测试结果相当完美!
-
- 实现apply
- 接下来apply就简单多了,只有多参数时第二个参数是数组,就不一步步细说了。
-
- Function.prototype.myApply = function(context) {
- // 如果没有传或传的值为空对象 context指向window
- if (typeof context === "undefined" || context === null) {
- context = window
- }
- let fn = mySymbol(context)
- context.fn = this //给context添加一个方法 指向this
- // 处理参数 去除第一个参数this 其它传入fn函数
- let arg = [...arguments].slice(1) //[...xxx]把类数组变成数组,arguments为啥不是数组自行搜索 slice返回一个新数组
- context.fn(arg) //执行fn
- delete context.fn //删除方法
-
- }
- 复制代码
- 实现bind
- 这个和call、apply区别还是很大的,容我去抽根烟回来收拾它 还是老套路先分析bind都能干些什么,有什么特点 1、函数调用,改变this 2、返回一个绑定this的函数 3、接收多个参数 4、支持柯里化形式传参 fn(1)(2)
-
- Function.prototype.bind = function(context) {
- //返回一个绑定this的函数,我们需要在此保存this
- let self = this
- // 可以支持柯里化传参,保存参数
- let arg = [...arguments].slice(1)
- // 返回一个函数
- return function() {
- //同样因为支持柯里化形式传参我们需要再次获取存储参数
- let newArg = [...arguments]
- console.log(newArg)
- // 返回函数绑定this,传入两次保存的参数
- //考虑返回函数有返回值做了return
- return self.apply(context, arg.concat(newArg))
- }
- }
-
- // 搞定测试
- let fn = Person.say.bind(Person1)
- fn()
- fn(18)
3、手写一个instanceOf
- 实现思路:
- 首先 instanceof 左侧必须是对象, 才能找到它的原型链
-
- instanceof 右侧必须是函数, 函数才会prototype属性
-
- 迭代 , 左侧对象的原型不等于右侧的 prototype时, 沿着原型链重新赋值左侧
-
-
-
- 复制代码
- // [1,2,3] instanceof Array ---- true
-
- // L instanceof R
- // 变量R的原型 存在于 变量L的原型链上
- function instance_of(L,R){
- // 验证如果为基本数据类型,就直接返回false
- const baseType = ['string', 'number','boolean','undefined','symbol']
- if(baseType.includes(typeof(L))) { return false }
-
- let RP = R.prototype; //取 R 的显示原型
- L = L.__proto__; //取 L 的隐式原型
- while(true){ // 无线循环的写法(也可以使 for(;;) )
- if(L === null){ //找到最顶层
- return false;
- }
- if(L === RP){ //严格相等
- return true;
- }
- L = L.__proto__; //没找到继续向上一层原型链查找
- }
- }
- !DOCTYPE html>
-
- <html>
- <!-- 防抖 -->
- <!-- 防抖就是在n秒内 防止连续触发,在n秒内触发了下一次,那就重新计算 -->
-
- <body>
- <div id="content"
- style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"></div>
- <script>
- let num = 1;
- let content = document.getElementById('content');
- /**
- *非立即执行版
- **/
- function debounceNoAtOnce(func, wait) {
- let timeout;
- return function () {
- let context = this;
- let args = arguments;
- if (timeout) clearTimeout(timeout);
- timeout = setTimeout(() => {
- func.apply(context, args)
- }, wait);
- }
- };
-
- /**
- *立即执行版
- **/
- function debounceAtOnce(func, wait) {
- let timeout;
- return function () {
- let context = this;
- let args = arguments;
- debugger
- if (timeout) clearTimeout(timeout);
-
- let callNow = !timeout;
- timeout = setTimeout(() => {
- timeout = null;
- }, wait)
-
- if (callNow) func.apply(context, args)
- }
- }
- /**
- *聚合版
- **/
- function debounce(func, wait, immediate) {
- let timeout;
-
- return function () {
- let context = this;
- let args = arguments;
-
- if (timeout) clearTimeout(timeout);
- if (immediate) {
- var callNow = !timeout;
- timeout = setTimeout(() => {
- timeout = null;
- }, wait)
- if (callNow) func.apply(context, args)
- } else {
- timeout = setTimeout(function () {
- func.apply(context, args)
- }, wait);
- }
- }
- }
-
- function count() {
- content.innerHTML = num++;
- };
- content.onmousemove = debounce(count, 1000, true);
- </script>
- </body>
- <script>
- </script>
-
- </html>
- 防抖的目的在于:n秒内点击多少次都算一次+每次点击都重新计算时间
- <!DOCTYPE html>
-
- <html>
- <!-- 节流 -->
- <!-- 节流是为了固定的时间段内只能点击一次 -->
-
- <body>
- <div id="content"
- style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"></div>
- <script>
- let num = 1;
- let content = document.getElementById('content');
- /**
- *throttleTime 时间戳版
- **/
- throttleTime = function (func, wait) {
- let previde = 0;
- return function () {
- let nowDate = Date.now();
- if (nowDate - previde > wait) {
- func();
- previde = nowDate;
- }
- }
- }
- /**
- *throttleSet 定时器版
- **/
- throttleSet = function (func, wait) {
- let timeout;
- return function () {
- if (!timeout) {
- timeout = setTimeout(() => {
- timeout = false
- func()
- }, wait)
- }
- }
- }
-
- function count() {
- content.innerHTML = num++;
- };
- content.onmousemove = throttleSet(count, 1000);
- </script>
- </body>
- <script>
- </script>
-
- </html>
节流的目的在于:固定时间段内只能点击一次
应用场景:输入框输入+提交/确定
- function createXMLHTTPRequest() {
- //1.创建XMLHttpRequest对象
- //这是XMLHttpReuquest对象无部使用中最复杂的一步
- //需要针对IE和其他类型的浏览器建立这个对象的不同方式写不同的代码
- var xmlHttpRequest;
- if (window.XMLHttpRequest) {
- //针对FireFox,Mozillar,Opera,Safari,IE7,IE8
- xmlHttpRequest = new XMLHttpRequest();
- //针对某些特定版本的mozillar浏览器的BUG进行修正
- if (xmlHttpRequest.overrideMimeType) {
- xmlHttpRequest.overrideMimeType("text/xml");
- }
- } else if (window.ActiveXObject) {
- //针对IE6,IE5.5,IE5
- //两个可以用于创建XMLHTTPRequest对象的控件名称,保存在一个js的数组中
- //排在前面的版本较新
- var activexName = [ "MSXML2.XMLHTTP", "Microsoft.XMLHTTP" ];
- for ( var i = 0; i < activexName.length; i++) {
- try {
- //取出一个控件名进行创建,如果创建成功就终止循环
- //如果创建失败,回抛出异常,然后可以继续循环,继续尝试创建
- xmlHttpRequest = new ActiveXObject(activexName[i]);
- if(xmlHttpRequest){
- break;
- }
- } catch (e) {
- }
- }
- }
- return xmlHttpRequest;
- }
JSONP实现原理
jsonp,其实就是单纯为了实现跨域请求而创造的一个欺骗(trick)。
虽然,因为同源策略的影响,不能通过XMLHttpRequest请求不同域上的数据(Cross -origin reads)。但是,在页面上引入不同域上的js脚本文件却是可以的(Cross -origin embedding)。因此,在js文件载入完毕之后,触发回调,可以将需要的data作为参数传入 注意,实现方式(需前后端配合)
优点
兼容性好(兼容低版本IE)
缺点
JSONP只支持GET请求,XMLHttpRequest相对于JSONP有着更好的错误处理机制。
实现
1.服务端采用的是Node,服务端处理请求方法如下
- router.get('/', function(req, res, next) {
-
-
-
- console.log('收到客户端的请求:', req.query);
-
-
-
- // 传回到客户端的数据
-
-
-
- let data = JSON.stringify({
-
-
-
- 'status':200,
-
-
-
- 'result':{
-
-
-
- 'name':'柳成荫',
-
-
-
- 'site':'123456'
-
-
-
- }
-
-
-
- });
-
-
-
- // 获取方法名称 - 这是客户端传过来的方法名参数
-
-
-
- // 因为这个方法名必须是客户端有的,必须要客户端告诉服务端是哪个方法
-
-
-
- let methodName = req.query.callback;
-
-
-
- let methodStr = methodName + '(' + data + ')';
-
-
-
- // 快速结束没有任何数据的响应,客户端会执行这个方法,从而获取到服务端返回的数据
-
-
-
- res.end(methodStr)
-
-
-
- });
2.封装JSONP
- (function (w) {
-
-
-
- /**
-
-
-
- * jsonp的实现
-
-
-
- * @param {Object}option
-
-
-
- */
-
-
-
- function jsonp(option) {
-
-
-
- // 把success函数挂载在全局的getDate函数上
-
-
-
- w.getData = option.success;
-
-
-
- // 处理url,拼接参数 - 回调方法是getData
-
-
-
- option.url = option.url + '?callback=getData';
-
-
-
- // 创建script标签,并插入body
-
-
-
- let scriptEle = document.createElement('script');
-
-
-
- scriptEle.src = option.url;
-
-
-
- document.body.appendChild(scriptEle);
-
-
-
- }
-
-
-
- // 全局挂载一个jsonp函数
-
-
-
- w.jsonp = jsonp;
-
-
-
- })(window);
-
-
-
-
-
-
-
- /**
-
-
-
- * 把对象转换成拼接字符串
-
-
-
- * 把形如
-
-
-
- data:{
-
-
-
- "sex":"男",
-
-
-
- "name":"九月"
-
-
-
- }
-
-
-
- 转换成sex=男&name=九月
-
-
-
- * @param paramObj 对象参数
-
-
-
- * @param words
-
-
-
- * @returns {string} 字符串
-
-
-
- */
-
-
-
- function getStrWithObject(paramObj,words){
-
-
-
- let resArr = [];
-
-
-
- // 1.转换对象
-
-
-
- for(let key in paramObj){
-
-
-
- let str = key + '=' + paramObj[key];
-
-
-
- resArr.push(str);
-
-
-
- }
-
-
-
- resArr.push(words);
-
-
-
- // 3.数组转换成字符串
-
-
-
- return resArr.join("&");
-
-
-
- }
看似代码没有任何问题。但是,我们测试一下,多次调用jsonp方法。
- jsonp({
-
-
-
- url:'http://localhost:3000/',
-
-
-
- data:{
-
-
-
- "sex":"男",
-
-
-
- "name":"九月"
-
-
-
- },
-
-
-
- success:function (data) {
-
-
-
- console.log(data);
-
-
-
- alert(1);
-
-
-
- }
-
-
-
- });
-
-
-
-
-
-
-
- jsonp({
-
-
-
- url:'http://localhost:3000/',
-
-
-
- data:{
-
-
-
- "sex":"男",
-
-
-
- "name":"九月"
-
-
-
- },
-
-
-
- success:function (data) {
-
-
-
- console.log(data);
-
-
-
- alert(2);
-
-
-
- }
-
-
-
- });
结果会怎么样?的确,还是会执行2次,但是每次执行的都是第二个jsonp方法。这是为什么?
问题出现在这里,多次调用,会发生函数覆盖。
解决方案就是让每一次调用函数名不一致,在JS里有很多方法,比如通过随机数、时间戳之类的。这里采用的是随机数,修改如下。
- // 0.产生不同的函数名 - 解决了调用多次请求,造成覆盖的问题
-
-
-
- // 如果方法名相同,调用多次,都会执行最后一个,出现覆盖现象
-
-
-
- let callBackName = 'lcy' + Math.random().toString().substr(2)
-
-
-
- + Math.random().toString().substr(2);
-
-
-
- // 把success函数挂载在全局的getDate函数上
-
-
-
- w[callBackName] = option.success;
-
-
-
- // 处理url,拼接参数
- option.url = option.url + '?' + getStrWithObject(option.data,'callback='+callBackName);
好,现在看似完美解决。然而,我们发现,每次调用jsonp方法,都会在body里插入一个script标签。我们并不希望在调用jsonp之后,添加这样一个标签,我们需要在调用完之后,将其移除。
怎么做呢?我们在将success函数挂载在全局时,我们在success外层再套个函数。在这个函数里调用success方法之后,即已经请求到数据之后,我们就去把这个script标签给它移除就行了。修改如下
- // 1.函数挂载在全局
-
-
-
- w[callBackName] = function(data){
-
-
-
- option.success(data);
-
-
-
- // 删除script标签
-
-
-
- document.body.removeChild(scriptEle);
-
-
-
- };
- 整个过程就是这样,以下是完整代码。
-
- (function (w) {
-
-
-
- /**
-
-
-
- * jsonp的实现
-
-
-
- * @param {Object}option
-
-
-
- */
-
-
-
- function jsonp(option) {
-
-
-
- // 0.产生不同的函数名 - 解决了调用多次请求,造成覆盖的问题
-
-
-
- // 如果方法名相同,调用多次,都会执行最后一个,出现覆盖现象
-
-
-
- let callBackName = 'lcy' + Math.random().toString().substr(2) + Math.random().toString().substr(2);
-
-
-
- // 1.函数挂载在全局
-
-
-
- w[callBackName] = function(data){
-
-
-
- option.success(data);
-
-
-
- // 删除script标签
-
-
-
- document.body.removeChild(scriptEle);
-
-
-
- };
-
-
-
- // 2.处理url
-
-
-
- option.url = option.url + '?' + getStrWithObject(option.data,'callback='+callBackName);
-
-
-
- // 3.创建script标签插入body
-
-
-
- let scriptEle = document.createElement('script');
-
-
-
- scriptEle.src = option.url;
-
-
-
- document.body.appendChild(scriptEle);
-
-
-
- }
-
-
-
- w.jsonp = jsonp;
-
-
-
- })(window);
-
-
-
-
-
-
-
- /**
-
-
-
- * 把对象转换成拼接字符串
-
-
-
- * @param paramObj 对象参数
-
-
-
- * @returns {string} 字符串
-
-
-
- */
-
-
-
- function getStrWithObject(paramObj,words){
-
-
-
- let resArr = [];
-
-
-
- // 1.转换对象
-
-
-
- for(let key in paramObj){
-
-
-
- let str = key + '=' + paramObj[key];
-
-
-
- resArr.push(str);
-
-
-
- }
-
-
-
- resArr.push(words);
-
-
-
- // 3.数组转换成字符串
-
-
-
- return resArr.join("&");
-
-
-
- }
- ar a = {
- name: "muyiy",
- book: {
- title: "You Don't Know JS",
- price: "45"
- },
- a1: undefined,
- a2: null,
- a3: 123
- }
- // hasOwnProperty表示是否有自己的属性。这个方法会查找一个对象是否有某个属性,但是不会去查找它的原型链。
- function cloneDeep1(source) {
- var target = {};
- for (var key in source) {
- if (source.hasOwnProperty(key)) {
- if (typeof source[key] === 'object') {
- target[key] = cloneDeep1(source[key]); // 注意这里
- } else {
- target[key] = source[key];
- }
- }
- }
- return target;
- }
-
- // 使用上面测试用例测试一下
- console.log(a)
- var b = cloneDeep1(a);
- a.name = "前端进阶";
- a.book.price = "55";
- console.log(b);
-
- 考虑再全一些的深拷贝
-
-
- function deepClone(obj,hash = new WeakMap()){ //递归实现
- if(obj instanceof RegExp) return new RegExp(obj);
- if(obj instanceof Date) return new Date(obj);
- if(obj === null || typeof obj != "object"){
- // 普通数据类型
- return obj;
- }
- if(hash.has(obj)){
- return hash.get(obj);
- }
- // 下面是数组和对象的判断
- let t = new obj.constructor();
- hash.set(obj,t);
- for(let key in obj){
- // 递归
- if(obj.hasOwnProperty(key)){ //是否是自身的属性
- t[key] = deepClone(obj[key],hash)
- }
- }
- return t;
- }
- function print() {
- console.log(this.name, ...arguments);
- }
-
- const obj = {
- name: 'mxin',
- };
-
- // Function.prototype.__bind()
- console.log('Function.prototype.__bind()');
-
- // 直接调用,返回原函数拷贝,this 指向 obj
- const F = print.__bind(obj, 26);
- F(178); // mxin, 26, 178
-
- // new 情况
- const _obj = new F(145); // undefined, 26, 145
- console.log(_obj); // print {}
-
- // Function.prototype.bind()
- console.log('Function.prototype.bind()');
-
- const Fn = print.bind(obj, 26);
- Fn(178); // mxin, 26, 178
-
- const __obj = new Fn(145); // undefined, 26, 145
- console.log(__obj); // print {}
- /**
- * 模拟 call
- * 使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数
- * @param {object} ctx
- * @param {...any} args
- * @returns {any} 调用 this 的返回值,若无有返回值,则返回 undefined
- */
- Function.prototype.__call = function (ctx, ...args) {
- if (typeof this !== 'function') throw new TypeError('Error');
-
- // 考虑 null 情况,参数默认赋值会无效
- if (!ctx) ctx = window;
-
- // 将 this 函数保存在 ctx 上
- ctx.fn = this;
-
- // 传参执行并保存返回值
- const res = ctx.fn(...args);
-
- // 删除 ctx 上的 fn
- delete ctx.fn;
-
- return res;
- };
-
- // ------------------------------ 测试 ------------------------------
-
- function Product(name, price) {
- this.name = name;
- this.price = price;
- }
-
- // Function.prototype.__call()
- console.log('Function.prototype.__call()');
-
- function Food(name, price) {
- Product.__call(this, name, price);
- this.category = 'food';
- }
- const food = new Food('cheese', 5);
- console.log(food);
- // Food {name: "cheese", price: 5, category: "food"}
- // category: "food"
- // name: "cheese"
- // price: 5
- // __proto__:
- // constructor: ƒ Food(name, price)
- // __proto__: Object
-
- // Function.prototype.call()
- console.log('Function.prototype.call()');
-
- function Toy(name, price) {
- Product.call(this, name, price);
- this.category = 'toy';
- }
- const toy = new Toy('car', 10);
- console.log(toy);
- // Toy {name: "car", price: 10, category: "toy"}
- // category: "toy"
- // name: "car"
- // price: 10
- // __proto__:
- // constructor: ƒ Toy(name, price)
- // __proto__: Object
- /**
- * 模拟 apply
- * 调用一个具有给定 this 值的函数,以及以一个数组(或类数组对象)的形式提供的参数
- * @param {object} ctx
- * @param {} args
- */
- Function.prototype.__apply = function (ctx, args) {
- if (typeof this !== 'function') throw new TypeError('Error');
-
- // 考虑 null 情况,参数默认赋值会无效
- if (!ctx) ctx = window;
-
- // 将 this 函数保存在 ctx 上
- ctx.fn = this;
-
- // 传参执行并保存返回值
- const result = ctx.fn(...args);
-
- // 删除 ctx 上的 fn
- delete ctx.fn;
-
- return result;
- };
-
- // ------------------------------ 测试 ------------------------------
-
-
- const numbers = [5, 6, 2, 3, 7];
-
- // Function.prototype.__apply()
- console.log('Function.prototype.__apply()');
-
- const max = Math.max.__apply(null, numbers);
- console.log(max); // 7
-
- // Function.prototype.apply()
- console.log('Function.prototype.apply()');
- const min = Math.min.apply(null, numbers);
- console.log(min); // 2